This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

review

In my last post, I’ve covered KVO operations, so let’s explore the underlying logic of KVO. How is KVO implemented?

  • This is shown in the official document below

Key-value observation is achieved using a technique called ISA-Swizzling.

  • The ISA pointer, as the name suggests, points to the object’s class, which holds a schedule table. The schedule consists primarily of Pointers to methods implemented by the class, as well as other data.

  • When an observer registers an object’s property, the ISA pointer to the observed object is modified to point to the intermediate class rather than the actual class. Therefore, the value of the ISA pointer does not necessarily reflect the actual class of the instance.

  • You should not rely on the ISA pointer to determine the members of a class. Instead, use the class method to determine the class of the instance object.

1. The isa – swizzling validation

In the add Observer area, hit the breakpoint, and then console LLDB debugging to see.

- (void)viewDidLoad {
	[super viewDidLoad];
	self.student = [[JPStudent alloc]init];
	[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
Copy the code
  • The console prints as follows

Student changed from JPStudent to NSKVONotifying_JPStudent after addObserver

We know that the relationship between an instance object and a class is actually that the isa of the instance object points to the class object. So here we can infer that self.student has gone from being an instance of class JPStudent, to being an instance of class NSKVONotifying_JPStudent, after calling the addObserver method.

2. NSKVONotifying_JPStudent subclass verification

  • Then thisNSKVONotifying_JPStudentWhat is it? Was it direct from the beginning, or was it the sum ofJPStudentWhat are the relationships between classes? Then look atNSKVONotifying_JPStudentIf it was there from the beginning, run the code again, the breakpoint is still broken at add Observer, print it out

Note: objc_getClass is the runtime API. You must import header files to make objc_getClass work.

In the calladdObserverMethod before and after printing, the results are describedNSKVONotifying_JPStudentIs a class that is added dynamically by the system. The names of these two classes are so similar, could it beJPStudentWhat about subclasses of theta? Let’s print it out

From the print, the new world has been discovered. NSKVONotifying_JPStudent is indeed inherited from JPStudent. So this middle class, is it possible to have its own subclass? Let’s look at this with the following code

#pragmaMark - Traverses classes and subclasses
- (void)printClasses:(Class)cls{
	 // Total number of registered classes
	 int count = objc_getClassList(NULL.0);
	 // Create an array containing the given object
	 NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
	 // Get all registered classes
	 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);
}
Copy the code
  • The print result is as follows

From the print, you can verifyNSKVONotifying_JPStudentisJPStudentThe subclass. thenNSKVONotifying_JPStudentSo what’s in this class? Class is generally stored insideMember variables,methods,agreementAnd so on, so take a look at the following code to see what’s in it.

#pragmaMark ** -traversal method -ivar-property**
- (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);
}
Copy the code
  • Print the following

The system overwrites setNickName, class, dealloc, and adds a method called _isKVOA to distinguish whether the system automatically generated by KVO or not.

3. Has the observer been removed from isa?

The official documentation says: calladdObserverThe method will changeisaPointing. So now what happens when we remove the observer? We are indeallocMethod to remove the observer and put a break point here and continue to observeself.studenttheisaPointing to.

After removing the observer, self.student’s ISA refers back to the JPStudent class. And the generated subclass NSKVONotifying_JPStudent is still there, not destroyed.

The reason is that if you continue adding observers next time, the system will not regenerate a new intermediate class, but directly use this class, to prevent the waste of resources.

4. A class method

After adding an observer, we all know that the dynamic subclass NSKVONotifying_JPStudent is generated,

So calling class method p self.student.class prints NSKVONotifying_JPStudent??

  • After the breakpoint adds an observer, let’s verify it on the console

From the printout, the output is stillJPStudentAlthough,self.studenttheisaHave been toNSKVONotifying_JPStudentBut becauseNSKVONotifying_JPStudentRewrite theclassMethod, and the final printout isJPStudentApple’s goal is to hide the actions that the system is doing behind the scenes, allowing developers to focus less on the underlying logic and only on the code implementation at the top.

5. A setter method

Since we overwrite the setter method to observe the property, can we also observe the property if we have a member variable? Test this by adding the age member variable

@interface JPStudent : NSObject
{
	@public
	int age;
}
@property (nonatomic.copy) NSString *name;

@end 
Copy the code

The listening callback method is not triggered when age is assigned. So we’re just listening on the setter method of the property.

Let’s take a look at thedeallocRemove observer inisaWhen pointing back, checknameThe value of the

That means that changes to name in the KVO generated class affect the original class.

rightnameLet’s debug next memory breakpoint

  • Bt prints stack information

It turns out that some Foundation methods are called, and finally [JPStudent setName:] assigns a value to name.

Tip: The Foundation framework is not open source.

  • Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
  • Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
  • Foundation`_NSSetObjectValueAndNotify

_NSSetObjectValueAndNotify assembly call mainly as follows:

"willChangeValueForKey:"Here is the callsetterMethods the assignment"didChangeValueForKey:"
"_changeValueForKey:key:key:usingBlock:"
Copy the code

From the stack information and assembly, can know in _changeValueForKeys: count: maybeOldValuesDict: maybeNewValuesDict: usingBlock: in the callback after the assignment, so sure to notify the listeners

  • inobserveValueForKeyPathMake a breakpoint in the callback to:

Confirmation is the callback in the NSKeyValueNotifyObserver notification.

6. Summary

  • KVOAdding an ObserveraddObserverDynamically generated subclassNSKVONotifying_XXX.
  • rewriteclassMethod that returns the parent classclassInformation. The parent classisaPoints to subclasses.

Add setter methods (all properties to be observed) to dynamic subclasses. The message is forwarded to the parent class.

  • setterThe original method of the parent class is called for assignment and is notified by a callback when it is completed.
  • removeobserverwhenisaReferring back to the parent class, dynamically generated children are not destroyed.

More to come

🌹 just like it 👍🌹

🌹 feel have harvest, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹

🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