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