1

introduce

KVO full name of KeyValueObserving , is an event notification mechanism provided by Apple. Allows an object to listen for changes in a specific property of another object and receive events when it changes. Due to KVO , it has an effect on attributes. Generally, objects NSObject KVO default.

KVO and NSNotificationCenter are both an implementation of the observer mode in iOS The difference is that, with respect to the relationship between the observer and the observer, KVO is one-to-one, not one-to-many. KVO is not intrusive to the monitored object, and the monitoring can be realized without manually modifying its internal code.

KVO can monitor the changes of a single attribute, and can also monitor the changes of a collection of objects. The proxy object is obtained through the mutableArrayValueForKey: KVC of 06198dc4e86c9a. When the internal object of the proxy object changes, the monitoring method of KVO Collection objects include NSArray and NSSet .

use

Using KVO divided into three steps

  1. Register an observer through the addObserver:forKeyPath:options:context: method, and the observer can receive the keyPath property change event of 06198dc4e86d10.
  2. observeValueForKeyPath:ofObject:change:context: method in the observer. When the attribute of keyPath KVO will call back this method to notify the observer.
  3. When the observer does not need to listen, you can call the removeObserver:forKeyPath: method to remove KVO It should be noted that calling removeObserver needs to be before the observer disappears, otherwise it will cause Crash .

register

When registering an observer, you can pass in the options parameter, which is an enumerated type. If the incoming NSKeyValueObservingOptionNew and NSKeyValueObservingOptionOld means to receive the new value and the old value, the default is to receive only the new value. If you want to receive a callback immediately after registering the observer, you can join the NSKeyValueObservingOptionInitial enumeration.

You can also context This object can be received in the code that receives the message callback. It is a value-passing method KVO

After calling the addObserver method, KVO does not make a strong reference to the observer. So we need to pay attention to the life cycle of the observer, otherwise it will cause Crash brought by the observer being released.

monitor

Observers need to implement the observeValueForKeyPath:ofObject:change:context: method. KVO event arrives. If it is not implemented, it will result in Crash . change dictionary stores the value related to the KVO attribute, which is returned according to the enumeration passed in at options The enumeration will correspond to the corresponding key to retrieve the value from the dictionary. For example, there is a NSKeyValueChangeOldKey field to store the old value before the change.

change there NSKeyValueChangeKindKey field, and NSKeyValueChangeOldKey relationship same level, to provide information on this change, the corresponding NSKeyValueChange enumerated type value . For example, when the observed attribute changes, the field is NSKeyValueChangeSetting .

If the object is a collection of objects to be observed, in NSKeyValueChangeKindKey field contains NSKeyValueChangeInsertion , NSKeyValueChangeRemoval , NSKeyValueChangeReplacement information indicating the operation mode set object.

Other trigger methods

When calling the KVO attribute object, not only can it be called set KVO compatible with many calling methods.

// 直接调用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];

Practical application

KVO mainly used for key value observation operations. If you want to notify another object after a value changes, it is most suitable to KVO There is a classic case in the iOS tutorial of Stanford University, which communicates between Model and Controller KVO

trigger

Active trigger

KVO when the attribute changes is automatic. If you want to manually control the call timing, or want to implement KVO attribute yourself, you can call it through the method provided KVO

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

We can see a call KVO rely mainly on two methods, called before the property changes willChangeValueForKey: method, called after the change didChangeValueForKey: method. However, if you do not call willChangeValueForKey , directly calling didChangeValueForKey does not take effect. The two are in order and need to be paired.

Disable KVO

If you want to prohibit the KVO a certain attribute, for example, if the key information does not want to be SDK by the KVO , you can use the automaticallyNotifiesObserversForKey method to return NO to prohibit other places from performing KVO on this attribute. If the method returns YES it means it can be called. If it returns NO it means it can’t be called. This method is a class method, you can judge keyPath inside the method to choose whether this attribute is allowed to be KVO .

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

KVC trigger

