IOS underlying principles + reverse article summary

KVO, or key-value observing, is a mechanism that allows objects to be notified of changes to specified properties of other objects.

KVO is based on KVC, Observing Programming Guide, or KVO

In order to understand key-value observing, you must first understand key-value coding. KVC specifies the key-value coding for an object. After the object is created, you can dynamically assign values to its attributes. KVO is a key-value observation, providing a listening mechanism, when the property of the specified object is modified, the object will be notified, so it can be seen that KVO is based on KVC to monitor the dynamic changes of the property

In the daily development of iOS, KVO is often used to listen for changes in object properties and respond in time. That is, when the properties of the specified observed object are modified, KVO will automatically notify the corresponding observer. So what is the difference between KVO and NSNotificatioCenter?

  • The same
    • 1. Both are implemented in the observer mode and are used for listening

    • 2, can achieve a one-to-many operation

  • The difference between
    • 1, KVO can only be used to listen for changes in object properties, and attribute names are found by NSString, the compiler will not help you to detect errors and completion, pure hand typing will be more error

    • We can control the operation of NSNotification post, kVO is controlled by the system.

    • 3, KVO can record the change of old and new values

Precautions for KVO use

1, basic use

The basic use of KVO is divided into three steps:

  • Registered observeraddObserver:forKeyPath:options:context
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
Copy the code
  • Implement the KVO callbackobserveValueForKeyPath:ofObject:change:context
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"name"]) { NSLog(@"%@",change); }}Copy the code
  • Remove observerremoveObserver:forKeyPath:context
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];
Copy the code

2. Context usage

In the official documentation, forParameters of the contextThere are the following instructions:

The context pointer in the addObserver: forKeyPath: options: context: method contains any data that will be passed back to the observer in the corresponding change notification. You can use the keyPath or keyPath string to determine the source of the change notification by specifying the context as NULL, but this approach can cause problems when the object’s parent class also observes the same keyPath for different reasons. So you can create a different context for each of the observed keyPath, eliminating the need for string comparisons at all and making notification parsing more efficient

Generally speaking, context is used to distinguish the properties of different objects with the same name, which can be directly used in KVO callback methods, which can greatly improve performance and code readability

Summary of Context Usage

  • Instead of using the context, use the keyPath to distinguish the notification source
// The context is of type nullable void *. Rather than nil [self. The person addObserver: self forKeyPath: @ "Nick" options: NSKeyValueObservingOptionNew context: NULL];Copy the code
  • Use context to distinguish notification sources
Static void *PersonNickContext = &PersonNickContext; static void *PersonNameContext = &PersonNameContext; / / registered observers [self. The person addObserver: self forKeyPath: @ "Nick" options: NSKeyValueObservingOptionNew context:PersonNickContext]; [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext]; //KVO callback - (void)observeValueForKeyPath (NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if (context == PersonNickContext) { NSLog(@"%@",change); }else if (context == PersonNameContext){ NSLog(@"%@",change); }}Copy the code

3. Need to remove KVO notifications

In the official documentation, forKVO removeThere are several explanations

When deleting an observer, keep the following in mind:

  • Asking to be removed as an observer (if not already registered as an observer) results in a NSRangeException. You can make a call to removeObserver: forKeyPath: context: to address the addObserver: forKeyPath: options: context: Or, if that’s not feasible in your application, use removeObserver: forKeyPath: context: to handle potential exceptions within the try/catch block.

  • After release, the observer does not automatically remove itself. The observed object continues to send notifications, ignoring the status of the observer. However, like any other message sent to a released object, a change notification triggers a memory access exception. Therefore, you can ensure that the observer deletes itself before it disappears from memory.

  • The protocol cannot ask whether an object is an observer or an observed. Construct the code to avoid release-related errors. A typical pattern is to register as an observer during initialization of the observer (for example, in init or viewDidLoad) and log out during release (usually in Dealloc) to ensure that messages are added and removed in pairs and order, and to ensure that the observer is unregistered and freed from memory before registration.

