1. 自定义KVO

仿照KVO的实现方式,自定义NSObject的分类LGKVO

1.1 准备工作

定义全局静态常量:

  1. static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
  2. static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";

实现setterForGetter方法:

  1. #pragma mark - get方法获取set方法的名称 key ===>>> setKey:
  2. static NSString *setterForGetter(NSString *getter){
  3. if (getter.length <= 0) { return nil;}
  4. NSString *firstString = [[getter substringToIndex:1] uppercaseString];
  5. NSString *leaveString = [getter substringFromIndex:1];
  6. return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
  7. }

实现getterForSetter方法:

  1. #pragma mark - set方法获取getter方法的名称 set<Key>:===> key
  2. static NSString *getterForSetter(NSString *setter){
  3. if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
  4. NSRange range = NSMakeRange(3, setter.length-4);
  5. NSString *getter = [setter substringWithRange:range];
  6. NSString *firstString = [[getter substringToIndex:1] lowercaseString];
  7. return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
  8. }

1.2 注册观察者

实现lg_addObserver方法:

  1. - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context{
  2. // 1: 验证是否存在setter方法 : 不让实例进来
  3. [self judgeSetterMethodFromKeyPath:keyPath];
  4. // 2: 动态生成子类
  5. Class newClass = [self createChildClassWithKeyPath:keyPath];
  6. // 3: isa的指向 : LGKVONotifying_LGPerson
  7. object_setClass(self, newClass);
  8. // 4: 保存观察者信息
  9. [self lg_addInfoWithObserver:observer forKeyPath:keyPath options:options];
  10. }
  • 【第一步】验证被监听者为属性,必须存在setter方法

  • 【第二步】动态生成子类

  • 【第三步】修改isa指向

  • 【第四步】保存观察者信息

1.2.1 验证被监听者

实现judgeSetterMethodFromKeyPath方法:

  1. - (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
  2. Class superClass = object_getClass(self);
  3. SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
  4. Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
  5. if (!setterMethod) {
  6. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"当前%@不存在setter方法",keyPath] userInfo:nil];
  7. }
  8. }

1.2.2 动态生成子类

实现createChildClassWithKeyPath方法:

  1. - (Class)createChildClassWithKeyPath:(NSString *)keyPath{
  2. NSString *oldClassName = NSStringFromClass([self class]);
  3. NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
  4. Class newClass = NSClassFromString(newClassName);
  5. // 防止重复创建生成新类
  6. if (newClass) return newClass;
  7. /**
  8. * 如果内存不存在,创建生成
  9. * 参数一: 父类
  10. * 参数二: 新类的名字
  11. * 参数三: 新类的开辟的额外空间
  12. */
  13. // 2.1 : 申请类
  14. newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
  15. // 2.2 : 注册类
  16. objc_registerClassPair(newClass);
  17. // 2.3.1 : 添加class : class的指向是LGPerson
  18. SEL classSEL = NSSelectorFromString(@"class");
  19. Method classMethod = class_getInstanceMethod([self class], classSEL);
  20. const char *classTypes = method_getTypeEncoding(classMethod);
  21. class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
  22. // 2.3.2 : 添加setter
  23. SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
  24. Method setterMethod = class_getInstanceMethod([self class], setterSEL);
  25. const char *setterTypes = method_getTypeEncoding(setterMethod);
  26. class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
  27. return newClass;
  28. }
  • 【第一步】验证动态子类是否已经创建

    • 已创建,直接返回

    • 未创建,进入【第二步】

  • 【第二步】创建子类

    • 申请类,需要父类、新类的名称、开辟的额外空间

    • 注册类

    • 添加方法,需要classsetter方法

1.2.3 保存观察者信息

