2.RAC解析 – 自己设置KVO

作者 : 开心源码 本文共4369个字,预计阅读时间需要11分钟 发布时间: 2022-05-12 共177人阅读

知识点概述

1.KVO实现原理
2.runtime使用

目的

给NSObject增加一个Category,用于给实例对象增加观察者,当该实例对象的某个属性发生变化的时候通知观察者。

大体思路

增加观察者的方法中

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

会用runtime的方式手动创立一个其子类,并且将该对象变为该子类。该子类会复写观察方法中keyPath的setter方法,使这个setter被调用时利用runtime去调用observer的回调方法

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

实现

这里只做KVO的基本功能,当被观察者改变属性的时候通知观察者,所以定义如下方法

NSObject+SQKVO.h

/** 增加观察者 @param observer 观察者 @param keyPath 被观察的属性名 */- (void)SQ_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;/** 当被观察的观察属性改变的时候的回调函数 @param keyPath 所观察被观察者的属性名 @param object 被观察者 @param value 被观察的属性的新值 */- (void)SQ_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object changeValue:(id)value;@end

由于这里要用到runtime所以需要增加runtime的头文件

#import <objc/message.h>

而且由于用到objc_msgSend所以要改变一下工程的环境变量

##

一.动态生成子类

在被观察者调用- SQ_addObserver:forKeyPath:时首先动态生成一个其子类。

    // 1.生成子类    // 1.1获取名称    Class selfClass = [self class];    NSString *className = NSStringFromClass(selfClass);    NSString *KVOClassName = [className stringByAppendingString:@"_SQKVO"];    const char *KVOClassNameChar = [KVOClassName UTF8String];    // 1.2创立子类    Class KVOClass = objc_allocateClassPair(selfClass, KVOClassNameChar, 0);    // 1.3注册    objc_registerClassPair(KVOClass);

这里可以看到,我们将子类的类名命名为“类名”+“SQKVO”,譬如类名为“Person”,这个子类是“Person_SQKVO”。
这里有个注意点,一般为动态创立的类名应尽量复杂少量避免重复。最好加上“
”。

二.根据KeyPath动态增加对应的setter

1 确定setter的名字

举个例子,假如客户给的keyPath是name,应该动态增加一个-setName:的方法。而这个setter的名字是 “set” + “把keyPath变为首字母大写” + “:”
所以可以得出

NSString *setterString =    [NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]];    SEL setter =  NSSelectorFromString(setterString);
2 利用class_addMethod()给子类动态增加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
  • cls:
    给哪个类增加方法。即新生成的子类,上面生成的 KVOClass。
  • name:
    所增加方法的名称。即上一步生成的字符串 setterString。
  • imp:
    所增加方法的实现。即这个方法的C语言实现,首先在下面先写一个C语言的方法。稍后会讲具体实现。
void setValue(id self, SEL _cmd, id newVale) {}
  • types:
    所增加方法的编码类型。setter的返回值是void,参数是一个对象(id)。void用”v”表示,返回值和参数之间用“@:”隔开,对象用”@”表示。最后我们可以得出结果”v@:@”。
    具体其余的编码类型可以参考苹果文档。

ps: 这里说下为什么返回值和参数之间用“@:”隔开。“:”代表字符串,所有的OC方法都有两个隐藏参数在参数列表的最前面,“发起者”和 “方法形容符”,“@”就是这个发起者,“:”是方法形容符。而这个types其实是imp返回值和参数的编码。由于OC方法中返回值和参数之间必然有“发起者”和“SEL”隔着,所以“@:”自然而然就成了返回值和参数之间的分隔符。
当然我们还可以用@encode来得到我们想要的编码类型

NSString *encodeString =[NSString stringWithFormat:@"%s%s%s%s",@encode(void), @encode(id), @encode(SEL), @encode(id)];
3 将当前对象的类变为我们所创立的子类的类型,即更改isa指针
object_setClass(self, KVOClass);
4 将keyPath和观察者关联(associate)到我们的对象上

用下面这个函数可以很方便的将一个对象用键值对的方式绑定到一个目标对象上。
*假如想理解跟多可以查找《Effective Objective-C》的第10条

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
  • object
    目标对象

  • key
    绑定对象的键,相当于NSDictionary的key
    这里的key一般采用下面的方式公告:

static const void *SQKVOObserverKey = &SQKVOObserverKey;static const void *SQKVOKeyPathKey = &SQKVOKeyPathKey;

这样做是由于若想令两个键匹配到同一个值,则两者必需是完全相同的指针才行。

  • value
    绑定对象,相当于NSDictionary的value

  • policy
    绑定对象的缓存策略
    @property (nonatomic, weak) :OBJC_ASSOCIATION_ASSIGN
    @property (nonatomic, strong) :OBJC_ASSOCIATION_RETAIN_NONATOMIC
    @property (nonatomic, copy) :OBJC_ASSOCIATION_COPY_NONATOMIC
    @property (atomic, strong) :OBJC_ASSOCIATION_RETAIN
    @property (atomic, weak) :OBJC_ASSOCIATION_COPY

最后关联的代码:

objc_setAssociatedObject(self, SQKVOObserverKey, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);objc_setAssociatedObject(self, SQKVOKeyPathKey, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);

三.setValue()的实现

这个函数的目的主要是:
1.利用objc_msgSend触发原价类的setter
2.利用objc_msgSend触发观察者的回调方法

1. 触发原价的setter方法
    // 保存子类    Class KVOClass = [self class];        // 变回原价的类型,去触发setter    object_setClass(self, class_getSuperclass(KVOClass));    NSString *keyPath = objc_getAssociatedObject(self, SQKVOKeyPathKey);    NSString *setterString = [NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]];    SEL setter = NSSelectorFromString(setterString);    objc_msgSend(self, setter, newVale);
2. 调用观察者的回调方法
id observer = objc_getAssociatedObject(self, SQKVOObserverKey);    objc_msgSend(observer, @selector(SQ_observeValueForKeyPath:ofObject:changeValue:), keyPath, self, newVale);
3.改回KVO类
object_setClass(self, KVOClass);

四.实现空的回调方法

- (void)SQ_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object changeValue:(id)value {    }

五.调用自己设置的KVO

恭喜你看到这里,并且恭喜你已经成功了!

- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.        self.name = @"A";    [self SQ_addObserver:self forKeyPath:@"name"];        self.name = @"B";}- (void)SQ_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object changeValue:(id)value {    NSLog(@"%@.%@=%@", object, keyPath, value);}

六.代码

代码下载地址

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 2.RAC解析 – 自己设置KVO

发表回复