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 observer
addObserver:forKeyPath:options:context
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
Copy the code
- Implement the KVO callback
observeValueForKeyPath: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 observer
removeObserver:forKeyPath:context
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];
Copy the code
2. Context usage
In the official documentation, forParameters of the context
There 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 remove
There 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 pairs
If 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, return
NO
, 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 pass
Manual 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 arrays
Type is described as follows, that is, to access collection objects, you need to passmutableArrayValueForKey
Method, so as toAdds elements to a mutable array
In 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
attribute
thekind
The general isSet the value
An array variable
thekind
The 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 name
和Attribute nickName
Register 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 object
person
theisa
Pointer toLGPerson
- After registering an observer: Instance object
person
theisa
Pointer 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}}
- in
LGStudent
The rewritesetNickName
Method to obtainLGStudent
Class
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 obtain
LGPerson
andNSKVONotifying_LGPerson
Method list for comparison
From what has been discussed above, it can be concluded that:
NSKVONotifying_LGPerson
The middle classrewrite
theThe parent class LGPerson
thesetNickName
methods-
NSKVONotifying_LGPerson
The middle classrewrite
theThe base class NSObject
theClass, dealloc, _isKVOA
methods- Among them
dealloc
Is the release method _isKVOA
Determine if the current class is KVO
- Among them
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 remains
NSKVONotifying_LGPerson
The middle class
- After removing the observer: the ISA pointing of the instance object changes to
LGPerson
class
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 interface
LGPerson
To 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 be
Registration and Response
Through functional programming, i.eblock
The methods of combining - 2, remove the system cumbersome trilogy, to achieve
KVO 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 subclass
Will need to be rewrittenclass
Method 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 to
From the original class, toPointing to the intermediate class
object_setClass(self, newClass);
Copy the code
- 4,
Save the information
You can also use a map, where you need to create informationmodel
Model 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
- about
objc_msgSend
Check off:target -> Build Setting -> Enable Strict Checking of objc_msgSend Calls
Set toNO
-
The class method must be overridden in order to be consistent with the external classes of the system, as shown below
System of KVO
The class of the instance object Person is always the same before and after the observer is addedCJLPerson
- if
Class is not overwritten
Method, 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 system
objc_msgSendSuper
Cast 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 destroyed
dealloc
Method (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 ❤