创建LGKVOInfo,用来存储观察者信息

  1. #import <Foundation/Foundation.h>
  2. typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
  3. LGKeyValueObservingOptionNew = 0x01,
  4. LGKeyValueObservingOptionOld = 0x02,
  5. };
  6. @interface LGKVOInfo : NSObject
  7. @property (nonatomic, weak) NSObject *observer;
  8. @property (nonatomic, copy) NSString *keyPath;
  9. @property (nonatomic, assign) LGKeyValueObservingOptions options;
  10. - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
  11. @end
  12. @implementation LGKVOInfo
  13. - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options{
  14. self = [super init];
  15. if (self) {
  16. self.observer = observer;
  17. self.keyPath = keyPath;
  18. self.options = options;
  19. }
  20. return self;
  21. }
  22. @end

实现lg_addInfoWithObserver方法:

  1. -(void)lg_addInfoWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options {
  2. LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
  3. NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
  4. if (!observerArr) {
  5. observerArr = [NSMutableArray arrayWithCapacity:1];
  6. [observerArr addObject:info];
  7. objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  8. }
  9. }

1.3 实现子类方法

动态生成子类时,添加的classsetter方法,它们的IMP分别指向lg_classlg_setter函数地址

1.3.1 lg_class

实现lg_class函数:

  1. Class lg_class(id self,SEL _cmd){
  2. return class_getSuperclass(object_getClass(self));
  3. }

使用中间类重写后的class方法,获取的还是原始类对象,仿佛KVO所做的一切都不存在

1.3.2 lg_setter

实现lg_setter函数:

  1. static void lg_setter(id self,SEL _cmd,id newValue){
  2. // 1:消息转发:转发给父类,改变父类的值
  3. NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
  4. id oldValue = [self valueForKey:keyPath];
  5. void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
  6. struct objc_super superStruct = {
  7. .receiver = self,
  8. .super_class = class_getSuperclass(object_getClass(self)),
  9. };
  10. lg_msgSendSuper(&superStruct,_cmd,newValue);
  11. // 2:获取观察者
  12. NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
  13. for (LGKVOInfo *info in observerArr) {
  14. if ([info.keyPath isEqualToString:keyPath]) {
  15. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  16. NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
  17. // 3:对新、旧值进行处理
  18. if (info.options & LGKeyValueObservingOptionNew) {
  19. [change setObject:newValue forKey:NSKeyValueChangeNewKey];
  20. }
  21. if (info.options & LGKeyValueObservingOptionOld) {
  22. if (oldValue) {
  23. [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
  24. }
  25. else{
  26. [change setObject:@"" forKey:NSKeyValueChangeOldKey];
  27. }
  28. }
  29. // 4:发送消息给观察者
  30. SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
  31. ((void (*) (id, SEL, NSString *, id, NSDictionary *, void *)) objc_msgSend)(info.observer,observerSEL,keyPath,self,change,NULL);
  32. });
  33. }
  34. }
  35. }
  • 【第一步】消息转发:转发给父类,改变父类的值

  • 【第二步】获取观察者

  • 【第三步】对新、旧值进行处理

  • 【第四步】发送消息给观察者

1.4 移除观察者

实现lg_removeObserver方法:

  1. - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
  2. NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
  3. if (observerArr.count<=0) {
  4. return;
  5. }
  6. for (LGKVOInfo *info in observerArr) {
  7. if ([info.keyPath isEqualToString:keyPath]) {
  8. [observerArr removeObject:info];
  9. objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  10. break;
  11. }
  12. }
  13. if (observerArr.count<=0) {
  14. // 指回给父类
  15. Class superClass = [self class];
  16. object_setClass(self, superClass);
  17. }
  18. }
  • 【第一步】移除观察者

  • 【第二步】isa指回原始类对象