KVC to KVO special compatible, when by KVC when calling an instance variable of non-property, KVC interior will trigger KVO callback, and by NSKeyValueDidChange and NSKeyValueWillChange callback upward.

The following ignores main function upwards, and only retains the critical stack. This is the KVO stack called back by calling the property setter

* 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:84
frame #1: 0x000000010327e820 Foundation`NSKeyValueNotifyObserver + 349
frame #2: 0x000000010327e0d7 Foundation`NSKeyValueDidChange + 483
frame #3: 0x000000010335f22b Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 778
frame #4: 0x000000010324b1b4 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 61
frame #5: 0x00000001032a7b79 Foundation`_NSSetObjectValueAndNotify + 255
frame #6: 0x0000000101bc3937 TestKVO`::-[ViewController viewDidLoad](self=0x00007f8419705890, _cmd="viewDidLoad") at ViewController.mm:70

This is done by KVC callback trigger up, you can see the normal way by modifying the properties of the trigger KVO , and by KVC triggered KVO there are differences. KVC by way of KVO , even without the call of _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:84
frame #1: 0x000000010886d820 Foundation`NSKeyValueNotifyObserver + 349
frame #2: 0x000000010886d0d7 Foundation`NSKeyValueDidChange + 483
frame #3: 0x000000010894d422 Foundation`NSKeyValueDidChangeWithPerThreadPendingNotifications + 148
frame #4: 0x0000000108879b47 Foundation`-[NSObject(NSKeyValueCoding) setValue:forKey:] + 292
frame #5: 0x0000000106be19aa TestKVO`::-[ViewController viewDidLoad](self=0x00007fe68ac07710, _cmd="viewDidLoad") at ViewController.mm:70

Realization principle

Core logic

KVO is achieved through isa-swizzling technology, which is the KVO implementation of the entire 06198dc4e870fe. At runtime, create an intermediate class based on the original class, which is a subclass of the original class, and dynamically modify the isa current object to point to the intermediate class. And class overridden methods, return to the original class Class . Apple rewrites the class method to shield the existence of intermediate classes.

Therefore, Apple recommends that you should not rely on the isa pointer in development, but class instance method to avoid being KVO by 06198dc4e87120 or other runtime methods.

_NSSetObjectValueAndNotify

Then modifies the intermediate category corresponding set method and inserted willChangeValueForkey methods and didChangeValueForKey method, call the parent class in the middle of two methods set method. In this process, the system encapsulates it into the _NSSetObjectValueAndNotify function. By viewing the assembly code of this function, you can see the call willChangeValueForkey method and the didChangeValueForKey

The system does not only encapsulate the _NSSetObjectValueAndNotify function, but calls different functions according to the attribute type. If it is the Int type, it will call _NSSetIntValueAndNotify . These implementations are defined in the Foundation framework. Specifically, you can view the implementation of the Foundation hopper

runtime will replace the implementation of the setObject method of the newly generated NSKVONotifying_KVOTest _NSSetObjectValueAndNotify function instead of rewriting the setObject function. Through the following test code, you can view the selector corresponding to IMP , and print out its realized address.

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

For the system to achieve KVO principle, it is possible to object_setClass break point, or to objc_allocateClassPair method can also break point, these two methods are class methods must take to create. Through the assembly stack of these two methods, backtracking forward. Then, you can get the following assembly code after translation.

You can see that there are some class name splicing rules, and then create a new class based on the class name. If newCls is empty, it has already been created, or it may be empty. If newCls not empty, register the newly created class and set some parameters of the 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;
}

verify

In order to verify KVO , we add the following test code. First create a KVOObject class, and add two properties in it, then rewrite the description method, and print some key parameters inside.

It should be noted that in order to verify KVO at runtime, I printed the object's class method, and obtained the object's class and parent class runtime Before and after adding KVO monitor, print it once and observe what the system does.

@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 @"";
}

Creating a KVOObject object, KVO key information objects are printed front and back, to see KVO what happens before and after.

