經過上一篇咱們知道,在添加觀察以後,isa指向發生了變化,指向了動態子類NSKVONotifying_Person
。該子類有4個方法git
setNickName
:觀察對象的set方法class
:重寫class方法dealloc
:在該方法中isa指向又從新指回了父類addObserver
時確保當前的類(Person)
的keyPath
有對應的setter
方法NSKVONotifying_Person
addObserver
1.首先從keyPath能夠拼湊推導出set方法。eg:(name-> setName)。咱們先判斷本類中是否存在setName方法,若是不存在就報出異常。github
- (void)hb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
//1.確保存在keyPath的set方法
[self getSetterMethodFromKeyPath:keyPath];
//2.動態生成子類
Class newClass = [self creatChildClassByKeyPath:keyPath];
//3.isa指向派生的子類
object_setClass(self, newClass);
//4.保存觀察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製代碼
2.動態生成子類的時候判斷當前的子類是否存在,若是存在直接返回。不存在的話就申請、註冊、而後動態添加方法 setName:
和class
markdown
#pragma mark - 動態生成子類
-(Class)creatChildClassByKeyPath: (NSString *)keyPath {
// 2.1獲取當前的類的名字
NSString *oldClassName = NSStringFromClass([self class]);
// 獲取當前的類的子類名字
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kHBKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 2.2判斷是否存在
if (newClass) return newClass;
// 2.3不存在就建立 申請-註冊-添加方法
objc_allocateClassPair([self class], newClassName.UTF8String, 0);
objc_registerClassPair(newClass);
// 2.3.1添加方法class方法 class指向的是Person
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)hb_class, classTypes);
//2.3.2添加setKeyPath方法
SEL setterSEL = NSSelectorFromString(setterFromKeyPath(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)hb_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)hb_dealloc, deallocTypes);
return newClass;
}
複製代碼
setter方法
在當前派生子類重寫的setName:
方法中咱們須要作3點 1.判斷自動開關是否打開 2.發送到父類Person的set方法中(由於咱們set方法最終仍是修改掉了父類的set) 3.發送完成以後須要一個回調 也就是給觀察者發送通知async
static void hb_setter(id self, SEL _cmd, id newValue){
NSLog(@"來了:%@",newValue );
// 消息轉發給父類
NSString *keyPath = getterFromKeyPath(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
void (*hb_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)),
};
hb_msgSendSuper(&superStruct, _cmd, newValue);
// 1: 拿到觀察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
// 2: 消息發送給觀察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
複製代碼
觀察的數據比較多的話,咱們最好弄一個對象來保存觀察者,以及keyPath的新值和舊值,因此在最開始的步驟4保存觀察者的能夠這麼寫:函數
typedef NS_OPTIONS(NSUInteger, HBKeyValueObservingOptions) {
HBKeyValueObservingOptionNew = 0x01,
HBKeyValueObservingOptionOld = 0x02,
};
@interface HBKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) HBKeyValueObservingOptions options;
複製代碼
// 4: 保存觀察者信息
HBKVOInfo *info = [[HBKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
if (!observerArr) {
observerArr = [NSMutableArray arrayWithCapacity:1];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製代碼
在setter方法中給觀察者發回調信息的話就遍歷oop
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
for (HBKVOInfo *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 & HBKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & HBKeyValueObservingOptionOld) {
[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,@{keyPath:newValue},NULL);
});
}
}
複製代碼
removeObserver
在移除觀察者的時候首先須要遍歷observerArr
把observer中的keyPath給移除掉,而後把isa從新指向父類優化
// 指回給父類
Class superClass = [self class];
object_setClass(self, superClass);
複製代碼
typedef void(^HBKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
複製代碼
這麼設計的話須要變更的地方有:ui
- (void)hb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;
複製代碼
@interface HBKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) HBKVOBlock handleBlock;
@end
複製代碼
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
for (HBKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
複製代碼
但願達到的效果是不用用戶調用,自動釋放。咱們看到上面的removeObserver
咱們在這裏面釋放,可是若是用戶不調用這個方法的話,是永遠不會釋放的。同時在上面的自定義的setter方法中,咱們派生的子類還有一個dealloc
方法沒有實現。把dealloc
的方法實現指定爲下面的函數atom
static void hb_dealloc(id self,SEL _cmd){
Class superClass = [self class];
object_setClass(self, superClass);
}
複製代碼
工程中驗證一下,在這個方法調用以前self.person指向的是派生類,這個以後指向的是Person類 spa
FBKVOController
思考KVO的麻煩的地方在於每次移除比較麻煩,觀察和回調的代碼段不是連續的比較分散,看起來寫起來都比較費勁。而FBKVOController
利用了中介者模式,函數響應式調用、不須要手動銷燬,vc不須要關注釋放,它下層已經作好了相關的工做。 self-> _kvoCtrl -> FB單例 -> self.person
- (FBKVOController *)kvoCtrl{
if (!_kvoCtrl) {
_kvoCtrl = [FBKVOController controllerWithObserver:self];
}
return _kvoCtrl;
}
複製代碼
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
複製代碼
1.建立一個info來接收keyPath
、option
、block
2.建立一個單例來把object
這裏指的是self.person
和info關聯起來 3.調用系統自身的kvo 4.在回調時拿到對應的block
而後執行
FB自動銷燬 咱們控制器釋放的時候,_kvoCtrl也會同步釋放。
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}
複製代碼
- (void)unobserveAll
{
[self _unobserveAll];
}
複製代碼
釋放的時候,拿到objectInfo的哈希map表一個一個移除,同時也移除全部的對象和信息。
- (void)_unobserveAll
{
// lock
pthread_mutex_lock(&_lock);
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// clear table and map
[_objectInfosMap removeAllObjects];
// unlock
pthread_mutex_unlock(&_lock);
_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
for (id object in objectInfoMaps) {
// unobserve each registered object and infos
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
複製代碼
GNUstep Base Library
GNU 蘋果開源的代碼,能夠看到KVO的內部實現跟本篇的自定義大體思想是一致的。