国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

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

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

[[419498]]

前言

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

正文

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

單獨(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,用戶可以在第 3 個(gè)界面里修改名字,這就需要更新這 3 個(gè)界面的名字,不然用戶 pop 返回時(shí)就會(huì)覺(jué)得奇怪。

UI

假如我們的名字放在一個(gè)叫做 UserInfo 的類里(訪問(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 的代碼類似 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è)類方法 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 的修改類似 FirstViewController,不贅述。

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

但是還有一個(gè)問(wèn)題, UserInfo 里的 nameListeners 數(shù)組可能會(huì)越來(lái)越長(zhǎng),比如用戶不斷地 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,類型從 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,也很合理。為了滿足 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ì)包含其他類型的屬性,例如 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)任何類型的屬性,它內(nèi)部再實(shí)現(xiàn)監(jiān)聽(tīng)系統(tǒng)即可。當(dāng)然,我們也讓監(jiān)聽(tīng)者支持泛型(struct Listener)以便執(zhí)行 action 時(shí)可以傳遞任意類型的參數(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-04-15 11:46:09

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

2022-02-09 19:45:41

MQTTOpenHarmon鴻蒙

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-09 19:31:41

Hi3861OpenHarmon鴻蒙

2022-02-08 15:21:59

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

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)

国产精品成人a在线观看| 女性隐私黄www网站视频| 污视频网站在线免费| 久久综合久久鬼色中文字| 亚洲成人自拍| 国产欧美91| 国产伦精品一区二区三区照片| 久久综合欧美| 26uuu另类亚洲欧美日本一| 久久视频社区| 九九久久综合网站| 电影91久久久| 色综合久综合久久综合久鬼88| 婷婷久久免费视频| 日韩中文第一页| 国产精品久一| 久久久免费在线观看| 麻豆视频久久| 欧美黑人巨大xxx极品| 风间由美一区二区av101| 欧美精品精品精品精品免费| 亚洲一区二区电影| 97在线免费观看| 九九久久电影| 国产狼人综合免费视频| 99精品视频在线观看播放| 97视频在线观看免费高清完整版在线观看| 亚洲成av人片在线观看www| 欧美成人精品一区| 欧美人与动xxxxz0oz| 国产精品精品久久久| 亚洲a一区二区三区| 国产精品theporn88| 久久综合导航| 今天免费高清在线观看国语| www.色精品| 16—17女人毛片毛片| 五月激情综合网| 五月天婷婷在线视频| 亚洲黄页网在线观看| 小明成人免费视频一区| 久久久久久久久久久91| 四季av一区二区凹凸精品| 久久亚裔精品欧美| 国产精品综合久久| 视频二区在线播放| 在线视频欧美区| 中文字幕色婷婷在线视频| 欧美精品激情视频| 欧美另类女人| 男人添女荫道口女人有什么感觉| 亚洲欧洲一区二区三区| 成人综合影院| 日韩亚洲在线观看| 天天色天天射综合网| 性高潮久久久久久久久| 久久久久一区二区三区四区| 亚洲精华国产精华| 精品免费日韩av| 成人福利一区| 久久一区二区三区av| 99re视频精品| 国产一二三在线观看| 亚洲一二三在线| 欧美日韩精品一区二区视频| 日韩国产一区久久| 一区视频在线播放| 美女高潮在线观看| 国产极品精品在线观看| 日韩av一级片| 中文字幕日产av一二三区| 亚洲成色777777女色窝| 精品国产99| 青青在线视频免费观看| 亚洲国产精品久久不卡毛片| 亚洲v.com| 99国产超薄肉色丝袜交足的后果| 成人国产精品免费观看动漫 | 8x8ⅹ国产精品一区二区二区| 国产欧美一区二区在线| 蜜桃av在线免费观看| 久久久久国产精品免费| 日韩精品免费专区| 中文字幕2018| 色偷偷偷综合中文字幕;dd| 欧美视频四区| 国产精品拍拍拍| 精品在线观看国产| 91精品1区| 波多野结衣xxxx| 亚洲欧洲国产精品| 亚洲综合日本| 伊人网在线免费观看| 欧美日韩国产成人在线| 日韩国产欧美三级| 日本大臀精品| 97视频色精品| 成人午夜视频在线观看| 午夜老司机在线观看| 国产精品网址在线| 久久久亚洲精品一区二区三区| 成人超碰在线| 国产一区精品视频| 激情成人在线视频| 精品国产一区二区三区成人影院| 成人毛片100部免费看| 日韩精品一区二区三区swag| 一区在线观看| 天堂av在线免费观看| 2019亚洲日韩新视频| 91丨porny丨户外露出| 狠狠操一区二区三区| 九九99久久| 日本精品免费观看高清观看| 日韩国产在线| 青檬在线电视剧在线观看| 97在线观看视频国产| 久久九九全国免费| 亚洲成a人片777777久久| 国产精品一色哟哟| 亚洲日韩第一页| 国产乱码一区二区三区| sm久久捆绑调教精品一区| 欧美日韩一区二区三区在线观看免| 岛国av在线不卡| 日韩国产一区二区| 蜜桃av成人| 国产噜噜噜噜噜久久久久久久久| 伊人夜夜躁av伊人久久| 成人免费电影网址| 亚洲尤物在线视频| 国产69精品久久久久9999apgf| 亚洲国产毛片aaaaa无费看| av亚洲在线观看| 在线国产网址| 99国产超薄丝袜足j在线观看| 欧美午夜影院在线视频| 亚洲xxx拳头交| 大胆av不用播放器在线播放| 国产亚洲一区二区三区在线播放 | 黄色无遮挡网站| 日本一本a高清免费不卡| 亚洲五码中文字幕| 欧美激情自拍| 色婷婷av在线| 2022中文字幕| 色综合久久88色综合天天看泰| 中文字幕中文在线不卡住| 成人毛片免费看| www亚洲人| 中文字幕成人一区| 久久综合久久美利坚合众国| 国产精品欧美一区喷水| 色中色综合网| 成人影欧美片| 日韩极品视频在线观看| 久久久久久久久久久免费精品| 亚洲精品一卡二卡| 精品不卡视频| 久九九久频精品短视频| 妺妺窝人体色www在线观看| 国产精品观看在线亚洲人成网| 日本韩国欧美一区二区三区| 美女脱光内衣内裤视频久久网站| 日韩欧美专区| 黄动漫在线看| 亚洲ai欧洲av| 米奇精品一区二区三区在线观看| 一区二区三区日韩| 久久九九免费| 视频一区视频二区欧美| 一区二区三区不卡在线视频| 国产欧美日韩一区二区三区| 亚洲视频在线观看| 依依成人精品视频| 久久精品中文| 亚洲午夜国产成人| 亚洲欧洲动漫| 亚洲欧美一二三| 欧美做受高潮1| 精品久久久久久久久久久久包黑料| 久久免费美女视频| 欧美精品一卡| 91嫩草国产线观看亚洲一区二区| 你懂的视频在线| 欧美日韩亚洲一| 国产精品一区二区欧美| 久久久国产精彩视频美女艺术照福利| 亚洲影院在线观看| 国产一区二区视频在线播放| 欧美手机在线| 先锋欧美三级| 成人精品福利| 91av俱乐部| 亚洲高清乱码| 国产精品美女在线| 国产一区二区三区日韩欧美| 欧美午夜电影在线| 久久青草欧美一区二区三区| 久久精品在线|