介绍

KVO全称KeyValueObserving,是苹果提供的一套事件告诉机制。容许对象监听另一个对象特定属性的扭转,并在扭转时接管到事件。因为KVO的实现机制,所以对属性才会产生作用,个别继承自NSObject的对象都默认反对KVO

KVONSNotificationCenter都是iOS中观察者模式的一种实现。区别在于,绝对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。KVO对被监听对象无侵入性,不须要手动批改其外部代码即可实现监听。

KVO能够监听单个属性的变动,也能够监听汇合对象的变动。通过KVCmutableArrayValueForKey:等办法取得代理对象,当代理对象的外部对象产生扭转时,会回调KVO监听的办法。汇合对象蕴含NSArrayNSSet

应用

应用KVO分为三个步骤

  1. 通过addObserver:forKeyPath:options:context:办法注册观察者,观察者能够接管keyPath属性的变动事件回调。
  2. 在观察者中实现observeValueForKeyPath:ofObject:change:context:办法,当keyPath属性产生扭转后,KVO会回调这个办法来告诉观察者。
  3. 当观察者不须要监听时,能够调用removeObserver:forKeyPath:办法将KVO移除。须要留神的是,调用removeObserver须要在观察者隐没之前,否则会导致Crash

注册

在注册观察者时,能够传入options参数,参数是一个枚举类型。如果传入NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld示意接管新值和旧值,默认为只接管新值。如果想在注册观察者后,立刻接管一次回调,则能够退出NSKeyValueObservingOptionInitial枚举。

还能够通过办法context传入任意类型的对象,在接管音讯回调的代码中能够接管到这个对象,是KVO中的一种传值形式。

在调用addObserver办法后,KVO并不会对观察者进行强援用。所以须要留神观察者的生命周期,否则会导致观察者被开释带来的Crash

监听

观察者须要实现observeValueForKeyPath:ofObject:change:context:办法,当KVO事件到来时会调用这个办法,如果没有实现会导致Crashchange字典中寄存KVO属性相干的值,依据options时传入的枚举来返回。枚举会对应相应key来从字典中取出值,例如有NSKeyValueChangeOldKey字段,存储扭转之前的旧值。

change中还有NSKeyValueChangeKindKey字段,和NSKeyValueChangeOldKey是平级的关系,来提供本次更改的信息,对应NSKeyValueChange枚举类型的value。例如被察看属性产生扭转时,字段为NSKeyValueChangeSetting

如果被察看对象是汇合对象,在NSKeyValueChangeKindKey字段中会蕴含NSKeyValueChangeInsertionNSKeyValueChangeRemovalNSKeyValueChangeReplacement的信息,示意汇合对象的操作形式。

其余触发办法

调用KVO属性对象时,不仅能够通过点语法和set语法进行调用,KVO兼容很多种调用形式。

// 间接调用set办法,或者通过属性的点语法间接调用[account setName:@"Savings"];// 应用KVC的setValue:forKey:办法[account setValue:@"Savings" forKey:@"name"];// 应用KVC的setValue:forKeyPath:办法[document setValue:@"Savings" forKeyPath:@"account.name"];// 通过mutableArrayValueForKey:办法获取到代理对象,并应用代理对象进行操作Transaction *newTransaction = <#Create a new transaction for the account#>;NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];[transactions addObject:newTransaction];

理论利用

KVO次要用来做键值察看操作,想要一个值产生扭转后告诉另一个对象,则用KVO实现最为适合。斯坦福大学的iOS教程中有一个很经典的案例,通过KVOModelController之间进行通信。

触发

被动触发

KVO在属性产生扭转时的调用是主动的,如果想要手动管制这个调用机会,或想本人实现KVO属性的调用,则能够通过KVO提供的办法进行调用。

- (void)setBalance:(double)theBalance {    if (theBalance != _balance) {        [self willChangeValueForKey:@"balance"];        _balance = theBalance;        [self didChangeValueForKey:@"balance"];    }}

能够看到调用KVO次要依附两个办法,在属性产生扭转之前调用willChangeValueForKey:办法,在产生扭转之后调用didChangeValueForKey:办法。然而,如果不调用willChangeValueForKey,间接调用didChangeValueForKey是不失效的,二者有先后顺序并且须要成对呈现。

