42

KVC/KVO实现原理

 4 years ago
source link: https://www.tuicool.com/articles/bi67Fzi
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、kvo实现原理

一个对象的属性被观察时系统动态创建了一个子类,并且改变了原有对象的 isa 指针指向,指向动态创建的子类,子类中重写了被观察属性的 set 方法,在使用点方法和 set 方法给属性赋值时,最终调用的是子类中的 set 方法。 注意:查看 isa 指针指向,如果断点执行过程中 isa 指针没有变化, 需要关闭 xcode 重新打开

相关代码:在 addObserver 处设置断点观察对象 isa 指针变化:

- (void)viewDidLoad {
    [super viewDidLoad];
    People *p = [[People alloc] init];
    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    p.name = @"123";
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey> *)change context:(void *)context{
    NSLog(@"%@",change);
}

通过断点观察 isa 指针的指向 被观察前 isa 指针指向的是原始类如图:

AnAFzeA.png!web

执行代码被观察后指针指向的是 NSKVONotifying_People 类:

a2EVFrQ.png!web

二、自定义KVO

创建一个分类新增一个方法 HBaddObserver ,在方法中创建子类注册并指向子类,再为子类添加 set 方法既可。

主要使用函数:1、创建一个子类

objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
 size_t extraBytes)
 superclass:设置新类的父类
 name:新类名称
 extraBytes:额外字节数设置为0

2、注册该类

objc_registerClassPair(Class _Nonnull cls)
 cls:当前要注册的类,注册后才可以使用

3、设置当前对象指向其他类

object_setClass(id _Nullable obj, Class _Nonnull cls)
 obj:要设置的对象
 cls:指向的类

4、动态添加一个方法

class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
 const char * _Nullable types)
 cls:设置添加方法对应的类
 name:选择子(选择器)名称,描述了方法的格式,并不会指向方法
 imp:函数名称(函数指针),和选择子一一对应,指向方法实现的地址

通过分类添加新的观察者添加方法:

-(void)HBaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    NSString *oldName = NSStringFromClass(self.class);
    NSString *newName = [@"HBKVO_" stringByAppendingString:oldName];
    //1、创建一个子类
    Class newClass = objc_allocateClassPair(self.class, newName.UTF8String, 0);
    //2、注册该类
    objc_registerClassPair(newClass);
    //3、指向子类
    object_setClass(self, newClass);
    //4、动态添加一个方法
    NSString *first = [keyPath substringWithRange:NSMakeRange(0, 1)];
    NSString *other = [keyPath substringFromIndex:1];
    NSString *setName = [NSString stringWithFormat:@"set%@%@:",first.uppercaseString,other];//设置一个属性名首字母大写的方法
    Method method = class_getInstanceMethod(self.class, sel_registerName(setName.UTF8String));
    const char *types = method_getTypeEncoding(method);
    class_addMethod(newClass, sel_registerName(setName.UTF8String), (IMP)setValue, types);
    //class_addMethod(newClass, sel_registerName(setMethod.UTF8String), (IMP)setName, "v@:@");

    //设置关联数据
    //获取元类旧值使用
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //设置新值的时候使用
    objc_setAssociatedObject(self, "setName", setName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //通知值变化
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //传进来的内容需要回传
    objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

设置属性值的新调用方法:

void setValue(id self,SEL _cmd,NSString *newValue){
    NSLog(@"newValue:%@",newValue);
    NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
    NSString *setName = objc_getAssociatedObject(self, "setName");
    id observer = objc_getAssociatedObject(self, "observer");
    id context = objc_getAssociatedObject(self, "context");
    //存储新类
    Class newClass = [self class];
    //指向父类获取旧值
    object_setClass(self, class_getSuperclass(newClass));
    NSString *oldValue = objc_msgSend(self,sel_registerName(keyPath.UTF8String));
    //对原始类属性或成员变量复制
    objc_msgSend(self, sel_registerName(setName.UTF8String),newValue);
    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    if (oldValue) {
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    if (newValue) {
        change[NSKeyValueChangeNewKey] = newValue;
    }
    //调用observer的回调方法
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath,observer,change,context);
    //操作完成后指回动态创建的新类
    object_setClass(self, newClass);
}

通过断点观察 isa 指针的指向 被观察前 isa 指针指向的是原始类如图:

FRBB3yE.png!web

执行代码被观察后指针指向的是 HBKVO_Person 类:

Q3Irem2.png!web

1、主控制器代码如下:

#import "ViewController.h"
#import <objc>
#import "Person.h"
#import "NSObject+HBKVO.h"

@interface ViewController (){
    Person *p;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    p = [[Person alloc] init];
    [p HBaddObserver:self forKeyPath:@"Name" options:NSKeyValueObservingOptionNew context:nil];

}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey> *)change context:(void *)context{
    NSLog(@"change:%@",change);
}
-(void)touchesBegan:(NSSet<UITouch> *)touches withEvent:(UIEvent *)event{
    static int num = 0;
    p.Name = [NSString stringWithFormat:@"%d",num++];
}
@end