1.5 自定义KVO的使用

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. self.person = [[LGPerson alloc] init];
  4. NSLog(@"注册观察者之前:%s",object_getClassName(self.person));
  5. [self.person lg_addObserver:self forKeyPath:@"nickName" options:(LGKeyValueObservingOptionNew|LGKeyValueObservingOptionOld) context:NULL];
  6. NSLog(@"注册观察者之后:%s",object_getClassName(self.person));
  7. }
  8. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  9. self.person.nickName = @"KC";
  10. }
  11. - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  12. NSLog(@"KVO回调:%@",change);
  13. }
  14. - (void)dealloc{
  15. NSLog(@"移除观察者之前:%s",object_getClassName(self.person));
  16. [self.person lg_removeObserver:self forKeyPath:@"nickName"];
  17. NSLog(@"移除观察者之后:%s",object_getClassName(self.person));
  18. }
  19. -------------------------
  20. //输出以下内容:
  21. 注册观察者之前:LGPerson
  22. 注册观察者之后:LGKVONotifying_LGPerson
  23. KVO回调:{
  24. new = KC;
  25. old = "";
  26. }
  27. 移除观察者之前:LGKVONotifying_LGPerson
  28. 移除观察者之后:LGPerson

2. 函数式KVO

函数式编程是一种编程方式,把运算过程写成一系列嵌套的函数调用。函数编程支持函数作为第一类对象,有时称为闭包或者仿函数(functor)对象

函数式编程的优点:

  • 代码简洁,开发快速

    • 函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快
  • 接近自然语言,易于理解

    • 函数式编程的自由度很高,可以写出很接近自然语言的代码
  • 更方便的代码管理

    • 函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合
  • 易于并发编程

    • 函数式编程不需要考虑死锁(deadlock),因为它不修改变量,所以根本不存在锁线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署并发编程(concurrency

2.1 注册观察者

定义LGKVOBlock

  1. typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);

修改lg_addObserver方法,将block作为方法参数

  1. - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
  2. // 1: 验证是否存在setter方法 : 不让实例进来
  3. [self judgeSetterMethodFromKeyPath:keyPath];
  4. // 2: 动态生成子类
  5. Class newClass = [self createChildClassWithKeyPath:keyPath];
  6. // 3: isa的指向 : LGKVONotifying_LGPerson
  7. object_setClass(self, newClass);
  8. // 4: 保存观察者信息
  9. [self lg_addInfoWithObserver:observer forKeyPath:keyPath block:block];
  10. }

2.1.1 保存观察者信息

修改LGKVOInfo,增加block属性,初始化方法增加block参数

  1. #import <Foundation/Foundation.h>
  2. typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
  3. LGKeyValueObservingOptionNew = 0x01,
  4. LGKeyValueObservingOptionOld = 0x02,
  5. };
  6. @interface LGKVOInfo : NSObject
  7. @property (nonatomic, weak) NSObject *observer;
  8. @property (nonatomic, copy) NSString *keyPath;
  9. @property (nonatomic, copy) LGKVOBlock handleBlock;
  10. - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block;
  11. @end
  12. @implementation LGKVOInfo
  13. - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
  14. if (self=[super init]) {
  15. _observer = observer;
  16. _keyPath = keyPath;
  17. _handleBlock = block;
  18. }
  19. return self;
  20. }
  21. @end

修改lg_addInfoWithObserver方法,将block作为方法参数

  1. -(void)lg_addInfoWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block {
  2. LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
  3. NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
  4. if (!mArray) {
  5. mArray = [NSMutableArray arrayWithCapacity:1];
  6. objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  7. }
  8. [mArray addObject:info];
  9. }

2.2 实现子类方法

修改子类的setter方法,使用函数式编程,传入的block为回调方法。所以在setter方法中,无需调用KVO回调方法,改为调用info中保存的block即可

2.2.1 lg_setter

修改lg_setter函数,将调用KVO回调方法,改为调用info中保存的block

  1. static void lg_setter(id self,SEL _cmd,id newValue){
  2. // 1:消息转发:转发给父类,改变父类的值
  3. NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
  4. id oldValue = [self valueForKey:keyPath];
  5. void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
  6. struct objc_super superStruct = {
  7. .receiver = self,
  8. .super_class = class_getSuperclass(object_getClass(self)),
  9. };
  10. lg_msgSendSuper(&superStruct,_cmd,newValue);
  11. // 2:获取观察者
  12. NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
  13. for (LGKVOInfo *info in mArray) {
  14. // 3:调用block
  15. if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
  16. info.handleBlock(info.observer, keyPath, oldValue, newValue);
  17. }
  18. }
  19. }