禁用KVO

如果想禁止某个属性的KVO,例如要害信息不想被三方SDK通过KVO的形式获取,能够通过automaticallyNotifiesObserversForKey办法返回NO来禁止其余中央对这个属性进行KVO。办法返回YES则示意能够调用,如果返回NO则示意不能够调用。此办法是一个类办法,能够在办法外部判断keyPath,来抉择这个属性是否容许被KVO

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {    BOOL automatic = NO;    if ([theKey isEqualToString:@"balance"]) {        automatic = NO;    }    else {        automatic = [super automaticallyNotifiesObserversForKey:theKey];    }    return automatic;}

KVC触发

KVCKVO有非凡兼容,当通过KVC调用非属性的实例变量时,KVC外部也会触发KVO的回调,并通过NSKeyValueDidChangeNSKeyValueWillChange向上回调。

上面疏忽main函数向上的零碎函数,只保留要害堆栈。这是通过调用属性setter办法的形式回调的KVO堆栈。

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 38.1* frame #0: 0x0000000101bc3a15 TestKVO`::-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007f8419705890, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath="object", object=0x0000604000015b00, change=0x0000608000265540, context=0x0000000000000000) at ViewController.mm:84frame #1: 0x000000010327e820 Foundation`NSKeyValueNotifyObserver + 349frame #2: 0x000000010327e0d7 Foundation`NSKeyValueDidChange + 483frame #3: 0x000000010335f22b Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 778frame #4: 0x000000010324b1b4 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 61frame #5: 0x00000001032a7b79 Foundation`_NSSetObjectValueAndNotify + 255frame #6: 0x0000000101bc3937 TestKVO`::-[ViewController viewDidLoad](self=0x00007f8419705890, _cmd="viewDidLoad") at ViewController.mm:70

这是通过KVC触发的向上回调,能够看到失常通过批改属性的形式触发KVO,和通过KVC触发的KVO还是有区别的。通过KVC的形式触发KVO,甚至都没有_NSSetObjectValueAndNotify的调用。

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 37.1* frame #0: 0x0000000106be1a85 TestKVO`::-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007fe68ac07710, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath="object", object=0x0000600000010c80, change=0x000060c000262780, context=0x0000000000000000) at ViewController.mm:84frame #1: 0x000000010886d820 Foundation`NSKeyValueNotifyObserver + 349frame #2: 0x000000010886d0d7 Foundation`NSKeyValueDidChange + 483frame #3: 0x000000010894d422 Foundation`NSKeyValueDidChangeWithPerThreadPendingNotifications + 148frame #4: 0x0000000108879b47 Foundation`-[NSObject(NSKeyValueCoding) setValue:forKey:] + 292frame #5: 0x0000000106be19aa TestKVO`::-[ViewController viewDidLoad](self=0x00007fe68ac07710, _cmd="viewDidLoad") at ViewController.mm:70

实现原理

外围逻辑

KVO是通过isa-swizzling技术实现的,这是整个KVO实现的重点。在运行时依据原类创立一个两头类,这个两头类是原类的子类,并动静批改以后对象的isa指向两头类。并且将class办法重写,返回原类的Class。苹果重写class办法,就是为了屏蔽两头类的存在。

所以,苹果倡议在开发中不应该依赖isa指针,而是通过class实例办法来获取对象类型,来防止被KVO或者其余runtime办法影响。

_NSSetObjectValueAndNotify

随后会批改两头类对应的set办法,并且插入willChangeValueForkey办法以及didChangeValueForKey办法,在两个办法两头调用父类的set办法。这个过程,零碎将其封装到_NSSetObjectValueAndNotify函数中。通过查看这个函数的汇编代码,能够看到外部封装的willChangeValueForkey办法和didChangeValueForKey办法的调用。

零碎并不是只封装了_NSSetObjectValueAndNotify函数,而是会依据属性类型,调用不同的函数。如果是Int类型就会调用_NSSetIntValueAndNotify,这些实现都定义在Foundation框架中。具体的能够通过hopper来查看Foundation框架的实现。

runtime会将新生成的NSKVONotifying_KVOTestsetObject办法的实现,替换成_NSSetObjectValueAndNotify函数,而不是重写setObject函数。通过上面的测试代码,能够查看selector对应的IMP,并且将其实现的地址打印进去。