2、 person 类头文件代码( .m 中无相关代码):

#import <Foundation>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property (nonatomic,strong)NSString *Name;
@end

NS_ASSUME_NONNULL_END

3、自定义 KVO 相关代码( NSObject 分类):

NSObject+HBKVO.h

#import <Foundation>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (HBKVO)
- (void)HBaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
NS_ASSUME_NONNULL_END

NSObject+HBKVO.m

#import "NSObject+HBKVO.h"
#import <objc>

@implementation NSObject (HBKVO)
-(void)HBaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    NSString *oldName = NSStringFromClass(self.class);
    NSString *newName = [@"HBKVO_" stringByAppendingString:oldName];
    //1、创建一个子类
    Class newClass = objc_allocateClassPair(self.class, newName.UTF8String, 0);
    //2、注册该类
    objc_registerClassPair(newClass);
    //3、指向子类
    object_setClass(self, newClass);
    //4、动态添加一个方法
    NSString *first = [keyPath substringWithRange:NSMakeRange(0, 1)];
    NSString *other = [keyPath substringFromIndex:1];
    NSString *setName = [NSString stringWithFormat:@"set%@%@:",first.uppercaseString,other];//设置一个属性名首字母大写的方法
    Method method = class_getInstanceMethod(self.class, sel_registerName(setName.UTF8String));
    const char *types = method_getTypeEncoding(method);
    class_addMethod(newClass, sel_registerName(setName.UTF8String), (IMP)setValue, types);
    //class_addMethod(newClass, sel_registerName(setMethod.UTF8String), (IMP)setName, "v@:@");

    //设置关联数据
    //获取元类旧值使用
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //设置新值的时候使用
    objc_setAssociatedObject(self, "setName", setName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //通知值变化
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //传进来的内容需要回传
    objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
void setValue(id self,SEL _cmd,NSString *newValue){
    NSLog(@"newValue:%@",newValue);
    NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
    NSString *setName = objc_getAssociatedObject(self, "setName");
    id observer = objc_getAssociatedObject(self, "observer");
    id context = objc_getAssociatedObject(self, "context");
    //存储新类
    Class newClass = [self class];
    //指向父类获取旧值
    object_setClass(self, class_getSuperclass(newClass));
    NSString *oldValue = objc_msgSend(self,sel_registerName(keyPath.UTF8String));
    //对原始类属性或成员变量复制
    objc_msgSend(self, sel_registerName(setName.UTF8String),newValue);
    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    if (oldValue) {
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    if (newValue) {
        change[NSKeyValueChangeNewKey] = newValue;
    }
    //调用observer的回调方法
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath,observer,change,context);
    //操作完成后指回动态创建的新类
    object_setClass(self, newClass);
}
@end

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK