IOS —— KVO的一个小封装
不偷懶,不偷懶。今天帶來一個KVO封裝,以及封裝過程中撿起來的知識
那么首先,KVO是什么呢?
Key - Value - Observer 的縮寫,意為鍵值對的觀察。
實際上的作用就是用來觀察鍵值對的變化,以及觀察到變化后應該執行些什么操作
怎么用?蘋果早就幫我們封裝好了
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));如果有需要觀察某鍵值的變化時,我們需要addObserver添加一個觀察者,并且在不需要的時候removerObserver去除他。這是一個需要分倆步的操作
今天封裝的這個方法便是實現一個簡單的需求:
為一個類添加一個觀察者,當監控到他屬性的值發生變化時,執行一個block后銷毀觀察者
創建分類、聲明block、聲明方法
typedef void(^xgKvoBlock)(void);@interface NSObject (XGKVO)- (void)xgObserver:(NSObject *)observer keyPath:(NSString *)keyPath block:(xgKvoBlock)block;@end這個是.h文件。
在鋪開.m文件的代碼前。我們先講一講觀察者observer以及觀察者對象keyPath這倆個家伙
一個observer可以對應多個keyPath。
一個keyPath也可以對應多個observer。
這倆句話聽起起來念起來都很奇怪。
照常舉個例就明白了:
現在有一個Person類,類里有name、sex、height等屬性。
1.我們可以在ViewController添加觀察者observer監聽name屬性,也可以利用觀察者observer同時的監聽sex屬性
2.我們可以在ViewController里添加觀察者observer監聽他的name屬性,也可以在ViewController2里添加觀察者observer他的name屬性
這倆句話也應對了上面加粗的倆句
他們是一一對應的,這意味著如果要完成剛才提到的需求,我們需要完整的獲取所有的KeyPath以及Observer并一起remove(釋放)他們
那么這時候需求明確了 ,Do it!
首先我們需要一個存儲block的字典對象,以及一個存儲KVO 中observer以及keyPath的字典對象。并且在內部約束存儲對象類型
@property (nonatomic , strong) NSMutableDictionary <NSString *,xgKvoBlock> *dict; @property (nonatomic , strong) NSMutableDictionary <NSString *,NSMutableArray *>*kvoDict;然后實現類方法
- (void)xgObserver:(NSObject *)observer keyPath:(NSString *)keyPath block:(xgKvoBlock)block;在引用observer.dict[keyPath]的時候我們會發現,報警告:告訴我們實例對象并沒有生成。這是分類方法中常見的警告。
分類方法中@property是不會幫我們生成實例對象,所以我們必須利用別的方式實現分類對象中的get、set方法。
這里就直接鋪一份代碼,代碼中有相應關鍵詞的注釋!
- (NSMutableDictionary<NSString *,xgKvoBlock> *)dict {/*objc_setAssocaitedObject 以及objc_getAssocaitedObjects方法 手動實現實例對象的創建objc_getAssocaitedObject 參數1:調用者 參數2:關聯的鍵值對象方法objc_setAssocatiedObject 參數1:調用者 參數2:關聯的鍵值對象方法 參數3:需要設置對象的屬性參數4:設置@property <(nonatomic , strong )> 尖括號中的屬性*/NSMutableDictionary *tmpDict = objc_getAssociatedObject(self, @selector(dict));if (!tmpDict) {tmpDict = [NSMutableDictionary dictionary];objc_setAssociatedObject(self, @selector(dict), tmpDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}return tmpDict; }- (NSMutableDictionary<NSString *,NSMutableArray *> *)kvoDict {NSMutableDictionary *tmpDict = objc_getAssociatedObject(self, @selector(kvoDict));if (!tmpDict) {tmpDict = [NSMutableDictionary dictionary];objc_setAssociatedObject(self, @selector(kvoDict), tmpDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}return tmpDict; }好的這下沒問題了。那么我們先實現第一步利用方法添加觀察者observer,并執行系統添加觀察者的方法
- (void)xgObserver:(NSObject *)observer keyPath:(NSString *)keyPath block:(xgKvoBlock)block {//observer 觀察者//keyPath 觀察者對象observer.dict[keyPath] = block; [self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:nil]; }?
接下來使用這么個方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
這時候腦袋大了,這方法有什么用呢?從釋意里截一段
/* Given that the receiver has been registered as an observer of the value at a key path relative to an object, be notified of a change to that value */在KVO當中,被觀察者與觀察者應該先建立關系,當被觀察的特定屬性改變時,立刻通知觀察者,建立聯系并調用此方法。
所以明確的一點是,當監聽的對象值變化時,block的調用就是在此。
當監聽的值發生改變時,獲取字典里keyPath鍵值對應的block代碼。執行他。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {//執行blockxgKvoBlock block = self.dict[keyPath];if (self.dict[keyPath]) {block();} }那么第一步完成了之后,開始尋思第二步的編碼了。當執行完block之后,銷毀該觀察者。
觀察者和觀察者對象不止一個,既然不止一個。我們就一起獲取他們,并且存到一個可變的array后一起銷毀他們。
主方法里通過鍵值keypath獲得觀察者對象,以及觀察者,一起加入數組中。
NSMutableArray *arr = self.kvoDict[keyPath];if (!arr) {arr = [NSMutableArray array];self.kvoDict[keyPath] = arr;}[arr addObject:observer];?
至于銷毀肯定會有人說,我知道我知道,dealloc方法!重寫他就好了。嘟嘟嘟
這是錯誤的,dealloc方法作為系統的根類。貿貿然重寫是會導致未知報錯的。所以我們自己編一個偽dealloc方法。并且在該分類方法中替換掉dealloc方法
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{method_exchangeImplementations(class_getInstanceMethod([self class], @selector(xgDealloc)), class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc")));});這里我們另開一條線程,單獨執行一次該替換方法的語句。至于代碼的細節。讀者英文就能懂了吧。。。
接下來實現的是dealloc方法
- (void)xgDealloc {[self.kvoDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) {NSMutableArray *arr = self.kvoDict[key];[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {[self removeObserver:obj forKeyPath:key];}];}];[self xgDealloc]; }方法中的enumerateKeysAndObjectsUsingBlock和enumerateObjectsUsingBlock分別類等于forin 遍歷/ for循環遍歷
那么剩下的就簡單易懂了,遍歷每個數組,銷毀他
這時候應該已經大功告成了吧?
其實并不是,這時候運行會引發不知名的死循環。原因出在dealloc中。報錯提示也是簡單易懂
kvodict為空指針,空指針銷毀怎么可能不報錯呢?正如上文提及到的。如果在分類方法中,實例的創建是需要手動的。自然而然此處也受到實例化失敗的影響。
所以此處我們必須得先加一個判斷(bool),并且修改下dealloc方法
- (BOOL)isKvoDict {if (objc_getAssociatedObject(self, @selector(kvoDict))) {return YES;}else{return NO;} } - (void)xgDealloc {if ([self isKvoDict]) {[self.kvoDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) {NSMutableArray *arr = self.kvoDict[key];[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop{[self removeObserver:obj forKeyPath:key];}];}];}[self xgDealloc]; }如果kvoDict有數據的話,開始判斷是否為空指針,當不為空指針時銷毀。
偷懶了好一陣子,撿起來撿起來撿起來。
這里涉及的部分知識點,實際上與我們日常接觸都差不了多少,很多也只是換一種形式。只是一些需要注意的地方要注意。
分類方法中實例問題啊,銷毀對象,根類改變等等。
那么今明倆天繼續更新~over
轉載于:https://www.cnblogs.com/UUUUgua/p/10149734.html
總結
以上是生活随笔為你收集整理的IOS —— KVO的一个小封装的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 可长点心吧-sort
- 下一篇: 使用UML描述需求都实现的过程