自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用 Swift 實現(xiàn)輕量的屬性監(jiān)聽系統(tǒng)

開發(fā) 前端
本文的主要目的是解決客戶端開發(fā)中對“模型的一處修改,UI 要多處更新”的問題。當(dāng)然,我們要知曉解決方案的細(xì)節(jié)和思考過程,以及看到其能達(dá)到的效果。我們會用到函數(shù)式編程的思想,以及偉大的“泛型”。

[[419498]]

前言

本文的主要目的是解決客戶端開發(fā)中對“模型的一處修改,UI 要多處更新”的問題。當(dāng)然,我們要知曉解決方案的細(xì)節(jié)和思考過程,以及看到其能達(dá)到的效果。我們會用到函數(shù)式編程的思想,以及偉大的“泛型”。請相信我,我們并非為了使用新技術(shù)而使用新技術(shù)。如果一個問題有更好的方法去解決,那為何不替換掉舊方法呢?

正文

假如你正在寫的 App 是有用戶系統(tǒng)的,也就是用戶需要管理自己的信息,如修改名字、頭發(fā)顏色之類的。

單獨(dú)拿名字來說,除開在修改界面,可能在系統(tǒng)的其他界面也會使用到它,這就涉及到在更新名字后再更新其他界面的問題。

你的第一直覺是什么呢?多半是使用通知,也就是 NSNotification。這是一種很好的辦法,雖然邏輯松散,寫起來有些麻煩。比如要定義一個通知名,發(fā)送通知,各界面都監(jiān)聽通知再處理,等等。

例如,對于如下 3 個界面,都有顯示名字。通過 push,用戶可以在第 3 個界面里修改名字,這就需要更新這 3 個界面的名字,不然用戶 pop 返回時就會覺得奇怪。

UI

