iOS中的KVC与KVO,NSNotification通知

问题: 什么是键值编码KVC,键路径是什么? 什么是键值观察KVO?

键值编码KVC: 键值编码是一种在NSKeyValueCoding非正式协议下使用字符串标志间接访问对象属性的一种机制,也就是访问对象变量的一种特殊的捷径。如果一个对象符合键值编码的约定,那么它的属性就可以通过一个准确的、唯一的字符串(键路径字符串)参数进行访问,类似于将所有对象看做字典Dictionary,键路径为key(实际为keypath),属性值即value,通过键路径访问属性值。键值编码的间接访问方式其实是传统实例变量的存取方法访问的一种替代,也就是另外一种可以访问对象变量的方法。其中注意键值编码可以暴力访问对象的任何变量,无论是否是pirvate私有类型的变量。

通常我们是通过存取方法来访问对象的属性的,getter方法返回属性的值,setter方法设置属性的值。对于实例对象,我们可以直接通过存取方法或者变量名来访问对象的属性,但是随着属性数量的增加和对象变量的嵌套深度增大,访问代码会随之增多。相比之下,通过键值编码就可以简洁而稳定的对所有属性进行访问。

键值编码是Cocoa框架中很基础的一个概念,像KVO键值观察、Cocoa绑定、Core Data等都是基于KVC的。

键路径: 键路径就是键值编码中某个属性的key,一个由连续键名组成的字符串,键名即属性名,键名之间用点隔开,用于指定一个连接在一起的对象性质序列。键路径使我们可以独立于模型实现的方式指定相关对象的性质。通过键路径,可以指定对象图中的一个任意深度的路径,使其指向相关对象的某个特定的属性。

键值观察KVO: 键值观察,是基于键值编码实现的一种观察者机制,提供了观察某一属性变化的监听方法,用来简化代码,优化逻辑和组织。

这里提供一个最基本的kvo和kvc的使用示例。假设有一个专业类Major,Major有一个专业名majorName私有属性;一个学生类Student,Student有一个姓名name私有属性,同时还有一个专业Major对象变量,这样就出现了简单的Major和Student的对象嵌套。Major和Student都继承自NSObject都符合键值编码约定,可以定义一个Student变量对其进行键值编码和键值观察

// 1.专业类模型:Major.h @interface Major : NSObject {
    @private
    NSString *majorName; // 私有实例变量专业名称 }
@end

// 2.学生类模型:Student.h @interface Student : NSObject {
    @private
    NSString *name; // 私有实例变量姓名 }
@property (nonatomic, strong)Major *major; // 学生专业 
// 3.kvc和kvo之间基本使用方法 #import "Student.h" #import "Major.h" 
@interface ViewController ()

@property (nonatomic, strong)Student *student;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化学生Student对象     _student = [[Student alloc] init];
    // 初始化学生的专业Major对象     _student.major = [[Major alloc] init];
    
    // 1.set: 通过KVC设置Student对象的值(可以强行访问private变量)     [_student setValue:@"Sam" forKey:@"name"];
    [_student setValue:@"Computer Science" forKeyPath:@"major.majorName"];
    
    // 2.get: 通过KVC读取Student对象的值(可以强行访问private变量)     NSLog(@"%@", [_student valueForKey:@"name"]);
    NSLog(@"%@", [_student valueForKeyPath:@"major.majorName"]);

    // 3.kvo:添加当前控制器为键路径major.majorName的一个观察者,如果major.majorName的值改变会通知当前控制器从而自动调用下面的observeValueForKeyPath函数,这里传递旧值和新值     [_student addObserver:self forKeyPath:@"major.majorName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    
    // 3s后改变major.majorName的值     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [_student setValue:@"Software Engineer" forKeyPath:@"major.majorName"];
    });
}

