肇鑫的技术博客

业精于勤,荒于嬉

深入探讨NotificationCenter的addObserver(forName:object:queue:using:)中的queque

先看看下面的代码,这两段notiObserver有区别吗?

override func viewDidLoad() {
    super.viewDidLoad()
    
    let noti = Notification(name: MyNotificationName)
    var v = 0
    let center = NotificationCenter.default
    var notiObserver:NSObjectProtocol! = nil
    
    notiObserver = center.addObserver(forName: MyNotificationName, object: nil, queue: .main) {_ in 
        if v == 10 { center.removeObserver(notiObserver) }
        else {
            v += 1
            center.post(noti)
        }
        print(v)
    }
    
    notiObserver = center.addObserver(forName: MyNotificationName, object: nil, queue: nil) { _ in
        DispatchQueue.main.async {
            if v == 10 { center.removeObserver(notiObserver) }
            else {
                v += 1
                center.post(noti)
            }
            print(v)
        }
    }
    
    center.post(noti)
}

无论你觉得有区别,还是没区别,你可以分别注释掉其中一个然后运行。运行结果是这样的。

上面的那段会输出

10
10
10
10
10
10
10
10
10
10
10

下面那段会输出

1
2
3
4
5
6
7
8
9
10
10

这是为什么呢?我们来看文档。苹果的文档在提到queue是这么说的。

queue
The operation queue to which block should be added.
If you pass nil, the block is run synchronously on the posting thread.

也就是说,它默认是在队列中同步执行。即便我们换成了.main,它也不是我们以为的是异步执行的。实际上,我们可以测试出来,这段代码,在运行时,是堆栈式的。新到的Notification,会进行压栈,优先执行。然后才是旧的Notification没执行完的代码。

如图所示,从左到右是时间轴,依次运行的3段代码,绿色的center.post(noti)导致黄色被执行,黄色的center.post(noti)导致蓝色被执行……,一直到v==10,最后的一段代码print(v),才会从上到下执行下来。

stack
结论:如果我们想要代码符合我们的预期的顺序,应该使用第二种方式,通过GCD,将代码加入到顺序执行的queue中去。

iOS app与watchOS app数据同步

保存的数据库采用realm。手表部分的app具备与iOS端相同的功能。

基础部分

数据保存在用户的Document里。升级app数据不会消失,卸载app数据会消失。

同步算法

以iOS端为核心。watchOS发起同步请求。

首次启动

WCSession的生命周期最为标准。每次WCSessionDelegatesession(_:activationDidCompleteWith:error:)的运行被认为是一次首次启动。

  • iOS:设定需要同步数据库needSyncDatabase = true。在同步数据库完成之前,不会发送新的同步信息。
// MARK: - WatchConnectivity
extension AppDelegate:WCSessionDelegate {
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        if transfer?.isTransferring == true { transfer?.cancel() }
        
        needSyncDatabase = true
        transfer = session.transferUserInfo(["needSyncDatabase":needSyncDatabase])
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        
    }
}
  • watchOS:设定同步数据库,手表端有两种情况。
    • WCSession首次启动时主动同步。
    • 接收到iOS发来的同步请求时同步。
// MARK: -WCSessionDelegate
extension ExtensionDelegate:WCSessionDelegate {
    func shouldSyncDatabase() -> Bool {
        return needSyncDatabase && !isSyncingDatabase
    }
    
    func willSyncDatabase() {
        isSyncingDatabase = true
    }
    
    func didSyncDatabase() {
        needSyncDatabase = false
        isSyncingDatabase = false
    }
    
    func syncDatabase() {
        guard shouldSyncDatabase() else { return }
        willSyncDatabase()
        // sync database
        didSyncDatabase()
    }
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        syncDatabase()
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        if let value = userInfo["needSyncDatabase"] as? Bool {
            needSyncDatabase = value
            syncDatabase()
        }
    }
}

运行中

  • iOS
    • 当数据库改变时,发送需要同步的数据。
    • 接收需要同步的数据,存入数据库。

思路

iOS -> watchOS 同步