So, in general,KVO registered observers and removed observers are required in pairsIf only registered, not removed, will appearLike a wild pointer crash, as shown in the figure below

The cause of the crash is due to the first time after registered KVO observer isn’t gone, again into the interface, lead to the second registration KVO observer, leads to KVO observations duplicate registration, and notify the object for the first time in memory, were not released, receives the property value changes at this time of notice, there will be a can’t find the original notification object, Only existing notification objects, i.e. observers registered on the second KVO, can be found, resulting in a crash similar to the wild pointer, where an wild notification is kept and is always listening

Note: In this case, the crash is done using singletons (the crash is very likely to occur, not every time), because singletons are resident in memory, for general class objects, it seems possible not to remove, but in order to prevent online accidents, it is recommended to remove

4, KVO automatic trigger and manual trigger

KVO observation can be turned on and off in two ways, automatic and manual

  • Automatic switch, returnNO, you can’t listen, returnYES, indicating listening
/ / automatic switch + (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {return YES; }Copy the code
  • When the automatic switch is off, it can passManual switch monitoring
- (void)setName:(NSString *)name{// switch [self willChangeValueForKey:@"name"]; _name = name; [self didChangeValueForKey:@"name"]; }Copy the code

The advantage of using manual switch is that you can listen if you want, and you can turn it off if you don’t want to, which is more convenient and flexible than automatic trigger

5. KVO observation: one to many

One to many in KVO observation means that by registering a KVO observer, you can listen for changes in multiple properties

For example, there is a need to calculate the current download progress currentProcess based on the total number of downloads and currentData of current downloads. There are two ways to implement this

  • Observe the total downloads totalData and current downloads currentData, and calculate the current download progress currentProcess when either of the two properties changes

  • KeyPathsForValuesAffectingValueForKey method, the two observation into a watch, is to observe the current download progress currentProcess

/ / 1, the unity of observation method + (NSSet < > nsstrings * *) keyPathsForValuesAffectingValueForKey: (nsstrings *) key {NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"currentProcess"]) { NSArray *affectingKeys = @[@"totalData", @"currentData"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; } / / 2, registered KVO observations [self. The person addObserver: self forKeyPath: @ "currentProcess options: (NSKeyValueObservingOptionNew)" context:NULL]; // 4, self.person.touches :(NSSet< touches *> *)touches withEvent:(UIEvent *)event{self.person.touches += 10; self.person.totalData += 1; } //4, removeObserver - (void)dealloc{[self.person removeObserver:self forKeyPath:@"currentProcess"]; }Copy the code

6. KVO observes mutable arrays

KVO is based on KVC, so if you add data directly to a mutable array, you don’t call the setter method, so all KVO to a mutable array observe that it doesn’t work by going directly to [self.person.datearray addObject:@”1″]; Adding an element to an array does not trigger the KVO notification callback

Self. person. DateArray = [NSMutableArray arrayWithCapacity:1]; [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL]; //2, KVO callback - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@",change); } //3, removeObserver - (void)dealloc{[self.person removeObserver:self forKeyPath:@"dateArray"]; } //4, self.person.content.view - (void)touches began :(NSSet< touches *> *)touches withEvent:(UIEvent *)event{[self.person.view addObject:@"1"]; }Copy the code

In the KVC official documentation, forA collection of mutable arraysType is described as follows, that is, to access collection objects, you need to passmutableArrayValueForKeyMethod, so as toAdds elements to a mutable arrayIn the

Modify the

Modify the code in 4 as follows

- (void)touches began :(NSSet< touches *> *)touches :(touches *) {// touches array [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"]; }Copy the code

As you can see, the element is added to the mutable array

Kind represents the type of key change, which is an enumeration. There are four main types

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {NSKeyValueChangeSetting = 1, / / a value NSKeyValueChangeInsertion = 2, / / insert NSKeyValueChangeRemoval = 3, / / removed NSKeyValueChangeReplacement = 4, / / replace};Copy the code

General properties are distinguished from KVO observations of collections by their kind, such as attribute name and mutable arrays

  • attributethekindThe general isSet the value
  • An array variablethekindThe general isinsert

Exploration of underlying principles of KVO

Official Documentation

In the KVO official user guide, it is described as follows

  • KVO is implemented using isA-Swizzling’s technology.

  • As the name suggests, the ISA pointer points to the class of the object that maintains the allocation table. The dispatch table essentially contains Pointers to the methods implemented by the class, as well as other data.

  • When an observer is registered for an object’s property, the ISA pointer to the observed object is modified to point to the intermediate class instead of the real class. As a result, the value of the ISA pointer does not necessarily reflect the actual class of the instance.

  • You should never rely on an ISA pointer to determine class membership. Instead, you should use the class method to determine the class of the object instance.

Code debugging Exploration

1. KVO only observes attributes

“NickName” (name, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName, nickName)

  • Respectively,Member variable nameAttribute nickNameRegister for KVO watch
self.person = [[LGPerson alloc] init];
    [self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
     [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
Copy the code
  • The KVO notification triggers the operation
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog (@ "reality: % @ - % @", the self. The person. The nickName, the self. The person - > name); self.person.nickName = @"KC"; self.person->name = @"Cooci"; }Copy the code

The result is as follows

Conclusion: KVO does not observe member variables, but only attributes. The difference between attributes and member variables is that attributes have one more setter method, while KVO happens to observe the setter method

2, intermediate class

According to the official documentation, after registering a KVO observer, the ISA pointer of the observed object changes

  • Before registering an observer: Instance objectpersontheisaPointer toLGPerson

  • After registering an observer: Instance objectpersontheisaPointer toNSKVONotifying_LGPerson

After the observer is registered, the isa pointer of the instance object is changed from LGPerson class to NSKVONotifying_LGPerson intermediate class

2-1, determine whether the intermediate class is a derived class or a subclass?

So what does this dynamically generated intermediate class NSKVONotifying_LGPerson have to do with the LGPerson class? Let’s verify this with code

You can get the class associated with LGPerson using the methods encapsulated below

#pragma mark - (void)printClasses:(Class) CLS {// register the total number of classes int count = objc_getClassList(NULL, 0); // Create an array containing the given object NSMutableArray *mArray = [NSMutableArray arrayWithObject: CLS]; Class* classes = (Class*)malloc(sizeof(Class)*count); objc_getClassList(classes, count); for (int i = 0; i<count; i++) { if (cls == class_getSuperclass(classes[i])) { [mArray addObject:classes[i]]; } } free(classes); NSLog(@"classes = %@", mArray); } / call / * * * * * * * * * * * * * * * * [self printClasses: [LGPerson class]].Copy the code

The output is as follows

From the result you can say that NSKVONotifying_LGPerson is a subclass of LGPerson

2-2. What’s in the middle class?

You can get all the methods in the NSKVONotifying_LGPerson class with the following method

(void)printClassAllMethod:(Class) CLS {unsigned int count = 0; Method *methodList = class_copyMethodList(cls, &count); for (int i = 0; i<count; i++) { Method method = methodList[i]; SEL sel = method_getName(method); IMP imp = class_getMethodImplementation(cls, sel); NSLog(@"%@-%p",NSStringFromSelector(sel),imp); } free(methodList); } / call / * * * * * * * * * * * * * * * * [self printClassAllMethod: objc_getClass (" NSKVONotifying_LGPerson ")];Copy the code

The output is as follows

{setNickName, class, dealloc, _isKVOA}}

  • inLGStudentThe rewritesetNickNameMethod to obtainLGStudentClass

The comparison with the methods of the intermediate class shows that only overridden methods are printed through the subclass’s method list, and inherited methods are not iterated through the subclass

  • To obtainLGPersonandNSKVONotifying_LGPersonMethod list for comparison

From what has been discussed above, it can be concluded that:

  • NSKVONotifying_LGPersonThe middle classrewritetheThe parent class LGPersonthesetNickNamemethods
    • NSKVONotifying_LGPersonThe middle classrewritetheThe base class NSObjecttheClass, dealloc, _isKVOAmethods
      • Among themdeallocIs the release method
      • _isKVOADetermine if the current class is KVO

Who is isa pointing to and will the intermediate class be destroyed when the observer is removed from dealloc?

  • Before removing the observer: the isa pointing to the instance object remainsNSKVONotifying_LGPersonThe middle class

  • After removing the observer: the ISA pointing of the instance object changes toLGPersonclass

So, after removing the KVO observer, the isa point is changed from NSKVONotifying_LGPerson to LGPerson

Does the intermediate class still exist after it is created and the observer is removed from the dealloc method?

  • Print on the upper interfaceLGPersonTo determine whether the intermediate class is destroyed

As you can see from the print-out of the subclass, once the intermediate class is generated, it is not removed, it is not destroyed, it is still in memory — mainly because of the idea of reuse, that the intermediate class is registered in memory, so the intermediate class is always there to allow for subsequent reuse problems

conclusion

To sum up, as for the intermediate class, it is as follows:

  • The reference to the instance object ISA is changed from the original class to the intermediate class after KVO observer registration

  • The intermediate class overrides the setter, class, dealloc, and _isKVOA methods for the observation property

  • In the dealloc method, after removing the KVO observer, the instance object isa point is changed from the intermediate class to the original class

  • Intermediate classes remain in memory since they are created and cannot be destroyed

Custom KVO

The process of custom KVO is consistent with the system, but some optimization is done on the basis of the system.

  • 1, will beRegistration and ResponseThrough functional programming, i.eblockThe methods of combining
  • 2, remove the system cumbersome trilogy, to achieveKVO automatic destruction mechanism

In the system, the registration observer and KVO response belong to reactive programming, which is written separately. In order to better coordinate the code in the customization, the form of block is used to combine the registration and callback logic together, that is, functional programming is adopted, or divided into three parts

  • Registered observer
//********* define block********* typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue); / / * * * * * * * * * registered observers * * * * * * * * * - (void) cjl_addObserver: (NSObject *) observer forKeyPath keyPath: (nsstrings *) handleBlock:(LGKVOBlock)block;Copy the code
  • KVO response

And this part of it is basically by overriding setter methods, in the setter methods of the intermediate class, and passing them to the outside in the form of blocks

  • Remove observer
/ / * * * * * * * * * remove observer * * * * * * * * * - (void) cjl_removeObserver: (NSObject *) observer forKeyPath (keyPath nsstrings *);Copy the code

Preparation: Create a class CJLJVO for the NSObject class

Registered observer

In the method of registered observer, there are the following operations:

  • Determine whether the setter method for the currently observed value keyPath exists
# pragma mark - verify whether there is a setter method - (void) judgeSetterMethodFromKeyPath keyPath: (nsstrings *) {Class superClass = object_getClass(self); SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getInstanceMethod(superClass, setterSelector); if (! setterMethod) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString StringWithFormat :@"CJLKVO - no setter for current %@ ", keyPath] userInfo:nil]; }}Copy the code
  • 2,Dynamically generated subclassWill need to be rewrittenclassMethod is added to the intermediate class
# pragma mark - dynamic subclassing - (Class) createChildClassWithKeyPath keyPath: (nsstrings *) {/ / access to the original Class name nsstrings * oldClassName = NSStringFromClass([self class]); / / stitching new class name nsstrings * newClassName = [nsstrings stringWithFormat: @ "% @ % @", kCJLKVOPrefix, oldClassName]; Class newClass = NSClassFromString(newClassName); Return newClass if (newClass) return newClass; NewClass = objc_allocateClassPair([self class], newClassName.utf8String, 0); / / 2.2 registered objc_registerClassPair (newClass); Selectors = selectors (class); Method classMethod = class_getInstanceMethod([self class], classSel); const char *classType = method_getTypeEncoding(classMethod); class_addMethod(newClass, classSel, (IMP)cjl_class, classType); return newClass; } / / * * * * * * * * * class methods * * * * * * * * * # pragma mark - rewrite the class method, Class cjl_class(id self, SEL _cmd){// Return CJLPerson class return class_getSuperclass(object_getClass(self)); }}}}}}}Copy the code
  • 3,Isa toFrom the original class, toPointing to the intermediate class
object_setClass(self, newClass);
Copy the code
  • 4,Save the informationYou can also use a map, where you need to create informationmodelModel class
/ / * * * * * * * * * KVO information model class / * * * * * * * * * # pragma mark information model class @ interface CJLKVOInfo: NSObject @property(nonatomic, weak) NSObject *observer; @property(nonatomic, copy) NSString *keyPath; @property(nonatomic, copy) LGKVOBlock handleBlock; - (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block; @end @implementation CJLKVOInfo - (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{ if (self = [super init]) { _observer = observer; _keyPath = keyPath; _handleBlock = block; } return self; } @ the end / / * * * * * * * * * to save information * * * * * * * * * / / - save multiple information CJLKVOInfo * info = [[CJLKVOInfo alloc] initWithObserver: the observer forKeyPath:keyPath handleBlock:block]; // Use array storage -- or map NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey)); if (! MArray) {// recreate mArray = [NSMutableArray arrayWithCapacity:1] if mArray does not exist; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } [mArray addObject:info];Copy the code

The full registered observer code is below

#pragma mark - registered observer - functional programming - (void)cjl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath HandleBlock: (LGKVOBlock) block {/ / 1, verify whether there is a setter method [self judgeSetterMethodFromKeyPath: keyPath]; / / / / save information - save multiple information CJLKVOInfo * info = [[CJLKVOInfo alloc] initWithObserver: the observer forKeyPath: keyPath handleBlock:block]; // Use array storage -- or map NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey)); if (! MArray) {// recreate mArray = [NSMutableArray arrayWithCapacity:1] if mArray does not exist; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } [mArray addObject:info]; / / judgment automaticallyNotifiesObserversForKey method returns the Boolean value of BOOL isAutomatically = [the self cjl_performSelectorWithMethodName:@"automaticallyNotifiesObserversForKey:" keyPath:keyPath]; if (! isAutomatically) return; / / 2, dynamically generated subclass, / * 2.1 Class 2.2 registered 2.3 adding methods * / Class newClass = [self createChildClassWithKeyPath: keyPath]; //3, isa points to object_setClass(self, newClass); SetterSel = NSSelectorFromString(setterForGetter(keyPath)); Method = class_getInstanceMethod([self class], setterSel]); Const char *type = method_getTypeEncoding(method); Class_addMethod (newClass, setterSel, (IMP)cjl_setter, type); }Copy the code

Pay attention to the point

  • aboutobjc_msgSendCheck off:target -> Build Setting -> Enable Strict Checking of objc_msgSend CallsSet toNO

  • The class method must be overridden in order to be consistent with the external classes of the system, as shown below

    • System of KVOThe class of the instance object Person is always the same before and after the observer is addedCJLPerson

    • ifClass is not overwrittenMethod, the custom KVO will see that the class of the instance object Person before and after registration is inconsistent, returning the isa changed class, that is, the intermediate class

- Custom KVO after overwritten class method, the display of the instance object class before and after registering the observer is consistent with the system displayCopy the code

KVO response

The main idea is to dynamically add setter methods to a subclass in order to send a message in the setter method to the parent class informing it of changes in property values

  • 5. Add setter method overrides to subclasses (mainly in registered observer methods)
SetterSel = NSSelectorFromString(setterForGetter(keyPath)); Method = class_getInstanceMethod([self class], setterSel]); Const char *type = method_getTypeEncoding(method); Class_addMethod (newClass, setterSel, (IMP)cjl_setter, type);Copy the code
  • 6, through the systemobjc_msgSendSuperCast a custom message to sendcjl_msgSendSuper
Objc_msgSendSuper void (*cjl_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper; Struct objc_super superStruct = {.receiver = self, Super_class = class_getSuperclass(object_getClass(self))); // The message receiver is self. super_class = class_getSuperclass(object_getClass(self)); // Call the custom sending message function cjl_msgSendSuper(&superStruct, _cmd, newValue);Copy the code
  • 7. Inform VC to respond: obtain information and pass it through the block
NSString keyPath = getterForSetter(NSStringFromSelector(_cmd)); NSString keyPath = getterForSetter(NSStringFromSelector(_cmd)); id oldValue = [self valueForKey:keyPath]; NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey)); for (CJLKVOInfo *info in mArray) { NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1]; if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) { info.handleBlock(info.observer, keyPath, oldValue, newValue); }}Copy the code

