1. 程式人生 > IOS開發 >iOS 中跨頁面狀態同步方案比較

iOS 中跨頁面狀態同步方案比較

由於團隊希望專案能夠去 CoreData 化,而以往狀態同步都是依賴於 CoreData 的 NSFetchedResultsController。因此去 CoreData 則必須尋找一種替代方案來進行狀態同步。

NotificationCenter

狀態同步實際是一對多的場景,也就是一個事件可以被多個觀察者監聽到。而蘋果的系統框架自帶的 NotificationCenter 正是用來適配這種場景,並且其也是被系統框架本身及我們開發者大面積使用的。用法如下:

  1. 定義通知名字,以及需要額外傳遞資訊的 key
  2. 基於 target-action 的方式註冊通知
open func addObserver
(_ observer: Any,selector aSelector: Selector,name aName: NSNotification.Name?,object anObject: Any?)
複製程式碼
  1. 實現監聽通知的方法
func onReceivedNotification(note: NSNotification)
複製程式碼
  1. 傳送通知,可以傳遞傳送通知的物件(object)以及一些額外的資訊(userInfo)
open func post(name aName: NSNotification.Name,object anObject: Any?,userInfo aUserInfo: [AnyHashable : Any]? = nil
)
複製程式碼
  1. 移除註冊的通知
open func removeObserver(_ observer: Any,object anObject: Any?)
複製程式碼

當然 NotificationCenter 也提供了一種更加便利基於 block 的方式註冊監聽通知,其將 2,3 兩個步驟整合為 1 個步驟。

open func addObserver(forName name: NSNotification.Name?,object obj: Any?,queue: OperationQueue?,using block: @escaping (Notification) -> Void
) -> NSObjectProtocol 複製程式碼

整體流程很清晰,簡單易用,但是卻有一個嚴重的缺點 —— 弱型別。我們接收到的是一個 NSNotification 物件。

open class NSNotification : NSObject,NSCopying,NSCoding {
    open var name: NSNotification.Name { get }
    open var object: Any? { get }
    open var userInfo: [AnyHashable : Any]? { get }
}
複製程式碼

假設我們需要傳遞一個關注狀態改變的資訊,那麼需要包含關注更改後的狀態以及被關注者的 ID。那麼我們需要從 userInfo 中取出所需要的值:

let following = notification.userInfo?["FollowingKey"] as! NSNumber
let userID = notification.userInfo?["UserIDKey"] as! NSNumber;
複製程式碼

也就是說接收通知的一方一般需要要檢視文件才知道怎樣從 userInfo 取值,取的值的型別又是什麼。這對於使用是極為不方便的。

SwiftNotificationCenter

SwiftNotificationCenter 是一種面向協議的通知中心方案。使用方式如下:

  1. 定義協議
protocol FollowingChanged {
    func followingDidChange(following: Bool,userID: NSNumber)
}
複製程式碼
  1. 基於協議註冊通知
Broadcaster.register(FollowingChanged.self,observer: observer)
複製程式碼
  1. 實現協議方法
extension ViewController: FollowingChanged {
  func followingDidChange(following: Bool,userID: NSNumber) {
    // do something
  }
}
複製程式碼
  1. 傳送通知
Broadcaster.notify(FollowingChanged.self) {
    $0.followingDidChange(following,userID)
}
複製程式碼
  1. 移除註冊的通知
Broadcaster.unregister(FollowingChanged.self,observer: observer)
複製程式碼

我們可以看到,其基於協議的方式解決了弱型別的問題,但其也存在著擴充套件性較差的問題。

依然是關注改變的場景,假如隨著業務的發展,有的地方需要知道關注後是否為互關的狀態,那麼又需要增加一個欄位來標識。因此我們需要修改協議,增加引數 followingEachOther,且由於它不是必須傳遞的引數,因此是 optional 型別。

protocol FollowingChanged {
    func followingDidChange(following: Bool,userID: NSNumber,followingEachOther: NSNumber?)
}
複製程式碼

如果在該型別通知被廣泛應用的場景,那麼需要修改的地方就尤其多了。這顯然也是難以接受的。

EventBus

EventBus 在安卓中被廣泛地應用,其流程如下圖所示:

EventBus-Publish-Subscribe
圖片來源:EventBus

使用方式如下:

  1. 定義事件
 class TPFollowingChangedEvent: NSObject,TPEvent {
    	private(set) var following: Bool
   		private(set) var userID: NSNumber
}
複製程式碼
  1. 註冊事件
TPEventBus<TPFollowingChangedEvent>.shared.register(eventType: TPFollowingChangedEvent.self,subscriber: self,selector: #selector(onEvent(event:object:)))
複製程式碼
  1. 實現監聽事件的方法
@objc func onEvent(event: TPFollowingChangedEvent,object: Any?) {
    	// do something
}
複製程式碼
  1. 傳送事件
TPEventBus.shared.post(event: event,object: self)
複製程式碼
  1. 移除事件的註冊
TPEventBus<TPFollowingChangedEvent>.shared.unregister(eventType: TPFollowingChangedEvent.self,subscriber: self)
複製程式碼

我們可以看到, EventBus 也是強型別的。

假如依然是關注的場景,需要知道互關的狀態,那麼我們只需要在 TPFollowingChangedEvent 中增加 followingEachOther 屬性即可。如下所示:

class TPFollowingChangedEvent: NSObject,TPEvent {
    	private(set) var following: Bool
   		private(set) var userID: NSNumber
    	private(set) var followingEachOther: NSNumber?
}
複製程式碼

因此使用 EventBus 實現了以下需求:

  • 強型別
  • 可擴充套件

EventBus 同 NotificationCenter 都是基於 target-action 的方案,但是我們不難將其擴充套件為支援 block 監聽的方式,並且同樣讓它也能夠自動移除事件的註冊。類似於如下的使用方式:

TPEventBus<TPFollowingChangedEvent>.shared.subscribe(eventType: TPFollowingChangedEvent.self).forObject(self).onQueue(OperationQueue.main).onEvent { (event,object) in
     // do something
}.disposed(by: self.tp_eventTokenBag)
複製程式碼

這裡我實現了一個小巧但比較全面的 EventBus 供參考:TPEventBus

最後

我們可以看到,一對多場景中觀察者模式的應用流程都大同小異,但是如何更好用確是值得深思的。當然以上也只是我在一些使用場景上的思考,肯定會欠缺考慮,歡迎拍磚?。