KVO原理分析下| 8月更文挑戰

KVO探索原理

經過上一篇咱們知道,在添加觀察以後,isa指向發生了變化,指向了動態子類NSKVONotifying_Person。該子類有4個方法git

  1. setNickName:觀察對象的set方法
  2. class:重寫class方法
  3. dealloc:在該方法中isa指向又從新指回了父類

自定義KVO思路

  1. addObserver時確保當前的類(Person)keyPath有對應的setter方法
  2. 動態生成的子類也就是isa_siwziling,給子類添加對應的方法
  3. 把isa指向從Person指到NSKVONotifying_Person
  4. set方法處理

自定義KVO-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:classmarkdown

#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;
}
複製代碼

自定義KVO-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);
}
複製代碼

自定義KVO-優化

觀察的數據比較多的話,咱們最好弄一個對象來保存觀察者,以及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);
            });
        }
    }
複製代碼

自定義KVO-removeObserver

在移除觀察者的時候首先須要遍歷observerArr把observer中的keyPath給移除掉,而後把isa從新指向父類優化

// 指回給父類
        Class superClass = [self class];
        object_setClass(self, superClass);
複製代碼

自定義函數式KVO

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);
        }
    }
複製代碼

KVO自動銷燬機制

但願達到的效果是不用用戶調用,自動釋放。咱們看到上面的removeObserver咱們在這裏面釋放,可是若是用戶不調用這個方法的話,是永遠不會釋放的。同時在上面的自定義的setter方法中,咱們派生的子類還有一個dealloc方法沒有實現。把dealloc的方法實現指定爲下面的函數atom

static void hb_dealloc(id self,SEL _cmd){
 	 Class superClass = [self class];
  	 object_setClass(self, superClass);
}
複製代碼

工程中驗證一下,在這個方法調用以前self.person指向的是派生類,這個以後指向的是Person類 image.pngspa

FaceBook的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來接收keyPathoptionblock 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的內部實現跟本篇的自定義大體思想是一致的。

相關文章
相關標籤/搜索