KVOTest *test = [[KVOTest alloc] init];[test setObject:[[NSObject alloc] init]];NSLog(@"%p", [test methodForSelector:@selector(setObject:)]);[test addObserver:self forKeyPath:@"object" options:NSKeyValueObservingOptionNew context:nil];[test setObject:[[NSObject alloc] init]];NSLog(@"%p", [test methodForSelector:@selector(setObject:)]);// 打印后果,第一次的办法地址为0x100c8e270,第二次的办法地址为0x7fff207a3203(lldb) p (IMP)0x100c8e270(IMP) $0 = 0x0000000100c8e270 (DemoProject`-[KVOTest setObject:] at KVOTest.h:11)(lldb) p (IMP)0x7fff207a3203(IMP) $1 = 0x00007fff207a3203 (Foundation`_NSSetObjectValueAndNotify)

_NSKVONotifyingCreateInfoWithOriginalClass

对于零碎实现KVO的原理,能够对object_setClass打断点,或者对objc_allocateClassPair办法打断点也能够,这两个办法都是创立类必走的办法。通过这两个办法的汇编堆栈,向前回溯。随后,能够失去翻译后如下的汇编代码。

能够看到有一些类名拼接规定,随后依据类名创立新类。如果newCls为空则曾经创立过,或者可能为空。如果newCls不为空,则注册新创建的类,并且设置SDTestKVOClassIndexedIvars构造体的一些参数。

Class _NSKVONotifyingCreateInfoWithOriginalClass(Class originalClass) {    const char *clsName = class_getName(originalClass);    size_t len = strlen(clsName);    len += 0x10;    char *newClsName = malloc(len);    const char *prefix = "NSKVONotifying_";    __strlcpy_chk(newClsName, prefix, len);    __strlcat_chk(newClsName, clsName, len, -1);    Class newCls = objc_allocateClassPair(originalClass, newClsName, 0x68);    if (newCls) {        objc_registerClassPair(newCls);        SDTestKVOClassIndexedIvars *indexedIvars = object_getIndexedIvars(newCls);        indexedIvars->originalClass = originalClass;        indexedIvars->KVOClass = newCls;        CFMutableSetRef mset = CFSetCreateMutable(nil, 0, kCFCopyStringSetCallBacks);        indexedIvars->mset = mset;        CFMutableDictionaryRef mdict = CFDictionaryCreateMutable(nil, 0, nil, kCFTypeDictionaryValueCallBacks);        indexedIvars->mdict = mdict;        pthread_mutex_init(indexedIvars->lock);        static dispatch_once_t onceToken;        dispatch_once(&onceToken, ^{            bool flag = true;            IMP willChangeValueForKeyImp = class_getMethodImplementation(indexedIvars->originalClass, @selector(willChangeValueForKey:));            IMP didChangeValueForKeyImp = class_getMethodImplementation(indexedIvars->originalClass, @selector(didChangeValueForKey:));            if (willChangeValueForKeyImp == _NSKVONotifyingCreateInfoWithOriginalClass.NSObjectWillChange && didChangeValueForKeyImp == _NSKVONotifyingCreateInfoWithOriginalClass.NSObjectDidChange) {                flag = false;            }            indexedIvars->flag = flag;            NSKVONotifyingSetMethodImplementation(indexedIvars, @selector(_isKVOA), NSKVOIsAutonotifying, nil);            NSKVONotifyingSetMethodImplementation(indexedIvars, @selector(dealloc), NSKVODeallocate, nil);            NSKVONotifyingSetMethodImplementation(indexedIvars, @selector(class), NSKVOClass, nil);        });    } else {        return nil;    }    return newCls;}

验证

为了验证KVO的实现形式,咱们退出上面的测试代码。首先创立一个KVOObject类,并在外面退出两个属性,而后重写description办法,并在外部打印一些要害参数。

须要留神的是,为了验证KVO在运行时做了什么,我打印了对象的class办法,以及通过runtime获取对象的类和父类。在增加KVO监听前后,都打印一次,察看零碎做了什么。

