深入理解一手KVO

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

来给监听加个断点,看下做了什么事情

xfQ+Nw0YmskAAAAAElFTkSuQmCC.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);
}

调用:

kRtFt+vzLywAAAABJRU5ErkJggg==.png

同理p1的类型是子类: RXPerson

如何使用kvo监听容器类的变化

一个需求,数据源数组变化之后需要立即刷新视图, kvo监听是一个比较好的方法.

但是会发现当你用数组去 addObject: 的时候,是不会收到任何值的变化的通知.

代码如下:

image.png

原因: 之前已经说过了是通过重写对象的set方法,由于数组里的 addObject: 并没有重写.所以肯定不管怎么变化都不会收到通知的


解决方法: 利用kvc的特性
NSMutableArray *temArr = [self.p1 mutableArrayValueForKey:@"sonArr"];
[temArr addObject:@"1"];
image.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

结尾: 有一年多没更新了,以后会陆续把笔记整理出来开放,这些笔记只是我自己在当时所理解的东西,难免存在差异,有理解错误的地方希望可以提出来纠正

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容