/** * 监听keyPath值的变化 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqual:@"major.majorName"]) {
        // 获取变化前后的值并打印         NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
        NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
        NSLog(@"major.majorName value changed:oldValue:%@ newValue:%@", oldValue,newValue);
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

/** * 移除观察者释放资源,防止资源泄漏 */
- (void)dealloc {
    [_student removeObserver:self forKeyPath:@"major.majorName"];
}
@end

可以看到‘major.majorName’就是一个由两个键连接起来的基本键路径,相当于用它可以直接访问属性的属性;使用addObserver函数将当前控制器注册为Student对象的观察者,当Student中键路径‘major.majorName’下的值发生改变时会通知当前控制器,触发observeValueForKeyPath监听函数。 ***

问题: NSNotification是同步还是异步? KVO是同步还是异步?NSNotification是全进程空间的通知吗?KVO呢?

NSNotification默认在主线程中通知是同步的,当通知产生时,通知中心会一直等待所有的观察者都收到并且处理通知结束,然后才会返回到发送通知的地方继续执行后面的代码;但可以将通知的发送或者将通知的处理方法放到子线程中从而避免通知阻塞。其中通知的发送可以添加到NSNotificationQueue异步通知缓冲队列中,也不会导致通知阻塞。NSNotificationQueue是一个通知缓冲队列,通常以FIFO先进先出的规则维护通知队列的发送,向通知队列添加通知有三种枚举类型:NSPostASAP、NSPostWhenIdle、和NSPostNow,分别表示尽快发送、空闲时发送和现在立刻发送,可以根据通知的紧急程度进行选择。

下面示例验证默认通知是同步的:

// 自定义消息的名称 #define MYNotificationTestName @"NSNotificationTestName" 
// 1.注册通知的观察者 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(process) name:MYNotificationTestName object:nil];
    
// 2.发出通知给观察者 NSLog(@"即将发出通知!");
[[NSNotificationCenter defaultCenter] postNotificationName:MYNotificationTestName object:nil];
NSLog(@"发出通知处的下一条代码!");
    
/** * 3.处理收到的通知 */
- (void)process {
    sleep(10); // 假设处理需要10s     NSLog(@"通知处理结束!");
}

打印结果

2017-01-21 22:21:30.501 SingleView[4579:146073] 即将发出通知!
2017-01-21 22:21:40.572 SingleView[4579:146073] 通知处理结束!
2017-01-21 22:21:40.572 SingleView[4579:146073] 发出通知处的下一条代码!

打印“即将发出通知”后,等了10s之后才打印出“通知处理结束”,然后才打印出@“发出通知处的下一条代码!”,“发出通知处的下一条代码!”是等到通知处理结束才打印出来的,说明通知是同步的。

可以通过将通知的发送语句或者通知的处理语句放到子线程实现通知的异步:

将通知的发送语句放到子线程:

NSLog(@"即将发出通知!");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:MYNotificationTestName object:nil];
});
NSLog(@"发出通知处的下一条代码!");

或者:

NSLog(@"即将发出通知!");
// 将通知放到通知异步缓冲队列 NSNotification *notification = [NSNotification notificationWithName:MYNotificationTestName object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
NSLog(@"发出通知处的下一条代码!");

将通知的处理放到子线程:

/** * 处理收到的通知 */
- (void)process {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(10); // 假设处理需要10s         NSLog(@"通知处理结束!");
    });
}

执行结果变为:

2017-01-21 22:31:09.259 SingleView[4711:151180] 即将发出通知!
2017-01-21 22:31:09.260 SingleView[4711:151180] 发出通知处的下一条代码!
2017-01-21 22:31:19.290 SingleView[4711:151252] 通知处理结束!

类似的,KVO也是同步的。 ***

问题:KVC(Key-Value-Coding)内部的实现:
一个对象在调用setValue的时候:

首先根据方法名找到运行方法的时候所需要的环境参数;
他会从自己isa指针结合环境参数,找到具体的方法实现的接口;
再直接查找得来的具体的方法实现。

KVO(Key-Value- Observing):当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名.

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

推荐阅读更多精彩内容