假如我們的名字放在一個叫做 UserInfo 的類里(訪問和修改都使用單例),如下:

  1. class UserInfo { 
  2.  
  3.     static let sharedInstance = UserInfo() 
  4.  
  5.     struct Notification { 
  6.         static let NameChanged = "UserInfo.Notification.NameChanged" 
  7.     } 
  8.  
  9.     var name: String = "NIX" { 
  10.         didSet { 
  11.             NSNotificationCenter.defaultCenter().postNotificationName(Notification.NameChanged, object: name
  12.         } 
  13.     } 

同時我們定義了一個通知。在 name 被改變后就發(fā)出這個通知,并把 name 傳出去。

三個界面分別為 FirstViewController、SecondViewController、ThirdViewController,都有一個 button 在正中間。其中前兩個負(fù)責(zé) push,最后一個點擊后可以改名字。因此,對于 FirstViewController 來說:

  1. class FirstViewController: UIViewController { 
  2.  
  3.     @IBOutlet weak var nameButton: UIButton! 
  4.  
  5.     override func viewDidLoad() { 
  6.         super.viewDidLoad() 
  7.  
  8.         title = "First" 
  9.  
  10.         nameButton.setTitle(UserInfo.sharedInstance.name, forState: .Normal) 
  11.  
  12.         NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI:"name: UserInfo.Notification.NameChanged, object: nil) 
  13.     } 
  14.  
  15.     func updateUI(notification: NSNotification) { 
  16.         if let name = notification.object as? String { 
  17.             nameButton.setTitle(name, forState: .Normal) 
  18.         } 
  19.     } 

除了加載時設(shè)置 button 之外,我們還要監(jiān)聽通知,并在 name 被改變時更新 button 的 title。

SecondViewController 的代碼類似 FirstViewController,不贅述。

對于 ThirdViewController,除了設(shè)置和通知外,還有一個 button 的 target-action 方法用于修改名字,也很簡單:

  1. @IBAction func changeName(sender: UIButton) { 
  2.  
  3.     let alertController = UIAlertController(title: "Change name", message: nil, preferredStyle: .Alert) 
  4.  
  5.     alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in 
  6.         textField.placeholder = self.nameButton.titleLabel?.text 
  7.     } 
  8.  
  9.     let action: UIAlertAction = UIAlertAction(title: "OK", style: .Default) { action -> Void in 
  10.         if let textField = alertController.textFields?.first as? UITextField { 
  11.             UserInfo.sharedInstance.name = textField.text // 更新名字 
  12.         } 
  13.     } 
  14.     alertController.addAction(action
  15.  
  16.     self.presentViewController(alertController, animated: true, completion: nil) 

似乎并不麻煩,看起來也算合理,那上面這樣寫有什么問題?我想答案是太重復(fù)。為了減少重復(fù),我們來增加自己的知識,讓腦神經(jīng)稍微痛苦一點,好形成一些新的聯(lián)結(jié)或破壞一些舊的聯(lián)結(jié)。

我們可以傳遞閉包給 UserInfo,它將閉包存儲起來,并在 name 被改變時調(diào)用這些閉包,這樣閉包里的操作就會被執(zhí)行了。自然,我們要在閉包里更新 UI。

這樣,新的 UserInfo 如下:

  1. class UserInfo { 
  2.  
  3.     static let sharedInstance = UserInfo() 
  4.  
  5.     typealias NameListener = String -> Void 
  6.  
  7.     var nameListeners = [NameListener]() 
  8.  
  9.     class func bindNameListener(nameListener: NameListener) { 
  10.         self.sharedInstance.nameListeners.append(nameListener) 
  11.     } 
  12.  
  13.     class func bindAndFireNameListener(nameListener: NameListener) { 
  14.         bindNameListener(nameListener) 
  15.  
  16.         nameListener(self.sharedInstance.name
  17.     } 
  18.  
  19.     var name: String = "NIX" { 
  20.         didSet { 
  21.             nameListeners.map { $0(self.name) } 
  22.         } 
  23.     } 

我們刪除了通知相關(guān)的代碼,定義了 NameListener,增加了一個 nameListeners 用于保存監(jiān)聽者閉包,并實現(xiàn)兩個類方法 bindNameListener 和 bindAndFireNameListener 來保存(并觸發(fā))監(jiān)聽者閉包。而在 name 的 didSet 里,我們只需要調(diào)用每個閉包即可,這里用了 map,也很直觀。

那么 FirstViewController 的代碼就簡化為:

  1. class FirstViewController: UIViewController { 
  2.  
  3.     @IBOutlet weak var nameButton: UIButton! 
  4.  
  5.     override func viewDidLoad() { 
  6.         super.viewDidLoad() 
  7.  
  8.         title = "First" 
  9.  
  10.         UserInfo.bindAndFireNameListener { name in 
  11.             self.nameButton.setTitle(name, forState: .Normal) 
  12.         } 
  13.     } 

我們刪除了通知相關(guān)的代碼和 updateUI 方法,只需要將我們更新 UI 的閉包綁定到 UserInfo 即可。因為我們也需要初始設(shè)置 button,所以用了 bindAndFireNameListener。

SecondViewController 和 ThirdViewController 的修改類似 FirstViewController,不贅述。

這樣一來,設(shè)置 UI 的操作和更新 UI 的操作就被很好地“融合”到一起了。代碼比第一版的的邏輯性更強(qiáng),VC 也更簡單。

但是還有一個問題, UserInfo 里的 nameListeners 數(shù)組可能會越來越長,比如用戶不斷地 push/pop。雖然在有限的時間里,nameListeners 的數(shù)量不會變的非常大,程序的性能可以接受,但這畢竟是一種浪費(fèi)(內(nèi)存和 CPU 時間)。我們再來解決這個問題。

問題關(guān)鍵是我們的閉包并沒有名字,我們無法將其找出并刪除。例如對于 SecondViewController 來說,第一次進(jìn)入它時,bindAndFireNameListener 執(zhí)行了一次,如果 pop 再 push,它又執(zhí)行了一次。那么,第一次被綁定的閉包其實沒有任何用處了,因為第二次看到的 VC 是新生成的。如果我們能為閉包取名字,我們就能在第二次進(jìn)入時用新的閉包替換舊的閉包,從而保證 nameListeners 的數(shù)量不會無限制的增長,也就不會浪費(fèi)內(nèi)存和 CPU 了。

為了限制 nameListeners 的無限制增長,我們可以將 nameListeners 改成 nameListenerSet,類型從 Array 改成 Set,這樣綁定時就能保證其中“同一個地方添加的閉包”最多只有一個。但很不幸,我們無法將閉包 NameListener 放入 Set,因為閉包無法實現(xiàn) Hashable 協(xié)議,而這正是使用 Set 所需要的。

似乎陷入困境了!

不要恐慌。雖然一個單純的閉包無法實現(xiàn) Hashable,但我們可以將其再封裝一次,例如放入一個 struct 里,我們再讓 struct 實現(xiàn) Hashable 協(xié)議。前面剛提到過,閉包無法實現(xiàn) Hashable,那么我們必然要在 struct 放入另外一個可以 Hashable 的屬性來幫助我們的 struct 實現(xiàn) Hashable。也就是:為閉包取一個名字。因此,我們新的 UserInfo 如下:

  1. func ==(lhs: UserInfo.NameListener, rhs: UserInfo.NameListener) -> Bool { 
  2.     return lhs.name == rhs.name 
  3.  
  4. class UserInfo { 
  5.  
  6.     static let sharedInstance = UserInfo() 
  7.  
  8.     struct NameListener: Hashable { 
  9.         let name: String 
  10.  
  11.         typealias Action = String -> Void 
  12.         let actionAction 
  13.  
  14.         var hashValue: Int { 
  15.             return name.hashValue 
  16.         } 
  17.     } 
  18.  
  19.     var nameListenerSet = Set<NameListener>() 
  20.  
  21.     class func bindNameListener(name: String, action: NameListener.Action) { 
  22.         let nameListener = NameListener(namenameactionaction
  23.  
  24.         self.sharedInstance.nameListenerSet.insert(nameListener) // TODO:需要處理同名替換 
  25.     } 
  26.  
  27.     class func bindAndFireNameListener(name: String, action: NameListener.Action) { 
  28.         bindNameListener(nameactionaction
  29.  
  30.         action(self.sharedInstance.name
  31.     } 
  32.  
  33.     var name: String = "NIX" { 
  34.         didSet { 
  35.             for nameListener in nameListenerSet { 
  36.                 nameListener.action(name
  37.             } 
  38.         } 
  39.     } 

我們設(shè)計了一個新的 struct:NameListener,它有一個 name 表明它是誰,原來的閉包就變成了 action,也很合理。為了滿足 Hashable 協(xié)議,我們用 name.hashValue 來作為 struct 的 hashValue。另外,因為 Hashable 繼承于 Equatable,我們也要實現(xiàn)一個 func ==。

另外,為了 API 更好使用,我們將 bindNameListener 與 bindAndFireNameListener 改造為接受一個 name 和一個 action 作為參數(shù),在方法內(nèi)部才“合成”一個 nameListener,這樣 API 在使用時看起來會更合理,如下:

  1. UserInfo.bindAndFireNameListener("FirstViewController.nameButton") { name in 
  2.     self.nameButton.setTitle(name, forState: .Normal) 

我們只在閉包前面增加了一個閉包的“名字”而已。

最后,UserInfo 的 name 的 didSet 里要稍微修改,因為是 Set,沒法 map 了,那就改成最傳統(tǒng)的循環(huán)吧。

小結(jié)

我們面臨一個“一處修改,多處更新”的問題,起初時我們用通知來實現(xiàn),并無不可。之后我們想要更合理(或者更酷)一些,于是利用 Swift 的閉包特性實現(xiàn)了一個監(jiān)聽者模式。最后,我們使用包裝的辦法,解決了監(jiān)聽者可能會無限制增長的問題。

而這一切的目的,都是為了讓代碼更有邏輯性,并減少 VC 的代碼量。

最后的最后,UserInfo 里可能會包含其他類型的屬性,例如 var hairColor: UIColor,如果它也面臨“一處修改,多處更新”的問題,那么我們也需要實現(xiàn)一個 HairColorListener 嗎?

也許我們該利用 Swift 的泛型編寫一個更加合理的 Listener,你說對吧?

非最終的效果請查看并運(yùn)行 Demo 代碼:[1]。如果你愿意的話,可以查看 git 的各個 commit 以得到整個過程。

(最終的)更好的泛型實現(xiàn)在分支 generic[2] 里,它的關(guān)鍵就是利用泛型實現(xiàn)一個 class Listenable 以對應(yīng)任何類型的屬性,它內(nèi)部再實現(xiàn)監(jiān)聽系統(tǒng)即可。當(dāng)然,我們也讓監(jiān)聽者支持泛型(struct Listener)以便執(zhí)行 action 時可以傳遞任意類型的參數(shù)。還有少許細(xì)節(jié)不同,例如 UserInfo 里直接使用 static 變量更方便,不需要用一個單獨(dú)的單例再訪問其屬性。

參考資料

[1]運(yùn)行 Demo 代碼: https://github.com/nixzhu/PropertyListenerDemo

[2]generic: https://github.com/nixzhu/PropertyListenerDemo/tree/generic

本文轉(zhuǎn)載自微信公眾號「Swift社區(qū)」

責(zé)任編輯:姜華 來源: Swift社區(qū)
相關(guān)推薦

2022-02-10 19:15:18

React監(jiān)聽系統(tǒng)模式

2022-04-15 14:31:02

鴻蒙操作系統(tǒng)

2024-01-05 15:32:47

鴻蒙SNTP智慧時鐘

2024-03-14 11:06:37

JavaScript引擎探索

2022-02-09 19:45:41

MQTTOpenHarmon鴻蒙

2022-04-15 11:46:09

輕量系統(tǒng)解耦鴻蒙操作系統(tǒng)

2021-09-13 08:20:13

Loki日志系統(tǒng)

2015-07-03 09:49:56

2022-02-10 15:07:10

云平臺OpenHarmon系統(tǒng)開發(fā)

2023-04-03 15:39:31

2022-01-21 21:22:24

OpenHarmon操作系統(tǒng)鴻蒙

2023-04-24 15:11:51

系統(tǒng)開發(fā)鴻蒙

2019-11-26 09:42:36

代碼開發(fā)API

2022-02-08 15:21:59

Hi3861開發(fā)鴻蒙

2023-03-24 14:39:17

鴻蒙系統(tǒng)開發(fā)

2022-02-09 19:31:41

Hi3861OpenHarmon鴻蒙

2024-01-08 08:23:08

OpenCV機(jī)器學(xué)習(xí)計算機(jī)視覺

2022-01-24 18:43:20

OpenHarmon操作系統(tǒng)鴻蒙

2015-08-03 11:42:27

Swift漢堡式過度動畫

2023-10-31 18:32:26

WebRTC存儲
點贊
收藏

51CTO技術(shù)棧公眾號