As promised, rain or shine, and 3 days of delay, the code has been sorted out, but not sorted into the article. That was the mission last Sunday

KVO(key-value Observing), can certainly be used, often used to monitor the change of a certain Value.

As usual, we have a Book class.

//Book.h
@interface Book : NSObject

@property (nonatomic,strong) NSString *name;

@end
Copy the code
//Book.m
@implementation Book

@end
Copy the code

Now you need to listen for the Book property name in the ViewController ~

//ViewController.m - (void)viewDidLoad { [super viewDidLoad]; // initialize _book1 = [[Book alloc] init]; [_book1 addObserver:selfforKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    _book1.name = @"Tom";

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSString *name = change[NSKeyValueChangeNewKey];
    NSLog(@"% @",name);
}
Copy the code

This is a property that we normally listen for in a class, but we stumbled upon something interesting that inspired the KVO principle.

In observeValueForKeyPath: ofObject: change: the context of method, an instance of a print Book class object _book1 class name.

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{// prints the class name NSLog(@"KVO class name :%s %@",object_getClassName(_book1) ,[_book1 class]);
}

Copy the code
KVO[13369:6261090] _book1 getClassName = : nskVonotifying_book1 class = BookCopy the code

Emmmm? What the hell is going on here? What’s apple up to? Then look at whether addObserver: forKeyPath: options: context: what place? Output (breakpoint) once before and after addObserver.

SEL = NSSelectorFromString(@)"setName:");
    
    NSLog(@"-----setName:%p Class name :%s",[_book1 methodForSelector:sel],object_getClassName(_book1));
    
    [_book1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    NSLog(@After listening -----setName:%p Class name :%s,[_book1 methodForSelector:sel],object_getClassName(_book1));

    _book1.name = @"Tom";

Copy the code
KVO[13369:6261090] -----setName: 0x1079C5EF0 16:28:36.745478+0800 Custom KVO[13369:6261090] after listening -----setName: 0x107D20B5e Class name :NSKVONotifying_BookCopy the code

NSKVONotifying_Book = NSKVONotifying_Book = NSKVONotifying_Book

NSKVONotifying_Book has a parent class of Book. The apple.

That is, when we listen on Book’s name, the Runtime dynamically generates a subclass of Book, NSKVONotifying_Book, and overrides setName:.

Understand the principle, we play by ourselves ~. Custom custom_addObserver: forKeyPath: options: context: method, called after our own method.

//Book.m NSString *getVauleKeyPath(NSString *methodName); void customSetName(id self, SEL _cmd , NSString *name){/* we must send it a message -(void) observeforkeypath :(NSString *)keyPath ofObject:(id)object Change :(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context */ / since we overwrote itset//id self: CustomKVONotifying_Book The parent is Book and we need to send it to the parentsetStruct objc_super superClass = {self, class_getSuperclass([self class])}; ((void (*)(id,SEL,NSString *))objc_msgSendSuper)((__bridge id)(&superClass), _cmd, name); Observer = objc_getAssociatedObject(self,"observer"); // our Key NSString *methodName = NSStringFromSelector(_cmd); NSString *keyPath = getVauleKeyPath(methodName); / / change the value of the NSDictionary < NSKeyValueChangeKey, id > * change = @ {NSKeyValueChangeNewKey: name}; ((void (*)(id,SEL, NSString * , id , id , void *))objc_msgSend)(observer, @selector(observeValueForKeyPath:ofObject:change:context:) ,keyPath, self, change, nil); } NSString *getVauleKeyPath(NSString *methodName){// overridesetName: method keyPath is of course "Name" //'set'':'NSRange = NSMakeRange(3, methodName.length-4); / / keyPath is currently'Name'; NSString *keyPath = [methodName substringWithRange:range]; NSString *first = [[keyPath substringToIndex:1] lowercaseString]; / / replace keyPath = [keyPath stringByReplacingCharactersInRange: NSMakeRange (0, 1) withString: first];return keyPath;
}

@implementation Book

- (void)custom_addObserver:(NSObject *)observer forKeyPath: (nsstrings *) KeyPath options: (NSKeyValueObservingOptions) options context: (nullable void *) context {/ / / to create the class / / the current name of the class  NSString *currentClass = [NSString stringWithUTF8String:object_getClassName(self)]; NSString *custom_Class = [NSString stringWithFormat:@"CustomKVONotifying_%@",currentClass]; Cutsom_Class = objc_allocateClassPair([self Class], custom_class.utf8String, 0); // Register the class with the system. objc_registerClassPair(cutsom_Class); // Change the isa pointer to our subclass, otherwise we will not be able to process what we sendsetName: message object_setClass(self, cutsom_Class); // Override the SetName method. //keyPath = name, capitalizedString (keyPath = name)setName:
    NSString *methodName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString]; SEL = NSSelectorFromString(methodName); //add_Method, as usual, is described in detail in the previous article. Class_addMethod (cutsom_Class, sel, (IMP)customSetName,"v@:@"); // Bind the listener to the current class so that it can be retrieved later and sent to the listener. objc_setAssociatedObject(self,"observer", observer, OBJC_ASSOCIATION_ASSIGN);
    
}

@end
Copy the code

Object_getClassName = object_getClassName; object_getClassName = object_getClassName; 2, Our subclass name CustomKVONotifying_Book; Create our subclass objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes); Register our subclass objc_registerClassPair(Class _Nonnull CLS); Modify our isa pointer because we need subclasses to implement the setName: method. Class_addMethod,setName:; 7. Bind our observer to the current class so that we can get the observer and send messages to the observer. 8, implement setName:; SetName: we need to change the value of the Book class name by calling the method objc_msgSendSuper that sends messages to the parent class. Send objc_msgSend to our bound observer.

So we basically recreated the KVO Apple scandal.