Swift中.sink订阅器重复执行问题求助
问题:.sink重复执行导致位置重复添加到数组
我在CoordinatesViewModel中写了addWaypoint函数,用户点击按钮时触发该函数。LocationManager中实现了获取用户位置后停止更新的逻辑,我用.sink订阅LocationManager的位置数据(刚接触Swift,不确定这个用法是否正确)。现在发现.sink有时会执行两次甚至更多次,导致同一位置被重复添加到数组中,第二次点击按钮时这个问题尤其明显。
控制台打印示例:
hello from ViewModel hello from LocationManager hello from ViewModel
LocationManager代码
private let manager = CLLocationManager() @Published var userWaypoint: CLLocation? = nil override init(){ super.init() manager.desiredAccuracy = kCLLocationAccuracyBest manager.requestWhenInUseAuthorization() manager.delegate = self } func getCurrentUserWaypoint(){ wasWaypointButtonPressed = true manager.requestWhenInUseAuthorization() manager.startUpdatingLocation() } extension LocationManager: CLLocationManagerDelegate{ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let userWaypoint = locations.last else {return} print("hello from LocationManager") DispatchQueue.main.async { self.userWaypoint = userWaypoint } self.manager.stopUpdatingLocation() } }
CoordinatesViewModel代码
private let locationManager: LocationManager var cancellable: AnyCancellable? @Published var waypoints: [CoordinateData] = [] init() { locationManager = LocationManager() } func addWaypoint(){ locationManager.getCurrentUserWaypoint() cancellable = locationManager.$userWaypoint.sink{ userWaypoint in if let userWaypoint = userWaypoint{ print("hello from ViewModel") DispatchQueue.main.async{ let newWaypoint = CoordinateData(coordinates: userWaypoint.coordinate) self.waypoints.append(newWaypoint) } } } }
问题原因&解决办法
核心问题
- 每次点击按钮调用
addWaypoint时,都会创建新的sink订阅,但旧订阅未被取消,多个订阅同时监听位置更新。 @Published属性在订阅时会立即发送当前存储的旧值,加上新获取的位置值,导致sink被触发多次。
具体修复步骤
- 订阅前取消旧订阅:创建新订阅前调用
cancellable?.cancel(),避免多个订阅共存。 - 过滤无效/旧值:使用
compactMap过滤nil值,配合first()只接收第一次有效位置更新,完成后自动取消订阅。 - 重置位置状态:在获取新位置前,重置
userWaypoint为nil,避免订阅时直接拿到旧值。
修复后的代码示例
修改后的CoordinatesViewModel
private let locationManager: LocationManager var cancellable: AnyCancellable? @Published var waypoints: [CoordinateData] = [] init() { locationManager = LocationManager() } func addWaypoint(){ // 取消旧订阅 cancellable?.cancel() // 重置位置管理器的旧位置数据 locationManager.resetWaypoint() locationManager.getCurrentUserWaypoint() cancellable = locationManager.$userWaypoint // 过滤掉nil,只保留有效位置 .compactMap { $0 } // 只取第一次更新的值,完成后自动结束订阅 .first() .sink(receiveValue: { userWaypoint in print("hello from ViewModel") let newWaypoint = CoordinateData(coordinates: userWaypoint.coordinate) self.waypoints.append(newWaypoint) }) }
LocationManager新增重置方法
func resetWaypoint() { userWaypoint = nil }
额外优化点
getCurrentUserWaypoint中重复调用manager.requestWhenInUseAuthorization()没必要,授权只需在初始化时请求一次,后续可先判断授权状态再处理。didUpdateLocations中DispatchQueue.main.async可保留,但@Published属性更新默认会在主线程触发,可根据实际情况调整。
内容的提问来源于stack exchange,提问作者ThisDude
相关产品推荐
相关产品推荐

