KVO

The full name of KVO is key-value Observing, commonly known as “key-value monitoring”, which can be used to monitor the change of an object’s attribute Value

Analysis and verification of KVO underlying implementation principle

The basic use

Print the result

So when assigning person1 and person2, why are only listener object property changes called?

We see that the difference between Person1 and Person2 is that person2 adds listeners. So the guess is that the Person2 instance object has changed.

Person1 isa points to NSKVONotifying_BYPerson and isa points to NSKVONotifying_BYPerson. The isa points to NSKVONotifying_BYPerson.

As we all know, the instance object calls the method to send a message to the object, according to the ISA of the instance object, find the class object, and search the object method in the class object first. If it cannot find the object method, it will find the class object of the parent class according to the superClass pointer, and then query the object method in the class object of the parent class.

Person1 isa

Person2 isa

Note: NSKVONotifying_BYPerson isa derived subclass, which is dynamically generated by the runtime. The isa pointer to NSKVONotifying_BYPerson points to the metaclass of the derived class

Let’s verify what object methods are in the NSKVONotifying_BYPerson class

- (void)printMethodNamesOfClass:(Class) CLS {unsigned int count; Method *methodList = class_copyMethodList(CLS, &count); // Store method name NSMutableString *methodNames = [NSMutableString String]; For (int I = 0; i < count; Method Method = methodList[I]; NSString *methodName = NSStringFromSelector(method_getName(method)); [methodNames appendString:methodName]; [methodNames appendString:@", "]; } // Free (methodList); // Prints the method name NSLog(@"%@ %@", CLS, methodNames); }Copy the code

The NSKVONotifying_BYPerson class has setAge:, class, dealloc, _isKVOA methods

The setAge: method in a derived subclass

According to the result, person2 calls setAge: in the class object NSKVONotifying_BYPerson first

Let’s look at the implementation of the setAge: method of the NSKVONotifying_BYPerson object:

**methodForSelector: The **method is an implementation of the object method that returns the instance object

We see that the “setAge:” method implementation for person1 has not changed, and that person2 is different before and after adding listeners

P (IMP) addressCopy the code

The implementation of the method is printed

Person1 is the setAge: method of the BYPerson class that is normally called, and person2’s “setAge:” method is implemented as the function **_NSSetIntValueAndNotify**

_NSSetIntValueAndNotify Internal implementation

  1. Call willChangeValueForKey to record the old value
  2. Call the original setter method
  3. Call didChangeValueForKey (inside the method will be called the observer observeValueForKeyPath: ofObject: change: the context method)

A class method in a derived child class

- (Class)class {return [BYPerson class]; }Copy the code

A derived child class overrides the class method and returns the class method of the parent class. Why would Apple do this?

I think apple doesn’t want to expose the NSKVONotifying_BYPerson class. It hides the internal implementation, hides the implementation of the derived subclasses, and lets developers focus more on business development. Even if the developer calls the class method, it’s telling the developer BYPerson, which is the parent class, which is correct.

KVOController source code discussion

KVO status quo

  • AddObserver and removeObserver must be paired and only the following uses are supported

    [observe addObserver...] ; // do something [observe removeObserver...] ;Copy the code
  • Most of the time, even though the code has been paired and called in sequence, the software runtime environment is complicated due to multi-threading or the observer is accidentally released prematurely and still finds related crashes.

  • To the same instance attributes of the object to add monitoring for many times, when there is a change in the properties, observeValueForKeyPath: ofObject: change: the context method will be called many times

The advantage of KVOController

  • Provide easy-to-use interfaces

Provides support for block callbacks and custom selectors, keeping it flexible and powerful

  • Provides more secure interfaces

Provides a more secure set of interfaces by addressing the problem that addObserver and removeObserver must be paired and called sequently.

Use singleton active listening to ensure that it is not released during the program declaration cycle.

  • Message filtering _FBKVOInfo *existingInfo = [infos member:info], first check whether the corresponding observer message has been contained, if so, directly return, do not register again.

KVOController process

The key class

_FBKVOInfo: {@public // weak reference to FBKVOController __weak FBKVOController *_controller; // Listener's keyPath NSString *_keyPath; NSKeyValueObservingOptions _options; SEL _action; void *_context; FBKVONotificationBlock _block; FBKVONotificationBlock // The state of this object _FBKVOInfoState _state; }Copy the code

FBKVOController: // NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap; @property (nullable, Nonatomic, weak, readonly) ID Observer;Copy the code

_FBKVOSharedController: NSHashTable<_FBKVOInfo *> *_infos;Copy the code

Step 1 (Basic Use)

// Arrange 1: Create a controller to observe changes to a circle. id<FBKVOTestObserving> observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // Arrange 2: Observe the key paths "radius" and "borderWidth" on the circle. // Aggregate the new values in an array. NSMutableArray *newValues = [NSMutableArray array]; FBKVOTestCircle *circle = [FBKVOTestCircle circle]; // Transmit listener and keyPath to FBKVOController inner [Controller observe:circle keyPath :@[radius, borderWidth] options:NSKeyValueObservingOptionNew block:^(id observer, FBKVOTestCircle *circle, NSDictionary *change) { [newValues addObject:change[NSKeyValueChangeNewKey]]; }];Copy the code

Step 2 (FBKVOController operation)

  1. Create a _FBKVOInfo object based on FBKVOController, keyPath, etc. Add the _FBKVOInfo object to the NSMutableSet with the listener as the key and the corresponding NSMutableSet as the value. Store the mapping between key and value in the objectInfosMap(type NSMapTable) member variable of FBKVOController

  2. When adding the NSMutableSet(value) next time, determine whether the NSMutableSet(value) is created according to the listener (key). If no, re-create the NSMutableSet. If yes, take out the corresponding NSMutableSet and check whether the NSMutableSet contains the keypath creation _FBKVOInfo object. If yes, return. If no, re-create the object. Filter out the same attribute that listens to the same listener repeatedly.

  3. Pass the listener and the generated _FBKVOInfo object to the singleton _FBKVOSharedController

Step 3, (actions in _FBKVOSharedController)

  1. Let the _FBKVOSharedController singleton listen on the listener. Because listeners are singletons, they are not released during the program’s runtime declaration cycle.

  2. The following judgments are made in the listener callback method observeValueForKeyPath

    2.1. Read the corresponding INFO and check whether the _controller held by the INFO is released. If it is released, return

    2.2. Check whether the listener held by _controller is released. If so, return directly

  3. After the above two steps, the block callback is performed. Ensure that the listener, listener, and KVOController are present during the callback