The simple code emulates the KVO derived class implementation. Of course, some people will ask, “There are many examples on the Internet, just look at them, why write?” . In fact, some knowledge points of personal understanding can not just look at, write a write, say it will have a promotion of their own cognition. Especially with digg friends to share exchanges can also have a new perception, learning progress together.
First, the foreshadowing before the realization of logic
1. How to understand derived classes
In fact, “derived” has no special meaning in my personal understanding. It is just a class, but it is created to serve a specific scene. In practical development, the “derived” class generated by the system is a hidden class, and it will not be called as UIButton.
2. What exactly is a class
A class is an object, and as soon as you say that, someone says, “No! If a class is an object, then what is an instance object?” . There may be no distinction between “class” and “object” in code logic, but in order to make the program better understood, some specific code is collectively referred to as “class”. The load method is interpreted as assigning a memory address to a class, and the corresponding memory area of the class stores the associated property list, method list, and so on according to the program Settings. The ISA pointer to an instance object of a class points to the memory address of the class. Therefore, the responsive methods or properties of an object are found through ISA to find whether they can respond or have corresponding properties.
Here’s something else. As you know, JS also has the concept of a prototype chain, showing a piece of code:
/ / animal
var Animal = function(){
this.run = function(){
console.log('I'm an animal, I'm running'); }};var animal = new Animal();
animal.eat = function(){
console.log('I'm animal. I'm eating.');
}
/ / human
var Person = function(){};
// Set animal as the parent of Person
Person.prototype = animal;
var person = new Person();
// Execute the method whose parent exists
person.run();
person.eat();
Copy the code
Running results:
You can see that the Person class is set to prototype as an instance of the Animal class. Then, an instance object of Person can inherit all the methods of the Animal class.
3, modify,isaWhat happens to Pointers
The isa pointer isa default “member variable” of the instance object, which holds the address of the class. Modifying isa only changes the pointing of the class, and does not affect the memory data already opened by the instance object. After creating a derived class, adjusting the structure of the derived class dynamically through the Runtime, and pointing the ISA pointer to the derived class, the instance object responds to any new methods of the derived class. To ensure runtime stability, it is desirable that “derived” classes inherit from the class to which the current instance object belongs.
Two, simple code implementation
1, create,Animalclass
@interface Animal : NSObject @property(nonatomic,strong) NSString * name; @property(nonatomic,strong) NSString * address; @property(nonatomic,strong) NSString * color; @implementation Animal - (instanceType)init {if (self = [super init]) {self.color = @" red "; } return self; } - (void)eat {NSLog(@" I am an animal, I am eating "); } - (void)setName:(NSString *)name { _name = name; NSLog(@" I am the original setName method \n"); } - (void)dealloc {NSLog(@" animal %@ destroyed ",self); } @endCopy the code
A very simple class, declare name, address, color three attributes. To prove that changing the ISA pointer does not affect the memory data of the instance object, the Animal color is set to red at initialization.
2, create,NSObject+WSLObserverclassification
(1) NSObject + WSLObserver. H
#import <Foundation/ foundation.h > NS_ASSUME_NONNULL_BEGIN @interface NSObject (WSLObserver) // Add observer - (void)addWSLObserverWithKey:(NSString *)key callBack:(void(^)(id _self,id obj))callBack; @end NS_ASSUME_NONNULL_ENDCopy the code
In this case, the addWSLObserverWithKey method, the key parameter is the property to observe, and the callBack is the callBack notification when the property is modified.
(2) the NSObject + WSLObserver. M
#import "NSObject+WSLObserver.h" #import <objc/runtime.h> #import <objc/message.h> static char * callBackDicStr; @implementation NSObject (WSLObserver) //C implementation void setValue(id self,SEL _sel,id value){Class currentClass = [self class]; // add isa to the parent object_setClass(self, class_getSuperclass([self class])); // Execute the parent method ((void (*) (id, SEL, id)) (void *)objc_msgSend)(self, _sel, value); // Set to the derived class object_setClass(self, currentClass); // Execute the derived class method NSMutableDictionary * callBackDic = objc_getAssociatedObject(self, &callBackDicStr); void(^callBack)(id,id) = (void(^)(id,id))callBackDic[NSStringFromSelector(_sel)]; if (callBack) { callBack(self,value); }} - (void)addWSLObserverWithKey:(NSString *)key callBack:(void(^)(id _self,id obj))callBack {// current Class Class = [self class]; // Derived Class newClass = Class; // Check whether key exists if (! [self checkIvarIsExist:class key:key]) {NSLog(@" not yet %@ attribute ",key); return; } // create a derived class NSString * classStr = NSStringFromClass(class); NSString * newClassStr = [NSString stringWithFormat:@"WSLDerived%@",classStr]; char * newClassChar = (char*) [newClassStr UTF8String]; BOOL isDerived = [NSStringFromClass(class) hasPrefix:@"WSLDerived"]; if (! IsDerived) {// Derived class newClass = objc_allocateClassPair(class,newClassChar,0); Object_setClass (self, newClass); } // override setKey = @""; If (key length > 0) {setKey = [key stringByReplacingCharactersInRange: NSMakeRange (0, 1) withString: [[key substringToIndex:1] capitalizedString]]; } else { return; } // NSMutableDictionary * callBackDic = objc_getAssociatedObject(self, &callBackDicStr); if (! callBackDic) { callBackDic = [[NSMutableDictionary alloc] init]; objc_setAssociatedObject(self,&callBackDicStr,callBackDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } callBackDic[[NSString stringWithFormat:@"set%@:",setKey]] = callBack; SetSel = NSSelectorFromString([NSString stringWithFormat:@"set%@:",setKey]); class_addMethod(newClass, setSel, (IMP)setValue,"v@:@"); } // check whether the key exists - (BOOL)checkIvarIsExist:(Class) Class key:(NSString *)key {BOOL isExist = NO; unsigned int outCount, i; Ivar * ivars = class_copyIvarList(class, &outCount); for (i = 0; i < outCount; i++){ Ivar ivar = ivars[i]; NSString * ivarName = [[NSString alloc] initWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding]; if ([ivarName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) { free(ivars); return YES; }} // if (! isExist) { if ([class superclass]) { isExist = [self checkIvarIsExist:[class superclass] key:key]; } } return isExist; }Copy the code
First, we declare a setValue c function at the beginning, where the method name does not really matter, the purpose is to obtain its IMP. The method body is mainly used to call the parent set method and the callBack saved by its associated object. This essentially overrides the set method and calls the super method.
Again, the addWSLObserverWithKey method creates a derived class that inherits from the class to which the instance object belongs, changes the ISA pointer to the derived class, and adds a set method for the derived class to which attributes need to be observed. The callBack closure passed in by the addWSLObserverWithKey method is stored under an association property of type NSMutableDictionary, with the set method name as the key and the callBack as the value.
Finally, animal assigns property values using the set method of its derived class.
Third, external call
Logical code:
Animal * animal = [[Animal alloc] init]; NSLog(@" animal = %@",animal); [animal addWSLObserverWithKey:@"name" callBack:^(id _Nonnull _self, id _Nonnull obj) { NSLog(@"\n name = %@ \n",obj); }]; [animal addWSLObserverWithKey:@"address" callBack:^(id _Nonnull _self, id _Nonnull obj) { Animal * animal = (Animal *)_self; NSLog(@"\n self.color = %@;address = %@ \n",animal.color,obj); }]; animal.name = @"cat"; Animal. address = @" Raniakaya supergroup "; NSLog(@" animal = %@",animal);Copy the code
Print result:
As you can see, changing ISA does not affect the object’s memory address, nor does the object’s color attribute.
Four,
Some friends will point out that when the object is destroyed, the ISA pointer does not change back, which does not meet the requirements. It is possible to change the isa pointer back to the original class. Instead of changing the isa pointer at the beginning of the set, the set implementation of the original class property changes the ISA pointer at the time of the set, and then changes the ISA pointer back to the original class when the set is finished. But if you do that there is really no point in creating a derived class, perhaps the system has more considerations than its own level of knowledge.
Also, don’t blindly use the Runtime’s method_exchangeImplementations API, the best way is to ensure that both methods of the current class exist, create one that doesn’t exist, because if one of the two methods exchanged is in the parent class, then, The parent class will crash when it calls the swapped method because it can’t find the implementation, because the implementation is written in the subclass.
Learning summary, don’t laugh big god [fist][fist][fist]