KVO

KVO即key-value-observing,键值观察,是一种观察者模式的实现机制(另一种为Notification)。KVO提供了一种机制,指定一个被观察对象(如student对象),当被观察对象student的某个属性(如name)发生改变时观察者对象(如teacher)会收到通知,同时作出相应处理(这孩子,怎么能随便改名呢,把你家长叫来)。

1.KVO的应用步骤
  • 注册观察者,实施监听:
[student addObserver:teacher forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

参数说明:
1)student:被观察者对象
2)teacher:观察者对象,该对象必须实现
observeValueForKeyPath:ofObject:change:context: 方法
3)forKeyPath:被观察者的属性名,必须和被观察者属性名相同
4)options:属性配置,有四个值:

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
//接收方法中传入属性变化后的新值,键为NSKeyValueChangeNewKey
NSKeyValueObservingOptionNew = 0x01,
//接收方法中传入属性变化前的旧值,键为NSKeyValueChangeOldKey
NSKeyValueObservingOptionOld = 0x02,
//注册之后会立刻调用接收方法,可以在程序第一次运行时做一些初始化操作,
如果配置了NSKeyValueObservingOptionNew,change参数内容会包含新值
NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
//接收方法会在属性变化前后分别调用一次,变化前的通知change参数包含键值对:notificationIsPrior = 1。
NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
}

5)context:接收一个C指针,可以为kvo的回调方法传值。

  • 在回调方法处理属性变化
    每当监听的keypath发生改变就会调用该方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == &PrivateKVOContext) {
if ([keyPath isEqualToString:@"name"]) {
            NSString *oldName = change[NSKeyValueChangeOldKey];
            NSString *newName = change[NSKeyValueChangeNewKey];
      }
   }
}

参数说明:
1)keyPath:被监听的keyPath , 用来区分不同的KVO监听;
2)object:被观察对象,可以获得修改后的属性值;
3)change:保存信息改变的字典(可能有旧的值,新的值等)

默认change参数会包含一个NSKeyValueChangeKindKey键值对,传递被监听 属性的变化类型:
enum {
//属性被重新设置
NSKeyValueChangeSetting = 1,
//表示更改的是集合属性,分别代表插入、删除、替换操作
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4
};
typedef NSUInteger NSKeyValueChange;

如果NSKeyValueChangeKindKey参数是针对集合属性的三个之一,change 参数还会包含一个NSKeyValueChangeIndexesKey键值对,表示变化的index。
4)context:上下文,可以用来区分不同的KVO监听。

  • 移除观察者
    因为添加观察者并不会retain,所以即使被观察者被释放了,其监听信息仍旧存在,如果不将观察者移除就会出现崩溃。
[student removeObserver:teacher forKeyPath:@"name" context:&PrivateKVOContext];
2.KVO的简单应用实例

KVO的常用场景是在MVC中同步model和UI,实现这样的需求:点击view的时候更新model的(person)数据并触发UI同步。可以看到应用KVO轻松的监听到模型数据的变化,进而在回调中更新UI。


pic8-1.gif
@interface ViewController ()
- (IBAction)randomAge:(UIButton *)sender;
@property (weak, nonatomic) IBOutlet UILabel *ageLabel;
@property (strong, nonatomic) Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc]init];
//创建观察者
[self.person addObserver:self
forKeyPath:@"myAge"
options:NSKeyValueObservingOptionNew
context:nil];

}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
//UI同步
self.ageLabel.text = [NSString stringWithFormat:@"%@",change[NSKeyValueChangeNewKey]];
}

- (IBAction)randomAge:(UIButton *)sender {
//更新model数据
self.person.myAge = arc4random() % 100 ;
}

- (void)dealloc {
//移除观察者
[self removeObserver:self forKeyPath:@"myAge"];
}
@end
3.手动键值观察

在以上KVO的应用中通过创建观察者,在属性变化时就会自动发出通知,而有些场景需要人为的控制通知的发送,这需要重写被观察者对象属性的getter/setter方法。

#import "Person.h"

@implementation Person
{
NSUInteger myAge;
}

- (NSUInteger)myAge {
return myAge;
}

- (void)setMyAge:(NSUInteger)newAge {
//发送通知:键值即将改变
[self willChangeValueForKey:@"myAge"];
myAge = newAge;
//发送通知:键值已经修改
[self didChangeValueForKey:@"myAge"];
}

//当设置键值之后,通过此方法,决定是否发送通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
//当key为myAge时,手动发送通知
if ([key isEqualToString:@"myAge"]) {
return NO;
}
//当为其他key时,自动发送通知
return [super automaticallyNotifiesObserversForKey:key];
}

@end
4.设置属性之间的依赖

假设我要监听一个color属性的变化,而一个color又与三原色相关,每种原色又由不同的组分构成,如此我们就需要监听N个原色属性的变化,每个原色属性变化就去设置color的值,我的天,太麻烦了,事情总是有解决的办法:KVO给我们提供了这种键之间的依赖方法