self.object = [[KVOObject alloc] init];
[self.object description];

[self.object addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

[self.object description];

The following is the key information printed before and after KVO

We found that KVO , its real type became NSKVONotifying_KVOObject , which is no longer the previous class. KVO will dynamically create a new class at runtime, isa object to the newly created class, and superClass to the original class KVOObject . The newly created class naming rule is in the format NSKVONotifying_xxx KVO order to make it more like the previous class, but also the object class example method rewritten, it is more like the original class.

Add KVO after, due to the modification of setName method and setAge methods IMP , so print these two methods IMP , also a new address, new implementations in NSKVONotifying_KVOObject in.

This implementation is not intrusive to the business code. It can monitor a single object and modify its method implementation KVOObject KVO callback when assigning values.

_isKVOA method was also found in the above code. This method can be used as KVO , and the system may also use this method. If we want to determine whether the current class is a KVO , we can search for this method from the method list.

// 第一次
object address : 0x604000239340
object setName: IMP 0x10ddc2770 object setAge: IMP 0x10ddc27d0
objectMethodClass : KVOObject, ObjectRuntimeClass : KVOObject, superClass : NSObject
object method list
method Name = .cxx_destruct
method Name = description
method Name = name
method Name = setName:
method Name = setAge:
method Name = age

// 第二次
object address : 0x604000239340
object setName: IMP 0x10ea8defe object setAge: IMP 0x10ea94106
objectMethodClass : KVOObject, ObjectRuntimeClass : NSKVONotifying_KVOObject, superClass : KVOObject
object method list
method Name = setAge:
method Name = setName:
method Name = class
method Name = dealloc
method Name = _isKVOA

object_getClass

Why the above calls runtime of object_getClass function, you can get to the real class it?

Call object_getClass after the function which returns a Class type, Class is objc_class defined a typedef alias by objc_class can get to the subject isa pointer Class , i.e. an object class object.

From this we can know that the object_getClass function internally returns the object's isa pointer.

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
}

be careful

Crash

KVO of addObserver and removeObserver need to be paired, if repeated remove will lead NSRangeException type of Crash , if you forget remove will be received again after the release of the observer KVO callback Crash .

Apple's official recommended way is, in init carried out when addObserver in dealloc when removeObserver , so you can ensure add and remove are paired, is an ideal use.

Error checking

If a wrong keyPath is passed in, there will be no error message. When calling KVO , you need to pass in a keyPath . Because keyPath is in the form of a string, if the attribute name is changed, the string does not change, which will easily lead to Crash . For this problem, we can use the reflection mechanism of the system to reflect keyPath , so that the compiler can perform a legality check @selector()

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

Cannot trigger callback

Due to KVO , if a member variable is called for assignment, KVO will not be triggered.

@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];

However, if the assignment operation is called KVC , the callback method of KVO This is because KVC to KVO a separate compatible, in KVC internal assignment method, manually invoke the willChangeValueForKey: and didChangeValueForKey: method.

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

Add repeatedly

Repeating addObserver on KVO will not cause a crash, but there will be a problem of 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] test
2018-08-03 11:48:52.975102+0800 KVOTest[5846:412257] test
2018-08-03 11:48:53.547145+0800 KVOTest[5846:412257] test
2018-08-03 11:48:54.087171+0800 KVOTest[5846:412257] test
2018-08-03 11:48:54.649244+0800 KVOTest[5846:412257] test

Through the above test code, and print object corresponding to Class in the callback, it can be seen that the subclass will not be created repeatedly, and it is always a class. Although repeating addobserver will not crash immediately, it will crash immediately when removeObserver is called for the first time after repeated addition. From the perspective of the crash stack, it is the same as the problem of repeated removal, which is an exception that the system actively throws.

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.'

Duplicate removal

KVO does not allow keyPath . Repeated removal will cause a crash. For example, the following test code.

