亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

iOS自動移除KVO觀察者的實現方法

2019-10-21 18:43:11
字體:
來源:轉載
供稿:網友

問題

KVO即:Key-Value Observing, 直譯為:基于鍵值的觀察者。 它提供一種機制,當指定的對象的屬性被修改后,則對象就會接受到通知。 簡單的說就是每次指定的被觀察的對象的屬性被修改后,KVO就會自動通知相應的觀察者了。

KVO的優點:當有屬性改變,KVO會提供自動的消息通知。 這樣開發人員不需要自己去實現這樣的方案:每次屬性改變了就發送消息通知。 這是KVO機制提供的最大的優點。 因為這個方案已經被明確定義,獲得框架級支持,可以方便地采用。 開發人員不需要添加任何代碼,不需要設計自己的觀察者模型,直接可以在工程里使用。 其次,KVO的架構非常的強大,可以很容易的支持多個觀察者觀察同一個屬性,以及相關的值。

但我們都知道, 使用KVO模式, 對某個屬性進行監聽時, Observer 需要在必要的時刻進行移除, 否則 App 必然會 Crash. 這個問題有點煩人, 因為偶爾會忘記寫移除 Observer 的代碼...

我一直想要這樣一個效果:

只管監聽, 并處理監聽方法. 不去分心, 管何時移除 Observer , 讓其能夠適時自動處理.

所幸, 它能夠實現, 先預覽一下:

@interface NSObject (SJObserverHelper)- (void)sj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;@end@interface SJObserverHelper : NSObject@property (nonatomic, unsafe_unretained) id target;@property (nonatomic, unsafe_unretained) id observer;@property (nonatomic, strong) NSString *keyPath;@property (nonatomic, weak) SJObserverHelper *factor;@end@implementation SJObserverHelper- (void)dealloc { if ( _factor ) { [_target removeObserver:_observer forKeyPath:_keyPath]; }}@end@implementation NSObject (ObserverHelper)- (void)sj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {  [self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:nil];  SJObserverHelper *helper = [SJObserverHelper new]; SJObserverHelper *sub = [SJObserverHelper new];  sub.target = helper.target = self; sub.observer = helper.observer = observer; sub.keyPath = helper.keyPath = keyPath; helper.factor = sub; sub.factor = helper;  const char *helpeKey = [NSString stringWithFormat:@"%zd", [observer hash]].UTF8String; objc_setAssociatedObject(self, helpeKey, helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(observer, helpeKey, sub, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end

項目源碼

下面來說說一步一步的實現吧:

初步思路實現:

我們都知道, 對象被釋放之前, 會調用dealloc方法, 其持有的實例變量也會被釋放.

我就這樣想, 在監聽注冊時, 為self和Observer關聯個臨時對象, 當兩者在釋放實例變量時, 我借助這個時機, 在臨時對象的dealloc方法中, 移除Observer就行了.

想法很好, 可總不能每個類里都加一個臨時對象的屬性吧. 那如何在不改變原有類的情況下, 為其關聯一個臨時對象呢?

關聯屬性

不改變原有類, 這時候肯定是要用Category了, 系統框架里面有很多的分類, 并且有很多的關聯屬性, 如下圖 UIView 頭文件第180行:

ios,kvo,移除觀察者,移除kvo,kvo不移除

依照上圖, 我們先看一個示例, 為NSObject的添加一個Category, 并添加了一個property, 在.m中實現了它的setter和getter方法.

#import <objc/message.h>@interface NSObject (Associate)@property (nonatomic, strong) id tmpObj;@end@implementation NSObject (Associate)static const char *testKey = "TestKey";- (void)setTmpObj:(id)tmpObj { // objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) objc_setAssociatedObject(self, testKey, tmpObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (id)tmpObj { // objc_getAssociatedObject(id object, const void *key) return objc_getAssociatedObject(self, testKey);}@end

很明確, objc_setAssociatedObject 便是關聯屬性的setter方法, 而objc_getAssociatedObject便是關聯屬性的getter方法. 最需要關注的就是setter方法, 因為我們要用來添加關聯屬性對象.

初步思路探索

初步嘗試:

既然屬性可以隨時使用objc_setAssociatedObject關聯了, 那我就嘗試先為self關聯一個臨時對象, 在其dealloc中, 將Observer移除.

@interface SJObserverHelper : NSObject@property (nonatomic, weak) id target;@property (nonatomic, weak) id observer;@property (nonatomic, strong) NSString *keyPath;@end@implementation SJObserverHelper- (void)dealloc { [_target removeObserver:_observer forKeyPath:_keyPath];}@end- (void)addObserver { NSString *keyPath = @"name"; [_xiaoM addObserver:_observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:nil];  SJObserverHelper *helper_obj = [SJObserverHelper new]; helper_obj.target = _xiaoM; helper_obj.observer = _observer; helper_obj.keyPath = keyPath; const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String; // 關聯 objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}

于是, 美滋滋的運行了一下程序, 當將_xiaoM 置為 nil 時, 砰 App Crash......

reason: 'An instance 0x12cd1c370 of class Person was deallocated while key value observers were still registered with it.

分析: 臨時對象的dealloc, 確確實實的跑了. 為什么會還有registered? 于是我嘗試在臨時對象的dealloc中, 打印實例變量target, 發現其為nil. 好吧, 這就是Crash問題原因!

嘗試 unsafe_unretained

通過上面操作, 我們知道self在被釋放之前, 會先釋放其持有的關聯屬性, self并未完全釋放, 可在臨時對象中target卻成了nil. 同時self還是有效的, 那如何保持不為nil呢?

我們看看OC中的兩個修飾符weak與unsafe_unretained:

  • weak: 持有者不會對目標進行retain, 當目標銷毀時, 持有者的實例變量會被置空
  • unsafe_unretained: 持有者不會對目標進行retain, 當目標釋放后, 持有者的實例變量還會依然指向之前的內存空間(野指針)

由上, unsafe_unretained很好的解決了我們的問題. 于是我做了如下修改:

@interface SJObserverHelper : NSObject@property (nonatomic, unsafe_unretained) id target;@property (nonatomic, unsafe_unretained) id observer;@property (nonatomic, strong) NSString *keyPath;@end

再次運行程序, 還行, 觀察者移除了.

最終實現

還存在的問題

目前, 我們只是實現了, 如何在self釋放的時候, 移除自己身上的Observer.

但如果Observer提前釋放了呢?

而添加關聯屬性, 兩者還不能同時持有臨時對象, 否則臨時對象也不會及時的釋放.

好吧, 既然一個不行, 那就各自關聯一個:

- (void)addObserver { .....   SJObserverHelper *helper_obj = [SJObserverHelper new]; SJObserverHelper *sub_obj = [SJObserverHelper new]; sub_obj.target = helper_obj.target = _xiaoM; sub_obj.observer = helper_obj.observer = _observer; sub_obj.keyPath = helper_obj.keyPath = keyPath; const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String; // 關聯 objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 關聯 objc_setAssociatedObject(_observer, helpeKey, sub_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}

如上, 仔細想想, 存在一個很明顯的問題, 兩個關聯屬性釋放的同時, 進行了兩次觀察移除的操作. 為避免這個問題, 我又做了如下修改:

@interface SJObserverHelper : NSObject@property (nonatomic, unsafe_unretained) id target;@property (nonatomic, unsafe_unretained) id observer;@property (nonatomic, strong) NSString *keyPath;@property (nonatomic, weak) SJObserverHelper *factor; // 1. 新增一個 weak 變量@end@implementation SJObserverHelper- (void)dealloc { if ( _factor ) {  [_target removeObserver:_observer forKeyPath:_keyPath]; }}@end- (void)addObserver { .....  SJObserverHelper *helper_obj = [SJObserverHelper new]; SJObserverHelper *sub_obj = [SJObserverHelper new]; sub_obj.target = helper_obj.target = _xiaoM; sub_obj.observer = helper_obj.observer = _observer; sub_obj.keyPath = helper_obj.keyPath = keyPath; // 2. 互相 weak 引用 helper_obj.factor = sub_obj;  sub_obj.factor = helper_obj; const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String; // 關聯 objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 關聯 objc_setAssociatedObject(_observer, helpeKey, sub_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}

在之前的操作中, 我們知道, weak 修飾的變量, 在目標釋放時,持有者的實例變量都會自動置為nil, 因此如上dealloc方法中, 我們只需要判斷weak引用的實例變量factor是否為空即可.

抽取

以上操作, 我們就可以解決偶爾忘記寫移除Observer的代碼了. 現在只需要把實現抽取出來, 做成一個通用的工具方法:

我新建了一個NSObject的Category, 并添加了一個方法, 如下:

ios,kvo,移除觀察者,移除kvo,kvo不移除

然后將上述的實現進行了整合放到了.m中:

ios,kvo,移除觀察者,移除kvo,kvo不移除

到此, 以后只需要調用- (void)sj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;這個方法即可, 移除就交給臨時變量自己搞定.

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到IOS開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人在线小视频| 久久久精品视频成人| 欧美视频免费在线观看| 国产精品久久久久久久一区探花| 2019中文在线观看| 亚洲精品美女久久久久| 大桥未久av一区二区三区| 亚洲a级在线观看| 日韩av色在线| 久久视频免费在线播放| 中文字幕亚洲无线码a| 日韩欧美国产网站| 国产精品久久久久久中文字| 欧美日韩激情视频8区| 国产亚洲在线播放| 亚洲韩国日本中文字幕| 中文字幕亚洲情99在线| 啪一啪鲁一鲁2019在线视频| 国产91色在线|| 亚洲香蕉在线观看| 国产欧美精品日韩| 欧美裸体男粗大视频在线观看| 亚洲免费精彩视频| 国产日韩精品综合网站| 国产精品一区二区女厕厕| 国产精品高潮呻吟久久av野狼| 国产精品黄页免费高清在线观看| 欧美专区国产专区| 亚洲视频视频在线| 久久综合伊人77777尤物| 成人午夜两性视频| 中日韩美女免费视频网站在线观看| 一区二区三区国产在线观看| 亚洲精品美女网站| 亚洲精品一区二区三区婷婷月| 国产精品电影在线观看| 国产精品黄色影片导航在线观看| 欧美日韩亚洲一区二区| 亚洲国产精品久久精品怡红院| 日韩成人激情在线| 欧美日韩亚洲精品一区二区三区| 伊人久久久久久久久久| 2019中文字幕免费视频| 欧美日韩午夜激情| 久久久之久亚州精品露出| 欧美在线www| www高清在线视频日韩欧美| 国产美女扒开尿口久久久| 97超视频免费观看| 欧美激情在线一区| 欧美黄色片免费观看| 亚洲精品第一页| 亚洲欧美日本另类| 日本乱人伦a精品| 国产精品69久久久久| 久久精品亚洲一区| 成人a视频在线观看| 日日狠狠久久偷偷四色综合免费| 久久青草福利网站| 久久久久久噜噜噜久久久精品| 久久九九精品99国产精品| 亚洲欧美日韩国产精品| 国产亚洲人成a一在线v站| 国产精品久久久久aaaa九色| 日韩黄色在线免费观看| 欧美视频在线免费看| 91久久久亚洲精品| 欧美国产在线视频| 国产精品一区二区av影院萌芽| 亚洲一区二区久久| 最新日韩中文字幕| 欧美性猛交xxxx久久久| 亚洲第一页自拍| 久久精品视频在线播放| 久久久在线视频| 欧美在线视频观看免费网站| 亚洲天堂2020| 国产精品美乳一区二区免费| 久久中文字幕视频| 亚洲一区第一页| 久久久久久亚洲精品不卡| 性色av一区二区三区红粉影视| 91情侣偷在线精品国产| 国产一区二区三区毛片| 亚洲欧洲一区二区三区在线观看| 欧美美女操人视频| 久久九九全国免费精品观看| 色无极亚洲影院| 亚洲午夜性刺激影院| 成人乱人伦精品视频在线观看| 隔壁老王国产在线精品| 日本高清视频精品| 欧美极品少妇xxxxⅹ免费视频| 性色av一区二区三区| 欧美性xxxxx极品娇小| 久久国产精品视频| 91精品国产自产在线| 精品av在线播放| 日本高清+成人网在线观看| 欧美成年人视频网站| 日韩中文字幕网| 中文字幕无线精品亚洲乱码一区| 欧美制服第一页| 国产一区二区三区毛片| 米奇精品一区二区三区在线观看| 亚洲国产婷婷香蕉久久久久久| 欧美中文在线观看| 久久久久久国产精品久久| 中文字幕久久久| 国产欧美一区二区三区视频| 日韩av电影中文字幕| 日韩一区二区久久久| 亚洲综合小说区| 欧美午夜影院在线视频| 亚洲成色777777在线观看影院| 中文字幕亚洲一区二区三区五十路| 欧美国产日本高清在线| 欧美性少妇18aaaa视频| 成人激情av在线| 国产精品久久久久免费a∨大胸| 中文字幕亚洲天堂| 国产剧情日韩欧美| 亚洲精品欧美极品| 大桥未久av一区二区三区| 欧美成人高清视频| 日本亚洲欧洲色α| 精品成人av一区| 97香蕉超级碰碰久久免费的优势| 国产欧美一区二区三区四区| 成人做爽爽免费视频| 国产精品电影网站| 91久久久久久久| 国产精品r级在线| 欧美电影免费观看大全| 国产不卡一区二区在线播放| 久久精品久久久久| 国产精品久久9| 国产成人精品一区二区三区| 最近2019中文字幕在线高清| 久久99热这里只有精品国产| 韩国精品美女www爽爽爽视频| 2018中文字幕一区二区三区| 91久久精品久久国产性色也91| 日韩美女av在线免费观看| 欧美在线欧美在线| 久久久久久亚洲精品中文字幕| 91久久国产综合久久91精品网站| 另类专区欧美制服同性| 欧美福利视频在线观看| 国产精品手机播放| 欧美成人中文字幕| 欧美贵妇videos办公室| 成人免费高清完整版在线观看| 欧美亚洲在线播放| 日本精品在线视频| 91chinesevideo永久地址| 亚洲一区中文字幕在线观看| 国产精品久久久久久久久久久久久| 97国产在线视频| 国产精品羞羞答答| 亚洲精品国产精品久久清纯直播| 国内精品视频久久| 日韩欧美亚洲成人| 久久在线精品视频|