2.3 函数式KVO的使用

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. self.person = [[LGPerson alloc] init];
  4. NSLog(@"注册观察者之前:%s",object_getClassName(self.person));
  5. [self.person lg_addObserver:self forKeyPath:@"nickName" block:^(id _Nonnull observer, NSString * _Nonnull keyPath, id _Nonnull oldValue, id _Nonnull newValue) {
  6. NSLog(@"回调方法:%@ - %@",oldValue,newValue);
  7. }];
  8. NSLog(@"注册观察者之后:%s",object_getClassName(self.person));
  9. }
  10. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  11. self.person.nickName = @"KC";
  12. }
  13. - (void)dealloc{
  14. NSLog(@"移除观察者之前:%s",object_getClassName(self.person));
  15. [self.person lg_removeObserver:self forKeyPath:@"nickName"];
  16. NSLog(@"移除观察者之后:%s",object_getClassName(self.person));
  17. }
  18. -------------------------
  19. //输出以下内容:
  20. 注册观察者之前:LGPerson
  21. 注册观察者之后:LGKVONotifying_LGPerson
  22. 回调方法:(null) - KC
  23. 移除观察者之前:LGKVONotifying_LGPerson
  24. 移除观察者之后:LGPerson

3. 自动销毁机制

使用KVO必须手动移除观察者,我们是否可以重写子类的dealloc方法,在其销毁的时候,自动移除观察者

3.1 动态生成子类

修改createChildClassWithKeyPath方法,添加dealloc方法

  1. - (Class)createChildClassWithKeyPath:(NSString *)keyPath{
  2. ...
  3. // 2.3.3 : 添加dealloc
  4. SEL deallocSEL = NSSelectorFromString(@"dealloc");
  5. Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
  6. const char *deallocTypes = method_getTypeEncoding(deallocMethod);
  7. class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
  8. return newClass;
  9. }

3.2 实现子类方法

动态生成子类时,添加的dealloc方法,IMP指向lg_dealloc函数地址

3.2.1 lg_dealloc

实现lg_dealloc函数:

  1. static void lg_dealloc(id self,SEL _cmd){
  2. NSLog(@"自动销毁之前:%s",object_getClassName(self));
  3. Class superClass = [self class];
  4. object_setClass(self, superClass);
  5. NSLog(@"自动销毁之后:%s",object_getClassName(self));
  6. }

3.3 自动销毁机制的使用

  1. - (void)dealloc{
  2. // LGViewController中的dealloc什么都不处理
  3. // 依赖子类中的lg_dealloc自动销毁
  4. }
  5. -------------------------
  6. //输出以下内容:
  7. 注册观察者之前:LGPerson
  8. 注册观察者之后:LGKVONotifying_LGPerson
  9. 回调方法:(null) - KC
  10. 自动销毁之前:LGKVONotifying_LGPerson
  11. 自动销毁之后:LGPerson

4. FBKVOController

上述案例中,自定义的KVO代码并不完善,目的只是阐述KVO的底层原理,列举KVO使用中的一些痛点

更完善的自定义KVO,已经被 Facebook实现并开源:

  • 采用中介者设计模式,对常用的KVO机制进行了额外的一层封装

  • 使用数组形式,可同时对model中多个属性进行监听

  • 提供actionblock两种方式进行回调,避免KVO的相关代码四处散落

  • 提供自动销毁机制,无需在dealloc方法中移除观察者

4.1 基本使用

