咱們先看下系統的KVO的實現:ios
@interface NSObject(NSKeyValueObserverRegistration)
/* Register or deregister as an observer of the value at a key path relative to the receiver. The options determine what is included in observer notifications and when they're sent, as described above, and the context is passed in observer notifications as described above. You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong. */ - (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)); - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; @end 複製代碼
原來KVO的實現是在NSObject的NSKeyValueObserverRegistration 分類裏.macos
因此咱們也實現個分類,在分類裏處理KVO的相關事宜:編程
接着咱們根據KVO的底層原理實現,開始一步步的自定義.api
從原理得知當對對象A進行KVO觀察時候,會動態生成一個子類,而後將對象的isa指向新生成的子類.因此應該在這方法裏:
安全
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
// 1: 驗證是否存在setter方法 : 不讓實例進來
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 動態生成子類
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存觀察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製代碼
Class newClass = [self createChildClassWithKeyPath:keyPath]; 動態生成子類時,咱們也要給新生成的子類重寫父類的set、class、dealloc、_isKVOA方法.bash
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重複建立生成新類
if (newClass) return newClass;
/**
* 若是內存不存在,建立生成
* 參數一: 父類
* 參數二: 新類的名字
* 參數三: 新類的開闢的額外空間
*/
// 2.1 : 申請類
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 註冊類
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
return newClass;
}
複製代碼
重寫class方法指向派生類的父類:async
Class lg_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
複製代碼
重寫setter方法:函數式編程
static void lg_setter(id self,SEL _cmd,id newValue){
}
複製代碼
在這裏你們和我一塊兒思考下,系統的KVO是怎麼觸發的?以前文章就已經說過要想了解KVO就必須先了解KVC,KVC的調用過程就會自動觸發鍵值觀察(KVO).好比:函數
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
self.person.nickName = @"KC";
複製代碼
其本質是Person類底層調用了- (void)setNickName:(NSString *)nickName方法, post
這樣就會觸發KVO的鍵值觀察.會進行KVO的回調:
#pragma mark - KVO回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
複製代碼
因此在重寫Person類的派生子類setter方法時, 要讓Person也響應setter方法,再一個就是observer (觀察者)響應 自定義方法 - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object。
因此在子類setter方法裏:轉發消息給父類,讓父類也實現setter方法,而後再給observer發送消息,響應
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"來了:%@",newValue);
// 4: 消息轉發 : 轉發給父類
// 改變父類的值 --- 能夠強制類型轉換
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 既然觀察到了,下一步不就是回調 -- 讓咱們的觀察者調用
// - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
// 1: 拿到觀察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
// 2: 消息發送給觀察者
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}複製代碼
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
// 指回給父類
Class superClass = [self class];
object_setClass(self, superClass);
}複製代碼
到這裏爲止自定義KVO的基本功能就完成了.咱們來試驗下 :
看運行輸出:
是否是來了,說明咱們自定義的KVO已經成功了,但確定還有許多東西要優化,請接着往下面看.
@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@end
複製代碼
@implementation LGInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
if (self=[super init]) {
_observer = observer;
_keyPath = keyPath;
}
return self;
}
@end
複製代碼
在添加觀察者時咱們就能夠:
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context{
// 1: 驗證是否存在setter方法 : 不讓實例進來
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 動態生成子類
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存觀察者信息
LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!observerArr) {
observerArr = [NSMutableArray arrayWithCapacity:1];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
複製代碼
在子類setter方法裏:
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"來了:%@",newValue);
// 4: 消息轉發 : 轉發給父類
// 改變父類的值 --- 能夠強制類型轉換
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 1: 拿到觀察者
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 對新舊值進行處理
if (info.options & LGKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & LGKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// 2: 消息發送給觀察者
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
});
}
}
}
複製代碼
這樣代碼是否是更好一些,設計更合理.
在剛剛的設計中咱們經過添加abserver以後,咱們還需從-(void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object,獲得屬性的變化,這種設計方法是否是太麻煩了.咱們能夠加入函數式編程設計思想,用block直接回調屬性變化結果.這樣接口和API須要稍微修改下:
@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) LGKVOBlock handleBlock;
@end
@implementation LGInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
if (self=[super init]) {
_observer = observer;
_keyPath = keyPath;
_handleBlock = block;
}
return self;
}
@end
複製代碼
lg_addObserver方法改爲:
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
// 1: 驗證是否存在setter方法 : 不讓實例進來
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 動態生成子類
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存信息
LGInfo *info = [[LGInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
複製代碼
派生類中:lg_setter裏直接用block進行回調.
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"來了:%@",newValue);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// 4: 消息轉發 : 轉發給父類
// 改變父類的值 --- 能夠強制類型轉換
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 5: 信息數據回調
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
}
複製代碼
補充下:這兩年蘋果底層不少api ,也加入了函數式編程思想.好比NSTimer,
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))複製代碼
剛剛自定的時候,每次須要本身手動移除觀察者,有時候盆友可能忘了,會致使崩潰異常等一系列後果,在這裏咱們就想能不能自動銷燬.
在這裏咱們先思考下,該何時銷燬?
有的盆友確定會想到當觀察者(一般是VC)釋放時,就該銷燬對象的監聽屬性.不錯,其實能不能更靠前,當Person 的派生類對象都釋放的時候,咱們是否是就應該能夠銷燬了.
因此咱們能夠監聽Person 的派生類的 dealloc 方法.可是這個dealloc 方法在NSOBject的分類裏,若是在這裏面之間操做的話、其它地方(VC)引用了這個頭文件沒有使用KVO的,當這個(VC)裏的NSObject對象釋放時也會來到這個方法.
因此方法一:
還記得 createChildClassWithKeyPath ,這個動態實現子類的方法嗎?
在這裏咱們重寫了 set、class 方法 ,如今再重寫dealloc 方法,
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重複建立生成新類
if (newClass) return newClass;
/**
* 若是內存不存在,建立生成
* 參數一: 父類
* 參數二: 新類的名字
* 參數三: 新類的開闢的額外空間
*/
// 2.1 : 申請類
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 註冊類
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
// 2.3.3 : 添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
return newClass;
}
static void lg_dealloc(id self,SEL _cmd){
Class superClass = [self class];
object_setClass(self, superClass);
}複製代碼
這樣就能夠自動釋放啦,因此你們知道爲何系統KVO在生成派生子類時,會重寫dealloc方法了吧.
第二種方法:
系統NSObject 原本就有dealloc方法,我爲啥要重寫?不重寫的話,若是在這裏面之間操做的話、其它地方(VC)引用了這個頭文件沒有使用KVO的,當這個(VC)裏的NSObject對象釋放時也會來到這個方法.因此咱們用到 MethodSwizzle.
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重複建立生成新類
if (newClass) return newClass;
/**
* 若是內存不存在,建立生成
* 參數一: 父類
* 參數二: 新類的名字
* 參數三: 新類的開闢的額外空間
*/
// 2.1 : 申請類
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 註冊類
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
// // 2.3.3 : 添加dealloc
// SEL deallocSEL = NSSelectorFromString(@"dealloc");
// Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
// const char *deallocTypes = method_getTypeEncoding(deallocMethod);
// class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
// 方法交換
[self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
return newClass;
}
複製代碼
- (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
// Class cls = self;
Class cls = [self class];
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!swiMethod) {
return NO;
}
if (!oriMethod) {
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
return YES;
}
複製代碼
- (void)myDealloc{
Class superClass = [self class];
object_setClass(self, superClass);
// 不會形成循環遞歸,下面調用myDealloc等於調用dealloc
[self myDealloc];
}
複製代碼
本文經過自定義實現一套簡化版的KVO進一步加深了對KVO
實現原理的理解。固然一套完善的KVO
並無這麼簡單,裏面還沒考慮到線程安全、鎖、觀察屬性以keypath等狀況。代碼中確定還有許多漏洞,在這裏給你們推薦比較成熟的自定義KVO.FaceBook開發設計好的(FBKVOController),你們有興趣能夠去看下.