1. KVO原理
??KVO的實現(xiàn)原理相信大家都應該有所了解, 就是在對象A為屬性B添加監(jiān)聽addObserver: forKeyPath: options: context:的時候會自動為類C(就是對象A的類)創(chuàng)建一個派生類D(子類), 并且在類D中重寫被監(jiān)聽屬性的setter方法E. 并且會把你被監(jiān)聽對象的isa指針由原來的C指向D, 當你調用被監(jiān)聽屬性的setter方法時, 方法E會被執(zhí)行. (這里不懂的話可以拿出紙筆在紙上畫出來, 更好理解)
2.實現(xiàn)KVO
??根據上面的原理我們知道, 實際上KVO就是Runtime的應用:
??①因為在調用addObserver:的時候會動態(tài)添加一個類newC, 所以這里會動態(tài)創(chuàng)建一個類;
??②動態(tài)添加類之后我們還需要重寫被監(jiān)聽對象的setter方法, 我們還需要為newC動態(tài)添加添加一個method;
??我們開始先來模仿系統(tǒng)實現(xiàn)KVO的方式.
// 添加監(jiān)聽
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
// 移除監(jiān)聽
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
// 監(jiān)聽值的變化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
上面這三個方法是我們在使用KVO會用到的三個方法, 現(xiàn)在我們來嘗試著自己寫這三個方法, 創(chuàng)建一個NSObject的category
@interface NSObject (KVO)
// 添加監(jiān)聽
- (void)ls_addObserver:(NSObject *)observer forKey:(NSString *)key;
// 移除監(jiān)聽
- (void)ls_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
@interface NSObject (Observering)
// 監(jiān)聽值變化
- (void)observeNewValue:(NSObject *)newValue;
@end
先來看添加監(jiān)聽的方法, 這里的任務是①動態(tài)創(chuàng)建一個被監(jiān)聽類的派生類; ②為整個派生類添加被監(jiān)聽屬性的setter方法;③修改被監(jiān)聽對象的isa指針, 使其指向派生類;
- (void)ls_addObserver:(NSObject *)observer forKey:(NSString *)key {
Class cls = [self class];
NSString* clsName = NSStringFromClass(cls);
NSString* setKey = [NSString stringWithFormat:@"set%@%@:", [key substringToIndex:1].uppercaseString, [key substringFromIndex:1]];
SEL setterSelector = NSSelectorFromString(setKey);
Method setterMethod = class_getInstanceMethod(cls, setterSelector);
const char* types = method_getTypeEncoding(setterMethod);
// 1.動態(tài)創(chuàng)建一個類
Class newClss = objc_allocateClassPair(cls, [NSString stringWithFormat:@"%@%@", kLsClassNamePrefix, clsName].UTF8String, 0);
// 2.添加方法, 需要自己寫setMethod
class_addMethod(newClss, setterSelector, (IMP)setMethod, types);
objc_registerClassPair(newClss);
// 3.替換isa指針
object_setClass(self, newClss);
// 記錄下對象的key和監(jiān)聽者
NSMutableArray* servers = objc_getAssociatedObject(self, kKey.UTF8String);
if (!servers) {
servers = [NSMutableArray arrayWithCapacity:0];
objc_setAssociatedObject(self, kKey.UTF8String, servers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
ObserverInfo* info = [[ObserverInfo alloc] initWithObserver:observer key:key];
[servers addObject:info];
}
正如上面說的, 這里是有三個任務,
// 這里是添加的替換方法
static void setMethod(id self, SEL _cmd, id newValue) {
NSString* setterName = NSStringFromSelector(_cmd);
NSString* getterName = [setterName substringFromIndex:4];
getterName = [NSString stringWithFormat:@"%@%@", [setterName substringWithRange:NSMakeRange(3, 1)].lowercaseString, getterName];
getterName = [getterName substringToIndex:getterName.length - 1];
// setter方法的任務有兩個
// 1. 告訴所有的監(jiān)聽此屬性的對象, 對象屬性發(fā)生改變
NSMutableArray* observers = objc_getAssociatedObject(self, kKey.UTF8String);
for (ObserverInfo* info in observers) {
if ([info.key isEqualToString:getterName]) {
if ([info.observer respondsToSelector:@selector(observeNewValue:)]) {
[info.observer performSelector:@selector(observeNewValue:) withObject:newValue];
}
}
}
// 2. 調用父類的setter方法
struct objc_super superClass = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
// 這里直接調用objc_msgSendSuper會發(fā)生錯誤
void(*objc_msgSendSuperCasted)(void *, SEL, id) = (void*)objc_msgSendSuper;
objc_msgSendSuperCasted(&superClass, _cmd, newValue);
}
這里是添加的setter方法
// 移除監(jiān)聽的方法
- (void)ls_removeObserver:(NSObject *)observer forKey:(NSString *)key {
NSMutableArray* servers = objc_getAssociatedObject(self, kKey.UTF8String);
ObserverInfo* info = nil;
for (ObserverInfo* observerInfo in servers) {
if (observerInfo.observer == observer && observerInfo.key == key) {
info = observerInfo;
break;
}
}
[servers removeObject:info];
}
然后在對應的監(jiān)聽者中實現(xiàn)- (void)observeNewValue:(NSObject *)newValue;就可以了.
當然本文旨在讓大家明白整體的流程, 這里的流程只是簡單的實現(xiàn)了KVO, 里面還有很多的流程判斷和注意事項就不一一贅述了.