成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用 Swift 實現輕量的屬性監聽系統

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

[[419498]]

前言

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

正文

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

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

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

例如,對于如下 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 被改變后就發出這個通知,并把 name 傳出去。

三個界面分別為 FirstViewController、SecondViewController、ThirdViewController,都有一個 button 在正中間。其中前兩個負責 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.     } 

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

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

對于 ThirdViewController,除了設置和通知外,還有一個 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) 

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

我們可以傳遞閉包給 UserInfo,它將閉包存儲起來,并在 name 被改變時調用這些閉包,這樣閉包里的操作就會被執行了。自然,我們要在閉包里更新 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.     } 

我們刪除了通知相關的代碼,定義了 NameListener,增加了一個 nameListeners 用于保存監聽者閉包,并實現兩個類方法 bindNameListener 和 bindAndFireNameListener 來保存(并觸發)監聽者閉包。而在 name 的 didSet 里,我們只需要調用每個閉包即可,這里用了 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.     } 

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

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

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

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

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

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

似乎陷入困境了!

不要恐慌。雖然一個單純的閉包無法實現 Hashable,但我們可以將其再封裝一次,例如放入一個 struct 里,我們再讓 struct 實現 Hashable 協議。前面剛提到過,閉包無法實現 Hashable,那么我們必然要在 struct 放入另外一個可以 Hashable 的屬性來幫助我們的 struct 實現 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.     } 

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

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

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

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

最后,UserInfo 的 name 的 didSet 里要稍微修改,因為是 Set,沒法 map 了,那就改成最傳統的循環吧。

小結

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

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

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

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

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

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

參考資料

[1]運行 Demo 代碼: https://github.com/nixzhu/PropertyListenerDemo

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

本文轉載自微信公眾號「Swift社區」

責任編輯:姜華 來源: Swift社區
相關推薦

2022-02-10 19:15:18

React監聽系統模式

2022-04-15 14:31:02

鴻蒙操作系統

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

輕量系統解耦鴻蒙操作系統

2021-09-13 08:20:13

Loki日志系統

2015-07-03 09:49:56

2022-02-10 15:07:10

云平臺OpenHarmon系統開發

2023-04-03 15:39:31

2022-01-21 21:22:24

OpenHarmon操作系統鴻蒙

2023-04-24 15:11:51

系統開發鴻蒙

2019-11-26 09:42:36

代碼開發API

2022-02-08 15:21:59

Hi3861開發鴻蒙

2023-03-24 14:39:17

鴻蒙系統開發

2022-02-09 19:31:41

Hi3861OpenHarmon鴻蒙

2024-01-08 08:23:08

OpenCV機器學習計算機視覺

2022-01-24 18:43:20

OpenHarmon操作系統鴻蒙

2023-10-31 18:32:26

WebRTC存儲

2022-01-24 18:35:56

OpenHarmon鴻蒙操作系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 青青草一区二区三区 | av一区二区三区四区 | 91免费观看 | 国产精品v| av中文在线 | 精品www | 国产精品美女www | 欧美狠狠操 | 日日操夜夜操天天操 | 91免费版在线观看 | 一区二区三区视频在线免费观看 | 一区二区免费看 | 99re在线视频 | 亚洲三级国产 | 亚洲 欧美 日韩在线 | 综合久久av| 午夜精品一区 | 国产福利资源在线 | 日韩精品在线免费观看视频 | 国产在线一区二区三区 | 欧美性久久 | 成年男女免费视频网站 | 日本免费视频 | 亚洲国产一区在线 | 亚洲精品视频一区二区三区 | 亚洲精品成人网 | 91大神在线资源观看无广告 | 黄色国产 | 午夜欧美一区二区三区在线播放 | 欧美理论在线观看 | 午夜在线视频 | 国产精品高潮呻吟久久av黑人 | 免费高清av | 欧美黄色一级毛片 | 欧美成人a∨高清免费观看 欧美日韩中 | 亚洲黄色网址视频 | 欧美最猛黑人xxxⅹ 粉嫩一区二区三区四区公司1 | 久久国产精品免费一区二区三区 | 日日噜噜噜夜夜爽爽狠狠视频97 | 亚洲影视在线 | 色视频www在线播放国产人成 |