@interface KVOObject : NSObject@property (nonatomic, copy  ) NSString *name;@property (nonatomic, assign) NSInteger age;@end- (NSString *)description {    IMP nameIMP = class_getMethodImplementation(object_getClass(self), @selector(setName:));    IMP ageIMP = class_getMethodImplementation(object_getClass(self), @selector(setAge:));    NSLog(@"object setName: IMP %p object setAge: IMP %p \n", nameIMP, ageIMP);        Class objectMethodClass = [self class];    Class objectRuntimeClass = object_getClass(self);    Class superClass = class_getSuperclass(objectRuntimeClass);    NSLog(@"objectMethodClass : %@, ObjectRuntimeClass : %@, superClass : %@ \n", objectMethodClass, objectRuntimeClass, superClass);        NSLog(@"object method list \n");    unsigned int count;    Method *methodList = class_copyMethodList(objectRuntimeClass, &count);    for (NSInteger i = 0; i < count; i++) {        Method method = methodList[i];        NSString *methodName = NSStringFromSelector(method_getName(method));        NSLog(@"method Name = %@\n", methodName);    }        return @"";}

创立一个KVOObject对象,在KVO前后别离打印对象的要害信息,看KVO前后有什么变动。

self.object = [[KVOObject alloc] init];[self.object description];[self.object addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];[self.object description];

上面是KVO前后打印的要害信息。

咱们发现对象被KVO后,其真正类型变为了NSKVONotifying_KVOObject类,曾经不是之前的类了。KVO会在运行时动态创建一个新类,将对象的isa指向新创建的类,并且将superClass指向原来的类KVOObject,新创建的类命名规定是NSKVONotifying_xxx的格局。KVO为了使其更像之前的类,还会将对象的class实例办法重写,使其更像原类。

增加KVO之后,因为批改了setName办法和setAge办法的IMP,所以打印这两个办法的IMP,也是一个新的地址,新的实现在NSKVONotifying_KVOObject中。

这种实现形式对业务代码没有侵入性,能够在不影响KVOObject其余对象的前提下,对单个对象进行监听并批改其办法实现,在赋值时触发KVO回调。

在下面的代码中还发现了_isKVOA办法,这个办法能够当做应用了KVO的一个标记,零碎可能也是这么用的。如果咱们想判断以后类是否是KVO动静生成的类,就能够从办法列表中搜寻这个办法。

// 第一次object address : 0x604000239340object setName: IMP 0x10ddc2770 object setAge: IMP 0x10ddc27d0objectMethodClass : KVOObject, ObjectRuntimeClass : KVOObject, superClass : NSObjectobject method listmethod Name = .cxx_destructmethod Name = descriptionmethod Name = namemethod Name = setName:method Name = setAge:method Name = age// 第二次object address : 0x604000239340object setName: IMP 0x10ea8defe object setAge: IMP 0x10ea94106objectMethodClass : KVOObject, ObjectRuntimeClass : NSKVONotifying_KVOObject, superClass : KVOObjectobject method listmethod Name = setAge:method Name = setName:method Name = classmethod Name = deallocmethod Name = _isKVOA

object_getClass

为什么下面调用runtimeobject_getClass函数,就能够获取到真正的类呢?

调用object_getClass函数后其返回的是一个Class类型,Classobjc_class定义的一个typedef别名,通过objc_class就能够获取到对象的isa指针指向的Class,也就是对象的类对象。

由此能够晓得,object_getClass函数外部返回的是对象的isa指针。