+ (NSSet *)keyPathsForValuesAffecting<Key>;
//设置属性依赖,属性greenComponent,依赖于属性lComponent和属性aComponent
+ (NSSet *)keyPathsForValuesAffectingGreenComponent {
return [NSSet setWithObjects:@"lComponent", @"aComponent",nil];
}

+ (NSSet *)keyPathsForValuesAffectingRedComponent {
return [NSSet setWithObject:@"lComponent"];
}

+ (NSSet *)keyPathsForValuesAffectingBlueComponent {
return [NSSet setWithObjects:@"lComponent", @"bComponent", nil];
}

+ (NSSet *)keyPathsForValuesAffectingColor {
return [NSSet setWithObjects:@"redComponent", @"greenComponent",@"blueComponent", nil];
}

通过color的属性依赖设置,在原色组分lComponent、aComponent、bComponent发生变化时观察者仍能收到color变化的通知。

5.KVO机制

前面已经了解到KVO的基本用法,那么KVO底层是如何实现的呢?Let me think,既然KVO能够监听属性的变化,那么在被观察者属性的set方法中判断如果属性值发生了变化就向观察者发送通知就可以实现,似乎很简单,但事实是KVO并没有对被观察者进行显示地重写set方法,那应该在哪重写set方法呢?
苹果通过isa混写(isa-swizzling)来实现KVO。当创建观察者观察一个对象person时,KVO机制会动态的创建一个名为NSKVONotifying_Person的新类(继承自person的本类Person)并将对象person的isa指针从Person类指向NSKVONotifying_Person(在这里只需要知道对象isa指针指向哪个类,对象就会到哪个类去寻找对应方法)。到这里我们应该找到了重写set方法的地方,只要在NSKVONotifying_Person中重写了Person的对应观察属性name的set方法,在被观察对象person的属性name被修改的时候会调用NSKVONotifying_Person中该属性的set方法,在set方法中完成相应的通知工作从而实现了属性变化的监听。由此可以知道只有当属性值是通过set方法修改的时KVO才有效,例如在Person中有这样一个方法:

//不是通过set方法修改的,不会触发KVO
- (void)changeName {
_name = @"newName";
}

KVO测试:

//辅助代码
static NSArray *ClassMethodNames(Class c) {
NSMutableArray *array = [NSMutableArray array];
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(c, &methodCount);
unsigned int i;
for (i = 0; i < methodCount; i++) {
[array addObject:NSStringFromSelector(method_getName(methodList[i]))];
}
free(methodList);
return array;
}

static void PrintDescription(NSString *name, id obj) {
struct objc_object *objcet = (__bridge struct objc_object *)obj;
Class cls = objcet->isa;
NSString *str = [NSString stringWithFormat:@"%@: %@\n\tNSObject class %s\n\tlibobjc class %s : super class %s\n\timplements methods <%@>",
name,
obj,
class_getName([obj class]),
class_getName(cls),
class_getName(class_getSuperclass(cls)),
[ClassMethodNames(cls) componentsJoinedByString:@", "]];
printf("%s\n", [str UTF8String]);
}

// 测试代码
Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];

[person2 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
PrintDescription(@"person1", person1);
PrintDescription(@"person2", person2);
//输出
person1: <Person: 0x60800002fec0>
NSObject class Person
libobjc class Person : super class NSObject
implements methods <.cxx_destruct, name, setName:>
person2: <Person: 0x60800002fee0>
NSObject class Person
libobjc class NSKVONotifying_Person : super class Person
implements methods <setName:, class, dealloc, _isKVOA>

可以看到被观察者对像person2的类已经被改为NSKVONotifying_Person了,并且重写了setName方法(在set方法里实现通知)。此外当我们试着打印person2对象的类([person2 class])时,仍然是Person。所以在NSKVONotifying_Person中除了重写相应的setter,还重写了class方法,让它返回原先的类。

PS: I am xinghun who is on the road.

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

推荐阅读更多精彩内容

  • 本文结构如下: Why? (为什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等开会阅读 1,638评论 1 21
  • 上半年有段时间做了一个项目,项目中聊天界面用到了音频播放,涉及到进度条,当时做android时候处理的不太好,由于...
    DaZenD阅读 3,013评论 0 26
  • 本文由我们团队的 纠结伦 童鞋撰写。 文章结构如下: Why? (为什么要用KVO) What? (KVO是什么...
    知识小集阅读 7,409评论 7 105
  • 一、概述 KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则其观察...
    DeerRun阅读 10,038评论 11 33
  • 1.KVC 关于 KVC 和 KVO ,我之前的总结文章有写过,但是趋于表面,没有探究其内部真正的实现原理和进阶用...
    Liberalism阅读 1,074评论 0 5