The complete setter method code is as follows

Static void cjl_setter(id self, SEL _cmd, id newValue){NSLog(@" coming :%@",newValue); // There should be code for willChange // send a message to parent LGPerson - through objc_msgSendSuper // customize objc_msgSendSuper void through system cast (*cjl_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper; Struct objc_super superStruct = {.receiver = self, Super_class = class_getSuperclass(object_getClass(self))); // The message receiver is self. super_class = class_getSuperclass(object_getClass(self)); // Call the custom sending message function cjl_msgSendSuper(&superStruct, _cmd, newValue); NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd)); id oldValue = [self valueForKey:keyPath]; NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey)); for (CJLKVOInfo *info in mArray) { NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1]; if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) { info.handleBlock(info.observer, keyPath, oldValue, newValue); }}}Copy the code

Remove observer

To avoid constantly calling the removeObserver method from the outside, implement automatic removal of observers in the custom KVO

  • 8, implementation,cjl_removeObserver:forKeyPath:Method, mainly emptying the array, and ISA pointing to changes
- (void)cjl_removeObserver (NSObject *)observer forKeyPath (NSString *)keyPath{// Empty the array NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey)); if (mArray.count <= 0) { return; } for (CJLKVOInfo *info in mArray) { if ([info.keyPath isEqualToString:keyPath]) { [mArray removeObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCJLKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }} if (marray. count <= 0) {// superClass = [self Class]; object_setClass(self, superClass); }}Copy the code
  • 9. Override the dealloc method in a subclass, which is automatically called when the subclass is destroyeddeallocMethod (added to dynamically subclassed methods)
# pragma mark - dynamic subclassing - (Class) createChildClassWithKeyPath keyPath: (nsstrings *) {/ /... // Add the dealloc method SEL deallocSel = NSSelectorFromString(@"dealloc"); Method deallocMethod = class_getInstanceMethod([self class], deallocSel); const char *deallocType = method_getTypeEncoding(deallocMethod); class_addMethod(newClass, deallocSel, (IMP)cjl_dealloc, deallocType); return newClass; } / / * * * * * * * * * * * * override dealloc methods * * * * * * * * * * * * * void cjl_dealloc (id self, SEL _cmd) {NSLog (@ "to"); Class superClass = [self class]; object_setClass(self, superClass); }Copy the code

The principle is as follows: The reason is that the person object’s ISA is pointing to the middle class, but the address of the instance object is the same, so the subclass is releasing the person. If you override cjl_dealloc, you override the CJLPerson dealloc method, so you go to the CJL_dealloc method) to automatically remove the observer

conclusion

To sum up, customizing KVO can be roughly divided into the following steps

  • Registered observer & response
    • Verify that there is a setter method

    • 2. Save the information

    • 3, dynamic subclass generation, need to override the class, setter methods

    • 4. Send a message to the parent class in the setter method of the subclass

    • Get the observer to respond

  • Remove observer
    • 1, change the ISA point to the original class

    • Override the dealloc method of a subclass

expand

The above custom logic is not perfect, but just illustrates the basic logic of the original implementation of KVO. For details, please refer to Facebook’s KVO tripartite framework KVOController

Custom KVO complete code see Github-CustomKVC_KVO, like can click ❤