成人免费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

鴻蒙操作系統

2015-07-03 09:49:56

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日志系統

2024-01-05 15:32:47

鴻蒙SNTP智慧時鐘

2022-01-21 21:22:24

OpenHarmon操作系統鴻蒙

2023-04-03 15:39:31

2022-02-10 15:07:10

云平臺OpenHarmon系統開發

2023-04-24 15:11:51

系統開發鴻蒙

2019-11-26 09:42:36

代碼開發API

2023-03-24 14:39:17

鴻蒙系統開發

2022-02-08 15:21:59

Hi3861開發鴻蒙

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存儲

2015-08-03 11:42:27

Swift漢堡式過度動畫
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产高清免费视频 | 看片wwwwwwwwwww | 毛片一级网站 | 久久久久久久久淑女av国产精品 | 欧美日韩不卡合集视频 | 国产在线精品一区二区三区 | 亚洲欧美高清 | 一级黄色日本片 | 美女久久久 | 午夜激情在线视频 | 久久久久久久av麻豆果冻 | 国产激情网站 | 国产情侣在线看 | 天天操,夜夜爽 | 永久av| 国产日产精品一区二区三区四区 | 欧美国产日韩一区二区三区 | 国产一区二区三区四区在线观看 | 国产精品毛片一区二区三区 | 天天搞夜夜操 | 夜久久| 日本欧美在线观看视频 | 亚洲精品视频在线观看视频 | 午夜激情视频 | 天天噜天天干 | 一区二区三区在线免费观看视频 | 日韩成人在线播放 | 欧美日韩国产一区二区三区 | 国产一区不卡 | 日韩精品久久久 | 岛国av一区二区三区 | 久久久久久天堂 | av黄色免费在线观看 | 亚洲一区二区三区视频 | 美女视频一区二区三区 | 久久久噜噜噜www成人网 | 久久这里只有精品首页 | 日韩电影一区二区三区 | 午夜精品久久久久久久久久久久久 | 狠狠操狠狠操 | 亚洲人成人一区二区在线观看 |