You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
            }
        }
    }
}

问题原因&解决办法

核心问题

  1. 每次点击按钮调用addWaypoint时,都会创建新的sink订阅,但旧订阅未被取消,多个订阅同时监听位置更新。
  2. @Published属性在订阅时会立即发送当前存储的旧值,加上新获取的位置值,导致sink被触发多次。

具体修复步骤

  1. 订阅前取消旧订阅:创建新订阅前调用cancellable?.cancel(),避免多个订阅共存。
  2. 过滤无效/旧值:使用compactMap过滤nil值,配合first()只接收第一次有效位置更新,完成后自动取消订阅。
  3. 重置位置状态:在获取新位置前,重置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 17:20:30