runtime uses three: self-realization kvo

A: kvo main principle:
When we call the system addObserver: forKeyPath options: context: method, the system at runtime to create a NSKVONotifying_XXX class for us, belong to a subclass of class XXX class, subclass us to rewrite set the method attribute of listening, additional to call the observeValueForKeyPath: ofObject: change: context :,
we can write the following code to test:

 _person = [[Person alloc] init];
 [_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

Calling addObserver: forKeyPath options: context: After we print _person object before isa pointer on the console, get the following answer:

(lldb) po _person->isa
Person

(lldb) po _person->isa
NSKVONotifying_Person

This is also why we direct verification of a property assignment and will not trigger kvo When a property @property rename setter methods, (extra kvc trigger, because the underlying kvc calls set method)

II: Self realization kvo
categories created NSobject implementation of custom kvo
NSObject + kvo.h

@interface NSObject (kvo)

- (void)zsh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

- (void)zsh_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;

@end

NSObject+kvo.m

@implementation NSObject (kvo)

- (void)zsh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    
    const char *className = object_getClassName(self);
    NSString *newClassStr = [NSString stringWithFormat:@"%s_kvo",className];
    
    Class newClass = objc_allocateClassPair(self.class, newClassStr.UTF8String, 0);
    objc_registerClassPair(newClass);
    
    SEL sel = NSSelectorFromString([NSString stringWithFormat:@"set%@:",[keyPath capitalizedString]]);
    class_addMethod(newClass, sel, (IMP)setAllValue, "v@:@");
    
    object_setClass(self, newClass);
    
    objc_setAssociatedObject(self, @"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_setAssociatedObject(self, @"keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

void setAllValue(id self,SEL _cmd, NSString *newValue) { //NSString *newValue
    
    id observer = objc_getAssociatedObject(self, @"objc");
    NSString *keyPath = objc_getAssociatedObject(self, @"keyPath");
    SEL sel = NSSelectorFromString([NSString stringWithFormat:@"set%@:",[keyPath capitalizedString]]);
    
    Class newClass = [self class];
    object_setClass(self, class_getSuperclass(newClass));
    objc_msgSend(self, sel, newValue);
    object_setClass(self, newClass);
    
    NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
    [dic setValue:newValue forKey:NSKeyValueChangeNewKey];
    [dic setValue:@"test" forKey:@"zhou"];
    
    objc_msgSend(observer, @selector(zsh_observeValueForKeyPath:ofObject:change:context:),keyPath,observer,dic,nil);
}
@end

Here we do some code to explain:

The first step : zsh_addObserver: (NSObject *) observer forKeyPath: (NSString *) keyPath options: (NSKeyValueObservingOptions) options context: (void *) context to achieve:
object_getClassName ------> get the class name of an object
we need the current system creates a subclass of class for NSKVONotifying_XXX class, I've created a class called XXX_kvo
objc_allocateClassPair -----> the first parameter is the name of the parent class I want to create a second class of third temporary matter Biography 0
objc_registerClassPair -----> register for this class
----------------> more than a new sub-class has been created
class_addMethod -----> add to this newClass SEL a method, this method (selector) is sel, it is setAllValue, "v @?" represents no return value (first V), the parameters for the object type (second @), type SEL (third a :), passed parameters (fourth @)
additional briefly explain the SEL and IMP
. 1: IMP equivalent to SEL and form a table, the index is SEL, IMP method is implemented, which is a pointer to the function the first address of the method body (so we Can be seen to change the correspondence between SEL and IMP, to achieve functional changes in the method, which is the principle method of exchange)
2: oc our default method with two arguments, the first is the id of the type of self, the only statement square method, and the second is the SEL, for this method of indexing
----------------> above for the method of adding a new class, SEL which is sel, IMP is setAllValue
object_setClass -----> self pointer pointing to isa newClass (using the isa switching technology), the purpose can be found in newClass in this method
an additional brief description of
1: isa an object points to its class, class of isa points to metaClass, metaClass pointed rootClass (ie NSObject)
object_getClass (<#id _Nullable obj #>) is the acquired obj isa point
class is a class for the object, a class to class, for metaClass is metaClass
2: our methods and attributes stored in the object class, the class stored in the class attributes and methods metaClass in; it is a target of a method call, in fact, is to look at the class structure in this method, in order to superClass
----------------> or more of the self to the new isa this class
objc_setAssociatedObject -----> associated object, and observer only during storage for subsequent keyPath zsh_observeValueForKeyPath: ofObject: change: context: passed back
----------------> zsh_addObserver: forKeyPath: options: context: Function completed
Step Two : setXX processing methods and callback zsh_observeValueForKeyPath: ofObject: change: context:
2 setAllValue front line: the observer and the object to get the associated keyPath
the SEL SEL = NSSelectorFromString ----> current method to give the SEL
object_setClass (self, class_getSuperclass (newClass)); -> the isa self pointer refers back to their class, It is to call your own setXX method
objc_msgSend ----> call self setXX method of
call completion point isa weight class subclass
zsh_observeValueForKeyPath create change information and call observer of: ofObject: change: context: we can use to put away this method of its
----------------> completed

Test:
person.h

@interface Person : NSObject

@property (nonatomic, strong) NSString *name; // ,setter=setzhouName:kvo不能作用到

@property (nonatomic, assign) NSString *avator;

- (void)aa;

@end

person.m

@implementation Person

- (void)setName:(NSString *)name {
    
    _name = name;
    NSLog(@" Person类的setName:方法");
}

- (void)setAvator:(NSString *)avator {
    
    _avator = avator;
     NSLog(@" Person类的setAvator:方法");
}

@end

ViewController.m call

@interface ViewController ()

{
    Person *_person;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _person = [[Person alloc] init];
    
    [_person zsh_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    [_person zsh_addObserver:self forKeyPath:@"avator" options:NSKeyValueObservingOptionNew context:nil];
}

- (IBAction)set:(id)sender {
   
    [_person setName:@"123"];
}
- (IBAction)setAvator:(id)sender {
    
    [_person setAvator:@"qwe"];
}

- (void)zsh_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@" 有值改变了 === observer=%@,keyPath=%@ change=%@",object,keyPath,change);
}

@end

Print run results are as follows:

2019-05-14 17:54:24.918459+0800 Test[36837:2344998]  Person类的setAvator:方法
2019-05-14 17:54:24.918684+0800 Test[36837:2344998]  有值改变了 === observer=<ViewController: 0x7f8aad519d10>,keyPath=avator change={
    new = 123;
    zhou = test;
}
2019-05-14 17:54:26.707345+0800 Test[36837:2344998]  Person类的setAvator:方法
2019-05-14 17:54:26.707466+0800 Test[36837:2344998]  有值改变了 === observer=<ViewController: 0x7f8aad519d10>,keyPath=avator change={
    new = qwe;
    zhou = test;
}

Published 40 original articles · won praise 10 · views 30000 +

Guess you like

Origin blog.csdn.net/ai_pple/article/details/90212701
Recommended