Key-Value Observing : 键值监听
KVO则必须实现NSKeyValueObServing协议,但不用担心,因为NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO
kvo是用来观察监听对象的属性变化(非成员变量)
@interface Person : NSObject
{
@public
NSString *_var1;
}
@property (nonatomic, copy) NSString *var2;
@end
验证代码:
self.p1 = [[Person alloc]init];
[self.p1 addObserver:self forKeyPath:@"var1" options:(NSKeyValueObservingOptionNew) context:nil];
[self.p1 addObserver:self forKeyPath:@"var2" options:(NSKeyValueObservingOptionNew) context:nil];
改变值:
self.p1.var2 = @"222";
self.p1->_var1 = @"111";
-----------------
输出结果: 222
来给监听加个断点,看下做了什么事情
![](https://img.haomeiwen.com/i1315383/bba0921b162bdf31.png)
我们可以过掉监听代码之后,person的类型虽然还是person类型,但是内部的isa指针已经指向另外一个类了,
这个类是动态生成的.
这里直接给出结果:
kvo其实是重写了set方法,动态创建该类的子类对象,然后子类属性值改变了之后通知给观察者
注意一点:
继承不意味着子类拥有父类所有的方法,继承意味着,子类方法里面没有这个方法,系统会去父类里面去寻找这个方法
介绍1个不常用的参数:
监听到通知之后的
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
里面有个 new
知道这些知道,我们完全可以手动实现一个
步骤:
1.创建NSObject分类,实现动态子类以及重写改类的set方法
2.在使用的地方调用
代码如下:
@implementation NSObject (RXKvo)
- (void)RXKvo_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
//新建类
Class newClass = objc_allocateClassPair(self.class, @"RXPerson".UTF8String, 0);
//注册类
objc_registerClassPair(newClass);
//添加方法
class_addMethod(newClass, @selector(setVar2:), (IMP)changeFunction, "v@:");
//修改类型
object_setClass(self, newClass);
}
void changeFunction(id self,SEL _cmd,NSString *var) {
NSLog(@"%@",var);
}
调用:
![](https://img.haomeiwen.com/i1315383/c2be96c85517787d.png)
同理p1的类型是子类: RXPerson
如何使用kvo监听容器类的变化
一个需求,数据源数组变化之后需要立即刷新视图, kvo监听是一个比较好的方法.
但是会发现当你用数组去 addObject: 的时候,是不会收到任何值的变化的通知.
代码如下:
![](https://img.haomeiwen.com/i1315383/1d6fdb56557fe61f.png)
原因: 之前已经说过了是通过重写对象的set方法,由于数组里的 addObject: 并没有重写.所以肯定不管怎么变化都不会收到通知的
解决方法: 利用kvc的特性
NSMutableArray *temArr = [self.p1 mutableArrayValueForKey:@"sonArr"];
[temArr addObject:@"1"];
![](https://img.haomeiwen.com/i1315383/6e30aadb5197e352.png)
大家可以看到通过:
mutableArrayValueForKey:这种形式返回一个新创建的子类
举一反三 集合类会有类似的方法:
mutableArrayValueForKey:
mutableOrderedSetValueForKey:
mutableSetValueForKey:
......
介绍下键值对里 kind的值
typedef NS_OPTIONS(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1, //值发生变化 kind=1
NSKeyValueChangeInsertion = 2, //插入
NSKeyValueChangeRemoval = 3, //移除
NSKeyValueChangeReplacement = 4 //替换
};
其他拓展
objc_msgSend() 函数默认会包含几个参数,我们在buildSetting 搜索 send
我们把 objc_msgSend 语法检查关闭,我们可以直接调用
//第一个参数是调用者,第二个参数是方法,第三个是参数,可继续添加多个参数
objc_msgSend(self.p1, @selector(setVar2:),@"111",@"111");
重点在于参数
//IMP指针函数默认是带形参的,严格依照顺序排列,参数名可以随意修改
void changeFunction(id self,SEL cmd,NSString *var ......) {
}
设备的CPU架构类型(指令集)
模拟器:
4s-5: i386
5s(包括5S)以后: x86_64
真机:
5c以前是: arm32位架构(5c是32位性能最强的机型)
5s之后架构: arm64
在x86架构下,也就是模拟器
objc_msgSend()在不同架构下如上代码 发送的参数 收集到的结果并不一致,是因为里面定义的类型在不同架构下的参数是不一样,
代码演示:
//值变化代码
objc_msgSend(self.p1, @selector(setVar2:),@"111");
//关联函数代码
class_addMethod(NewClass, @selector(setVar2:), (IMP)changeFunction,"");
//打印值变化代码
void changeFunction(id self,SEL cmd,NSString *var) {
NSLog(@"收到..%@",var);
}
//输出结果
---------------------------------------
//在arm64:
2018-03-29 17:14:13.253638+0800 TranslateVoice[2426:1737249] 收到..{(
<UITouch: 0x15bd20220> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x15be0cbc0; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x1c424abc0>; layer = <UIWindowLayer: 0x1c402af20>> view: <UIView: 0x15be0db30; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x1c402b420>> location in window: {138.5, 368.5} previous location in window: {138.5, 368.5} location in view: {138.5, 368.5} previous location in view: {138.5, 368.5}
)}
---------------------------------------
//x86下:
2018-03-29 17:16:14.039398+0800 TranslateVoice[26380:578592] 收到..111
2018-03-29 17:16:14.220402+0800 TranslateVoice[26380:578592] 收到..111
结尾: 有一年多没更新了,以后会陆续把笔记整理出来开放,这些笔记只是我自己在当时所理解的东西,难免存在差异,有理解错误的地方希望可以提出来纠正
网友评论