typedef struct objc_class *Class;struct objc_class {    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__    Class _Nullable super_class                              OBJC2_UNAVAILABLE;    const char * _Nonnull name                               OBJC2_UNAVAILABLE;    long version                                             OBJC2_UNAVAILABLE;    long info                                                OBJC2_UNAVAILABLE;    long instance_size                                       OBJC2_UNAVAILABLE;    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;#endif}

留神点

Crash

KVOaddObserverremoveObserver须要是成对的,如果反复remove则会导致NSRangeException类型的Crash,如果遗记remove则会在观察者开释后再次接管到KVO回调时Crash

苹果官网举荐的形式是,在init的时候进行addObserver,在deallocremoveObserver,这样能够保障addremove是成对呈现的,是一种比拟现实的应用形式。

谬误查看

如果传入一个谬误的keyPath并不会有谬误提醒。在调用KVO时须要传入一个keyPath,因为keyPath是字符串的模式,如果属性名产生扭转后,字符串没有扭转容易导致Crash。对于这个问题,咱们能够利用零碎的反射机制将keyPath反射进去,这样编译器能够在@selector()中进行合法性检查。

NSString *keyPath = NSStringFromSelector(@selector(isFinished));

不能触发回调

因为KVO的实现机制,如果调用成员变量进行赋值,是不会触发KVO的。

@interface TestObject : NSObject {    @public    NSObject *object;}@end// 谬误的调用形式self.object = [[TestObject alloc] init];[self.object addObserver:self forKeyPath:@"object" options:NSKeyValueObservingOptionNew context:nil];self.object->object = [[NSObject alloc] init];

然而,如果通过KVC的形式调用赋值操作,则会触发KVO的回调办法。这是因为KVCKVO有独自的兼容,在KVC的赋值办法外部,手动调用了willChangeValueForKey:didChangeValueForKey:办法。

// KVC的形式调用self.object = [[TestObject alloc] init];[self.object addObserver:self forKeyPath:@"object" options:NSKeyValueObservingOptionNew context:nil];[self.object setValue:[[NSObject alloc] init] forKey:@"object"];

反复增加

KVO进行反复addObserver并不会导致解体,然而会呈现反复执行KVO回调办法的问题。

[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];self.testLabel.text = @"test";[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];self.testLabel.text = @"test";// 输入2018-08-03 11:48:49.502450+0800 KVOTest[5846:412257] test2018-08-03 11:48:52.975102+0800 KVOTest[5846:412257] test2018-08-03 11:48:53.547145+0800 KVOTest[5846:412257] test2018-08-03 11:48:54.087171+0800 KVOTest[5846:412257] test2018-08-03 11:48:54.649244+0800 KVOTest[5846:412257] test

通过下面的测试代码,并且在回调中打印object所对应的Class来看,并不会反复创立子类,始终都是一个类。尽管反复addobserver不会立即解体,然而反复增加后在第一次调用removeObserver时,就会立即解体。从解体堆栈来看,和反复移除的问题一样,都是零碎被动抛出的异样。

Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <UILabel 0x7f859b547490> for the key path "text" from <UILabel 0x7f859b547490> because it is not registered as an observer.'

反复移除

KVO是不容许对一个keyPath进行反复移除的,如果反复移除,则会导致解体。例如上面的测试代码。

[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];self.testLabel.text = @"test";[self.testLabel removeObserver:self forKeyPath:@"text"];[self.testLabel removeObserver:self forKeyPath:@"text"];[self.testLabel removeObserver:self forKeyPath:@"text"];

执行下面的测试代码后,会造成上面的解体信息。从KVO的解体堆栈能够看进去,零碎为了实现KVOaddObserverremoveObserver,为NSObject增加了一个名为NSKeyValueObserverRegistrationCategoryKVOaddObserverremoveObserver的实现都在外面。

在移除KVO的监听时,零碎会判断以后KVOkeyPath是否曾经被移除,如果曾经被移除,则被动抛出一个NSException的异样。

2018-08-03 10:54:27.477379+0800 KVOTest[4939:286991] *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x7ff6aee31600> for the key path "text" from <UILabel 0x7ff6aee2e850> because it is not registered as an observer.'*** First throw call stack:(    0   CoreFoundation                      0x000000010db2312b __exceptionPreprocess + 171    1   libobjc.A.dylib                     0x000000010cc6af41 objc_exception_throw + 48    2   CoreFoundation                      0x000000010db98245 +[NSException raise:format:] + 197    3   Foundation                          0x0000000108631f15 -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:] + 497    4   Foundation                          0x0000000108631ccb -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:] + 84    5   KVOTest                             0x0000000107959a55 -[ViewController viewDidAppear:] + 373    // .....    20  UIKit                               0x000000010996d5d6 UIApplicationMain + 159    21  KVOTest                             0x00000001079696cf main + 111    22  libdyld.dylib                       0x000000010fb43d81 start + 1)libc++abi.dylib: terminating with uncaught exception of type NSException

排查链路

KVO是一种事件绑定机制的实现,在keyPath对应的值产生扭转后会回调对应的办法。这种数据绑定机制,在对象关系很简单的状况下,很容易导致不好排查的bug。例如keyPath对应的属性被调用的关系很简单,就不太倡议对这个属性进行KVO

本人实现KVO

除了下面的毛病,KVO还不反对block语法,须要独自重写父类办法,这样加上addremove办法就会导致代码很扩散。所以,我通过runtime简略的实现了一个KVO,源码放在我的Github上,叫做EasyKVO。

self.object = [[KVOObject alloc] init];[self.object lxz_addObserver:self originalSelector:@selector(name) callback:^(id observedObject, NSString *observedKey, id oldValue, id newValue) {    // 解决业务逻辑}];self.object.name = @"lxz";// 移除告诉[self.object lxz_removeObserver:self originalSelector:@selector(name)];

调用代码很简略,间接通过lxz_addObserver:originalSelector:callback:办法就能够增加KVO的监听,能够通过callbackblock接管属性产生扭转后的回调。而且办法的keyPath接管的是一个SEL类型参数,所以能够通过@selector()传入参数时进行办法合法性检查,如果是未实现的办法间接就会报正告。

通过lxz_removeObserver:originalSelector:办法传入观察者和keyPath,当观察者所有keyPath都移除后则从KVO中移除观察者对象。

如果反复addObserverremoveObserver也没事,外部有判断逻辑。EasyKVO外部通过weak对观察者做援用,并不会影响观察者的生命周期,并且在观察者开释后不会导致Crash。一次add办法调用对应一个block,如果观察者监听多个keyPath属性,不须要在block回调中判断keyPath

KVOController

想在我的项目中平安便捷的应用KVO的话,举荐Facebook的一个KVO开源第三方框架KVOController。KVOController实质上是对系统KVO的封装,具备原生KVO所有的性能,而且躲避了原生KVO的很多问题,兼容blockaction两种回调形式。

源码剖析

从源码来看还是比较简单的,次要分为NSObjectCategoryFBKVOController两局部。

Category中提供了KVOControllerKVOControllerNonRetaining两个属性,顾名思义第一个会对observer产生强援用,第二个则不会。其外部代码就是创立FBKVOController对象的代码,并将创立进去的对象赋值给Category的属性,间接通过这个Category就能够懒加载创立FBKVOController对象。

- (FBKVOController *)KVOControllerNonRetaining{  id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey);    if (nil == controller) {    controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO];    self.KVOControllerNonRetaining = controller;  }    return controller;}

实现原理

FBKVOController中分为三局部,_FBKVOInfo是一个公有类,这个类的性能很简略,就是以结构化的模式保留FBKVOController所需的各个对象,相似于模型类的性能。

还有一个公有类_FBKVOSharedController,这是FBKVOController框架实现的要害。从命名上能够看出其是一个单例,所有通过FBKVOController实现的KVO,观察者都是它。每次通过FBKVOController增加一个KVO时,_FBKVOSharedController都会将本人设为观察者,并在其外部实现observeValueForKeyPath:ofObject:change:context:办法,将接管到的音讯通过blockaction进行转发。

其性能很简略,通过observe:info:办法增加KVO监听,并用一个NSHashTable保留_FBKVOInfo信息。通过unobserve:info:办法移除监听,并从NSHashTable中将对应的_FBKVOInfo移除。这两个办法外部都会调用零碎的KVO办法。

在外界应用时须要用FBKVOController类,其外部实现了初始化以及增加和移除监听的操作。在调用增加监听办法后,其外部会创立一个_FBKVOInfo对象,并通过一个NSMapTable对象进行持有,而后会调用_FBKVOSharedController来进行注册监听。

应用FBKVOController的话,不须要手动调用removeObserver办法,在被监听对象隐没的时候,会在dealloc中调用remove办法。如果因为业务需要,能够手动调用remove办法,反复调用remove办法不会有问题。

- (void)_observe:(id)object info:(_FBKVOInfo *)info{    NSMutableSet *infos = [_objectInfosMap objectForKey:object];    _FBKVOInfo *existingInfo = [infos member:info];    if (nil != existingInfo) {      return;    }    if (nil == infos) {      infos = [NSMutableSet set];      [_objectInfosMap setObject:infos forKey:object];    }    [infos addObject:info];    [[_FBKVOSharedController sharedController] observe:object info:info];}

因为FBKVOController的实现很简略,所以这里就很简略的讲讲,具体实现能够去Github下载源码仔细分析一下。