偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

[[419498]]

前言

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

正文

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

單獨(dú)拿名字來(lái)說(shuō),除開(kāi)在修改界面,可能在系統(tǒng)的其他界面也會(huì)使用到它,這就涉及到在更新名字后再更新其他界面的問(wèn)題。

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

例如,對(duì)于如下 3 個(gè)界面,都有顯示名字。通過(guò) push,用戶(hù)可以在第 3 個(gè)界面里修改名字,這就需要更新這 3 個(gè)界面的名字,不然用戶(hù) pop 返回時(shí)就會(huì)覺(jué)得奇怪。

UI

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

  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.     } 

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

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

  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í)設(shè)置 button 之外,我們還要監(jiān)聽(tīng)通知,并在 name 被改變時(shí)更新 button 的 title。

SecondViewController 的代碼類(lèi)似 FirstViewController,不贅述。

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

  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) 

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

我們可以傳遞閉包給 UserInfo,它將閉包存儲(chǔ)起來(lái),并在 name 被改變時(shí)調(diào)用這些閉包,這樣閉包里的操作就會(huì)被執(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,增加了一個(gè) nameListeners 用于保存監(jiān)聽(tīng)者閉包,并實(shí)現(xiàn)兩個(gè)類(lèi)方法 bindNameListener 和 bindAndFireNameListener 來(lái)保存(并觸發(fā))監(jiān)聽(tīng)者閉包。而在 name 的 didSet 里,我們只需要調(diào)用每個(gè)閉包即可,這里用了 map,也很直觀。

那么 FirstViewController 的代碼就簡(jiǎn)化為:

  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 即可。因?yàn)槲覀円残枰跏荚O(shè)置 button,所以用了 bindAndFireNameListener。

SecondViewController 和 ThirdViewController 的修改類(lèi)似 FirstViewController,不贅述。

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

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

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

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

似乎陷入困境了!

不要恐慌。雖然一個(gè)單純的閉包無(wú)法實(shí)現(xiàn) Hashable,但我們可以將其再封裝一次,例如放入一個(gè) struct 里,我們?cè)僮?struct 實(shí)現(xiàn) Hashable 協(xié)議。前面剛提到過(guò),閉包無(wú)法實(shí)現(xiàn) Hashable,那么我們必然要在 struct 放入另外一個(gè)可以 Hashable 的屬性來(lái)幫助我們的 struct 實(shí)現(xiàn) Hashable。也就是:為閉包取一個(gè)名字。因此,我們新的 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.     } 

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

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

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

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

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

小結(jié)

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

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

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

也許我們?cè)摾?Swift 的泛型編寫(xiě)一個(gè)更加合理的 Listener,你說(shuō)對(duì)吧?

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

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

參考資料

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

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

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

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

2022-02-10 19:15:18

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

2022-04-15 14:31:02

鴻蒙操作系統(tǒng)

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

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

2021-09-13 08:20:13

Loki日志系統(tǒng)

2024-01-05 15:32:47

鴻蒙SNTP智慧時(shí)鐘

2022-01-21 21:22:24

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

2023-04-03 15:39:31

2022-02-10 15:07:10

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

2023-04-24 15:11:51

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

2019-11-26 09:42:36

代碼開(kāi)發(fā)API

2023-03-24 14:39:17

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

2022-02-08 15:21:59

Hi3861開(kāi)發(fā)鴻蒙

2022-02-09 19:31:41

Hi3861OpenHarmon鴻蒙

2024-01-08 08:23:08

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

2022-01-24 18:43:20

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

2023-10-31 18:32:26

WebRTC存儲(chǔ)

2015-08-03 11:42:27

Swift漢堡式過(guò)度動(dòng)畫(huà)
點(diǎn)贊
收藏

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