[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"];

After executing the above test code, the following crash message will be caused. From KVO can see the collapse of the stack, the system in order to achieve KVO of addObserver and removeObserver , as NSObject added a named NSKeyValueObserverRegistration of Category , KVO of addObserver and removeObserver implemented in it.

Removing KVO when listening, the system will determine the current KVO of keyPath has already been removed, if it has been removed, take the initiative to throw a NSException exception.

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

Check the link

KVO is an implementation of an event binding mechanism. After keyPath changes, the corresponding method will be called back. bug which is not easy to investigate when the object relationship is very complicated. For example, the keyPath which the attribute corresponding to 06198dc4e876b5 is called is very complicated, so it is not recommended to use KVO for this attribute.

Implement KVO by yourself

In addition to the above shortcomings, KVO does not support the block syntax, and the parent method needs to be rewritten separately. Adding the add and remove methods will result in very scattered code. Therefore, I simply implemented a KVO runtime , and the source code is placed on my Github , called 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)];

The calling code is very simple. You can directly add the monitoring of KVO lxz_addObserver:originalSelector:callback: method, and you can receive the callback after the attribute changes callback of block And the method keyPath receives a SEL type 06198dc4e87741, so the @selector() when the parameter is passed in through 06198dc4e87742. If it is an unimplemented method, it will directly report a warning.

Pass the observer and keyPath lxz_removeObserver:originalSelector: method, and remove the observer object KVO when all keyPath observer is removed.

If you repeat addObserver and removeObserver it’s okay, there is internal judgment logic. EasyKVO internally weak , which will not affect the life cycle of the observer, and will not cause Crash after the observer is released. One add method call corresponds to one block . If the observer monitors multiple keyPath attributes, there is no need to judge keyPath block callback.

KVOController

Want to project a safe and convenient use KVO then recommend Facebook one KVO open source third-party frameworks KVOController . KVOController is essentially the system KVO package, with native KVO all the features, but also to avoid the native KVO a lot of problems, compatible block and action two kinds of callbacks.

Source code analysis

From the point of view source code is relatively simple, mainly divided into NSObject of Category and FBKVOController two parts.

In Category provided in the KVOController and KVOControllerNonRetaining two properties have a first name suggests observer generating strong reference, the second is not. Its internal code is to create FBKVOController code objects, and create objects out assigned to Category properties directly through this Category can lazily create FBKVOController object.

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

Realization principle

FBKVOController is divided into three parts in _FBKVOInfo , 06198dc4e878bc is a private class, the function of this class is very simple, it is to save FBKVOController in a structured form, similar to the function of the model class.

There is also a private class _FBKVOSharedController , which is the key to the implementation of the FBKVOController It can be seen from the naming that it is a singleton, all the FBKVOController implemented by KVO , the observer is it. Every time FBKVOController add a KVO through 06198dc4e878ef, _FBKVOSharedController will set itself as an observer, and implement the observeValueForKeyPath:ofObject:change:context: method inside it, and forward the received message through block or action .

Its function is very simple, add KVO observe:info: method, and use a NSHashTable save the _FBKVOInfo information. Use the unobserve:info: method to remove the monitor, and NSHashTable the corresponding _FBKVOInfo from 06198dc4e87923. Both methods will call the system's KVO method internally.

For external use, the FBKVOController class is required, which internally implements the operations of initialization and adding and removing monitors. After calling the add listener method, a _FBKVOInfo object will be created internally and held by a NSMapTable object, and then _FBKVOSharedController will be called to register and listen.

Use FBKVOController words, do not need to manually call removeObserver method, when the listener objects disappear, will dealloc call remove method. If because of business needs, you can manually call the remove method. Repeated calls to the remove method will not cause problems.

- (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];
}

Because FBKVOController is very simple, so here is a very simple talk, the specific implementation can go to Github download the source code and analyze it carefully.


刘小壮
396 声望820 粉丝

向着星辰大海的征途,去到那别人连梦想都未曾抵达的地方!