4.1.1 使用block回调

  1. #import "ViewController.h"
  2. #import <FBKVOController.h>
  3. #import "LGPerson.h"
  4. @interface ViewController ()
  5. @property (nonatomic, strong) FBKVOController *kvoCtrl;
  6. @property (nonatomic, strong) LGPerson *person;
  7. @end
  8. @implementation ViewController
  9. - (void)viewDidLoad {
  10. [super viewDidLoad];
  11. self.person = [[LGPerson alloc] init];
  12. [self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
  13. NSLog(@"****%@****",change);
  14. }];
  15. }
  16. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  17. self.person.name = @"KC";
  18. }
  19. - (FBKVOController *)kvoCtrl{
  20. if (!_kvoCtrl) {
  21. _kvoCtrl = [FBKVOController controllerWithObserver:self];
  22. }
  23. return _kvoCtrl;
  24. }
  25. @end

4.1.2 使用action回调

  1. [self.kvoCtrl observe:self.person keyPath:@"age" options:NSKeyValueObservingOptionNew action:@selector(lg_observerAge)];

4.1.3 对可变数组的监听

  1. [self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
  2. NSLog(@"****%@****",change);
  3. }];

4.2 源码解析

4.2.1 FBKVOController的初始化

  1. - (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
  2. {
  3. self = [super init];
  4. if (nil != self) {
  5. //一般情况下 observer 会持有 FBKVOController 为了避免循环引用,此处的_observer 的内存管理语义是弱引用
  6. _observer = observer;
  7. //定义 NSMapTable key的内存管理策略,在默认情况,传入的参数 retainObserved = YES
  8. NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
  9. //创建 NSMapTable key 为 id 类型,value 为 NSMutableSet 类型
  10. _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
  11. //初始化互斥锁,避免多线程间的数据竞争
  12. pthread_mutex_init(&_lock, NULL);
  13. }
  14. return self;
  15. }

4.2.2 注册观察者

  1. - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
  2. {
  3. NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
  4. if (nil == object || 0 == keyPath.length || NULL == block) {
  5. return;
  6. }
  7. // create info
  8. _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
  9. // observe object with info
  10. [self _observe:object info:info];
  11. }
  • _FBKVOInfo类,存储观察者信息,包含:FBKVOControllerkeyPathoptionsblock

  • 添加observeinfo的关联

4.2.3 observeinfo的关联

  1. - (void)_observe:(id)object info:(_FBKVOInfo *)info
  2. {
  3. // lock
  4. pthread_mutex_lock(&_lock);
  5. NSMutableSet *infos = [_objectInfosMap objectForKey:object];
  6. // check for info existence
  7. _FBKVOInfo *existingInfo = [infos member:info];
  8. if (nil != existingInfo) {
  9. // observation info already exists; do not observe it again
  10. // unlock and return
  11. pthread_mutex_unlock(&_lock);
  12. return;
  13. }
  14. // lazilly create set of infos
  15. if (nil == infos) {
  16. infos = [NSMutableSet set];
  17. [_objectInfosMap setObject:infos forKey:object];
  18. }
  19. // add info and oberve
  20. [infos addObject:info];
  21. // unlock prior to callout
  22. pthread_mutex_unlock(&_lock);
  23. [[_FBKVOSharedController sharedController] observe:object info:info];
  24. }
  • FBKVOController中持有NSMapTable,使用NSMapTable代替关联对象

  • object作为key,获取NSMutableSet

  • NSMutableSet中存储_FBKVOInfo

  • 如果infos不存在,进行创建,并添加到NSMapTable

  • info对象添加到NSMutableSet类型的infos

  • _FBKVOSharedController为单例模式的中介者,负责真正的KVO操作

4.2.4 中介者-注册系统KVO

  1. - (void)observe:(id)object info:(nullable _FBKVOInfo *)info
  2. {
  3. if (nil == info) {
  4. return;
  5. }
  6. // register info
  7. pthread_mutex_lock(&_mutex);
  8. [_infos addObject:info];
  9. pthread_mutex_unlock(&_mutex);
  10. // add observer
  11. [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
  12. if (info->_state == _FBKVOInfoStateInitial) {
  13. info->_state = _FBKVOInfoStateObserving;
  14. } else if (info->_state == _FBKVOInfoStateNotObserving) {
  15. // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
  16. // and the observer is unregistered within the callback block.
  17. // at this time the object has been registered as an observer (in Foundation KVO),
  18. // so we can safely unobserve it.
  19. [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  20. }
  21. }
  • 传入的object为实例对象,调用系统KVO的注册观察者,开始对属性监听

  • 传入addObserver方法的self为当前单例的中介者

  • 中介者中持有NSHashTable,以弱引用的方式存储_FBKVOInfo

  • 通过_FBKVOInfo_state成员变量,根据枚举值_FBKVOInfoStateInitial_FBKVOInfoStateObserving_FBKVOInfoStateNotObserving决定新增或删除KVO

4.2.5 中介者-实现回调

  1. - (void)observeValueForKeyPath:(nullable NSString *)keyPath
  2. ofObject:(nullable id)object
  3. change:(nullable NSDictionary<NSString *, id> *)change
  4. context:(nullable void *)context
  5. {
  6. NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
  7. _FBKVOInfo *info;
  8. {
  9. // lookup context in registered infos, taking out a strong reference only if it exists
  10. pthread_mutex_lock(&_mutex);
  11. info = [_infos member:(__bridge id)context];
  12. pthread_mutex_unlock(&_mutex);
  13. }
  14. if (nil != info) {
  15. // take strong reference to controller
  16. FBKVOController *controller = info->_controller;
  17. if (nil != controller) {
  18. // take strong reference to observer
  19. id observer = controller.observer;
  20. if (nil != observer) {
  21. // dispatch custom block or action, fall back to default action
  22. if (info->_block) {
  23. NSDictionary<NSString *, id> *changeWithKeyPath = change;
  24. // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
  25. if (keyPath) {
  26. NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
  27. [mChange addEntriesFromDictionary:change];
  28. changeWithKeyPath = [mChange copy];
  29. }
  30. info->_block(observer, object, changeWithKeyPath);
  31. } else if (info->_action) {
  32. #pragma clang diagnostic push
  33. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  34. [observer performSelector:info->_action withObject:change withObject:object];
  35. #pragma clang diagnostic pop
  36. } else {
  37. [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
  38. }
  39. }
  40. }
  41. }
  42. }
  • 通过info获取FBKVOController

  • FBKVOController中,获取observer,也就是真正的VC

  • 通过info获取block并调用,传入必要的参数

4.2.6 FBKVOController的销毁

  1. - (void)dealloc
  2. {
  3. [self unobserveAll];
  4. pthread_mutex_destroy(&_lock);
  5. }
  6. - (void)unobserveAll
  7. {
  8. [self _unobserveAll];
  9. }
  10. - (void)_unobserveAll
  11. {
  12. // lock
  13. pthread_mutex_lock(&_lock);
  14. NSMapTable *objectInfoMaps = [_objectInfosMap copy];
  15. // clear table and map
  16. [_objectInfosMap removeAllObjects];
  17. // unlock
  18. pthread_mutex_unlock(&_lock);
  19. _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
  20. for (id object in objectInfoMaps) {
  21. // unobserve each registered object and infos
  22. NSSet *infos = [objectInfoMaps objectForKey:object];
  23. [shareController unobserve:object infos:infos];
  24. }
  25. }
  • VC销毁时,控制器中初始化的FBKVOController也会跟随销毁

  • FBKVOControllerdealloc方法,先拷贝自身持有的NSMapTable到临时表,然后将其清空,再遍历临时表,从单例的中介者中,取消实例对象和infos的关联

4.2.7 中介者-移除系统KVO的监听

  1. - (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos
  2. {
  3. if (0 == infos.count) {
  4. return;
  5. }
  6. // unregister info
  7. pthread_mutex_lock(&_mutex);
  8. for (_FBKVOInfo *info in infos) {
  9. [_infos removeObject:info];
  10. }
  11. pthread_mutex_unlock(&_mutex);
  12. // remove observer
  13. for (_FBKVOInfo *info in infos) {
  14. if (info->_state == _FBKVOInfoStateObserving) {
  15. [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  16. }
  17. info->_state = _FBKVOInfoStateNotObserving;
  18. }
  19. }
  • 调用系统KVOremoveObserver方法,移除观察者

  • 销毁流程:VC销毁→FBKVOController销毁→调用中介者的unobserve方法→移除观察者

4.3 扩展

4.3.1 NSSetNSHashTable

  • NSSet是排重后的object集合,NSHashTableNSSet的容器,并且只有可变版本,允许对添加到容器中的对象是弱引用的持有关系,当NSHashTable中的对象销毁时,该对象也会从容器中移除

4.3.2 NSDictionaryNSMapTable

  • NSMapTableNSDictionary类似,唯一区别是多一个功能,可设置keyvalueNSPointerFunctionsOptions

  • NSDictionarykey策略固定是copy,考虑到开销问题,一般使用简单的数字或字符串作为key。但如果碰到使用object作为key的场景,使用NSMapTable更为适宜

  • NSMapTable可通过NSFunctionsPointer来分别定义对keyvalue的内存管理策略,简单可以分为 strongweak以及copy

4.3.3 NSSetmember方法

FBKVOController中,注册观察者方法,每次都创建_FBKVOInfo类型的临时变量info
image.png

info传入关联方法中,使用NSSetmember方法,判断info是否存在
image.png

这里有个疑问,既然info是每次都创建的临时变量,每次的地址一定是不同的,使用NSSetmember方法,是根据什么来判断info是否存在的?

NSSetmember方法:确定给定的对象是否存在于集合中,如果存在则返回该对象

  1. - (ObjectType)member:(ObjectType)object;
  • 将检查集合中的每个元素是否与object相等,直到找到匹配项或到达集合的末尾。如果isEqual:返回YES,则认为对象是相等的

也就是说,NSSetmember方法,依赖于object对象的isEqual:方法,如果该方法返回YES,则认为对象是相等的

找到_FBKVOInfoisEqual方法:

  1. - (BOOL)isEqual:(id)object
  2. {
  3. if (nil == object) {
  4. return NO;
  5. }
  6. if (self == object) {
  7. return YES;
  8. }
  9. if (![object isKindOfClass:[self class]]) {
  10. return NO;
  11. }
  12. return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
  13. }
  • 最终判断两个对象的keyPath是否相同,所以即使info的地址不一样,也有可能被判断为相同对象

5. GNUstep Base

5.1 addObserver

image.png

  • setup:开始准备阶段
  • replacementForClass:生成动态子类

image.png

  • 保存info

image.png

  • 多级keyPath的处理

5.1.1 setup

image.png

  • 开始准备阶段

5.1.2 replacementForClass

image.png

  • 通过c查找r,存在直接返回
  • 不存在,调用GSKVOReplacementinitWithClass初始化r,添加到表中

5.1.3 GSKVOReplacementinitWithClass方法

image.png

  • GSObjCMakeClass:动态生成子类
  • GSObjCAddClasses:注册到内存
  • GSObjCAddClassBehavior:添加方法并重写

5.2 setter

image.png

  • 判断automaticallyNotifiesObserversForKey方法的返回
  • 返回YES
    • 调用willChangeValueForKey
    • 调用imp
    • 调用didChangeValueForKey
  • 返回NO
    • 调用imp

5.2.1 didChangeValueForKey

image.png

  • notifyForKey:发送通知

5.2.2 notifyForKey

image.png

  • 让观察者触发相应,触发KVOobserveValueForKeyPath回调

5.3 removeObserver

image.png

  • 通过哈希表找到pathInfo
  • pathInfo不为空,获取到observer
  • 开始移除观察者

对于isa的处理,重写GSKVOBasedealloc方法

  1. - (void) dealloc
  2. {
  3. // Turn off KVO for self ... then call the real dealloc implementation.
  4. [self setObservationInfo: nil];
  5. object_setClass(self, [self class]);
  6. [self dealloc];
  7. GSNOSUPERDEALLOC;
  8. }