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

首頁 > 系統 > iOS > 正文

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

2020-07-26 02:35:47
字體:
來源:轉載
供稿:網友

問題

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行:


依照上圖, 我們先看一個示例, 為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, 并添加了一個方法, 如下:

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

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

總結

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

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91精品国产高清自在线看超| 奇米影视亚洲狠狠色| 国产欧美日韩中文| 国产精品久久久久久久久久久久久| 精品在线观看国产| 久久91精品国产91久久久| 亚洲欧洲午夜一线一品| 国产精品视频白浆免费视频| 日日摸夜夜添一区| 91av网站在线播放| 欧美丰满少妇xxxx| 亚洲区bt下载| 亚洲欧美综合精品久久成人| 午夜精品一区二区三区在线视频| 久久久久久久电影一区| 亚洲成人999| 98精品国产高清在线xxxx天堂| 欧美性极品xxxx做受| 精品久久久久久国产91| 久久人人爽人人爽人人片av高请| 欧美高清在线观看| 日韩美女在线看| 亚洲成人av片在线观看| 欧美日韩中文字幕在线视频| 韩国三级电影久久久久久| 在线亚洲男人天堂| www.久久色.com| 久久九九全国免费精品观看| 精品亚洲一区二区三区在线播放| 亚洲欧美日韩图片| 成人久久18免费网站图片| 欧美裸体男粗大视频在线观看| 91av视频在线免费观看| 国产精品第100页| 久久成人人人人精品欧| 91在线视频免费| 久久综合伊人77777蜜臀| 亚洲福利在线播放| 久久九九热免费视频| 欧美视频一区二区三区…| 日韩www在线| 欧美成人午夜视频| 欧美性猛交xxxx久久久| 久久久综合免费视频| 日本午夜精品理论片a级appf发布| 国产精品中文字幕在线观看| 欧美精品videossex88| 97精品国产97久久久久久春色| 亚洲网在线观看| 欧美男插女视频| 亚洲午夜精品久久久久久久久久久久| 久久国产色av| 青青草成人在线| 九九久久久久99精品| 久久久久久久爱| 日本国产精品视频| 国产精品第二页| 国产成人精彩在线视频九色| 欧美做受高潮电影o| 在线视频欧美日韩精品| 亚洲国产91色在线| 日韩激情av在线播放| 欧美情侣性视频| 97国产真实伦对白精彩视频8| 日韩成人在线视频网站| 国外成人性视频| 久久电影一区二区| 琪琪亚洲精品午夜在线| 乱亲女秽乱长久久久| 日韩av手机在线| 日韩av在线免费| 欧美成人午夜免费视在线看片| 成人激情av在线| 久久精品成人欧美大片古装| 国产精品老牛影院在线观看| 国产精品高潮呻吟视频| 福利视频一区二区| 91精品久久久久久久久| 欧美一级电影在线| 久久夜精品va视频免费观看| 国产精品久久激情| 久久精品国产一区二区电影| 亚洲人a成www在线影院| 亚洲乱码一区av黑人高潮| 欧美激情videoshd| 96精品视频在线| 日韩有码在线观看| 岛国精品视频在线播放| 清纯唯美日韩制服另类| 日韩成人激情在线| 日韩电影在线观看中文字幕| 97精品欧美一区二区三区| 亚洲伊人第一页| 亚洲一区二区三区毛片| 91日韩在线视频| 成人欧美一区二区三区在线| 亚洲一区二区三区成人在线视频精品| 亚洲乱码av中文一区二区| 91精品中国老女人| 亚洲精品成人av| 日韩欧美亚洲一二三区| 亚洲男女自偷自拍图片另类| 国产精品九九九| 国产日韩精品一区二区| 久久精品国产亚洲精品| 亚洲人成绝费网站色www| 97在线看免费观看视频在线观看| 在线观看日韩专区| 欧美日韩免费一区| 日韩最新在线视频| 国产999在线观看| 国产亚洲一区二区在线| 欧美国产视频一区二区| 国产精品视频一区二区高潮| 成人激情电影一区二区| 欧美日韩亚洲高清| 亚洲乱码国产乱码精品精| 97免费视频在线| 在线国产精品视频| 国产激情久久久| 欧美日韩国产中文精品字幕自在自线| 97精品伊人久久久大香线蕉| 九九热精品视频国产| 色爱精品视频一区| 亚洲综合视频1区| 欧美黄色片在线观看| 亚洲一区中文字幕在线观看| 亚洲另类激情图| 久久99精品久久久久久琪琪| 国产日韩综合一区二区性色av| 成人免费xxxxx在线观看| 亚洲性av网站| 亚洲精品永久免费精品| 色www亚洲国产张柏芝| 亚洲香蕉成视频在线观看| 亚洲福利视频二区| 久久久久久国产精品三级玉女聊斋| 欧美在线视频在线播放完整版免费观看| 日韩一区二区精品视频| 国产精品观看在线亚洲人成网| 九九精品在线视频| 欧美激情免费视频| 国产日韩精品视频| 麻豆国产精品va在线观看不卡| 国产精品午夜视频| 最近中文字幕2019免费| 欧美性在线视频| 国产91ⅴ在线精品免费观看| 精品国内自产拍在线观看| 欧美在线视频一区二区| 久久综合九色九九| 5252色成人免费视频| 欧美亚州一区二区三区| 成人a级免费视频| 免费91在线视频| 青青久久av北条麻妃海外网| **欧美日韩vr在线| 久久久噜噜噜久久中文字免| 日韩精品在线免费观看视频| 欧美一性一乱一交一视频| 国产精品成人免费电影| 69久久夜色精品国产69| 欧美日韩亚洲高清| 精品视频在线观看日韩|