KVC解析(六) —— KVC容器类及深层次原理

版本记录

版本号 时间
V1.0 2017.09.12

前言

KVC相信大家再熟悉不过了,键值编码,可以解决很多问题,包括视图上的给UITextField占位文字颜色大小进行设置等等,还有很多地方可以用KVC,接下来几篇我们就深度解析一下KVC。总结一下,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。还是老规矩,由面到点,由浅到深,希望对大家有所帮助。感兴趣的可以看我写的另外几篇文章。
1. KVC解析(一) —— 基本了解
2. KVC解析(二) —— 不可不知的赋值深层次原理
3. KVC解析(三) —— 不可不知的取值深层次原理
4. KVC解析(四) —— keyPath的深度解析
5. KVC解析(五) —— KVC几种典型的异常处理

容器类

容器类主要是指NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等有序容器和NSSet等无序容器。


有序不可变容器类

对于NSArray可以通过valueForKey:进行访问,如下代码所示:

#import "JJKVCCollectionVC.h"

@interface JJKVCCollectionVC ()

@property (nonatomic, strong) NSArray *kvcArr;

@end

@implementation JJKVCCollectionVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self demoArray];
    
}

#pragma mark - Object Private Function

- (void)demoArray
{
    self.kvcArr = @[@1, @2, @3, @4];
    
    NSObject *obj = [self valueForKey:@"kvcArr"];
    
    NSLog(@"obj = %@", obj);
}

@end

下面看输出结果

2017-09-12 16:15:20.614 JJOC[3774:104163] obj = (
    1,
    2,
    3,
    4
)

对于NSDictionary也是一样的,下面看一下代码。

#import "JJKVCCollectionVC.h"

@interface JJKVCCollectionVC ()

@property (nonatomic, strong) NSDictionary *kvcDict;

@end

@implementation JJKVCCollectionVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];

    [self demoDictionary];

}

#pragma mark - Object Private Function

- (void)demoDictionary
{
    self.kvcDict = @{@"1" : @"One", @"2" : @"Two", @"3" : @"Three"};
    
    NSObject *obj = [self valueForKey:@"kvcDict"];
    
    NSLog(@"obj = %@", obj);
}

@end

下面看输出结果

2017-09-12 16:20:38.571 JJOC[4054:110989] obj = {
    1 = One;
    2 = Two;
    3 = Three;
}

从上面我们可以看出对于有序不可变容器类NSArrayNSDictionary等等,都可以使用valueForKey :这个方法来获取该属性。


有序可变容器类

下面我们看一下可变容器类,比如说NSMutableArrayNSMutableDictionary。比如对于NSMutableArray可以使用下面方法获取数组。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

下面看示例代码

#import "JJKVCCollectionVC.h"

@interface JJKVCCollectionVC ()

@property (nonatomic, strong) NSMutableArray *kvcArrM;

@end

@implementation JJKVCCollectionVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];

    [self demoNSMutableArray];
}

#pragma mark - Object Private Function

- (void)demoNSMutableArray
{
    NSArray *arr = @[@1, @2, @3, @4];
    self.kvcArrM = [NSMutableArray arrayWithArray:arr];
    
    NSObject *obj = [self mutableArrayValueForKey:@"kvcArrM"];
    
    NSLog(@"obj = %@", obj);
}

@end

下面看输出结果

2017-09-12 16:37:09.181 JJOC[4520:123892] obj = (
    1,
    2,
    3,
    4
)

下面我们看一下方法- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;的深层次原理。

  • 搜索insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex:或者 insert<Key>AdIndexes ,remove<Key>AtIndexes格式的方法。如果至少找到一个insert方法和一个remove方法,那么同样返回一个可以响应NSMutableArray所有方法代理集合(类名是NSKeyValueFastMutableArray2),那么给这个代理集合发送NSMutableArray的方法,以insertObject:in<Key>AtIndex:, removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes组合的形式调用。还有两个可选实现的接口:replaceOnjectAtIndex:withObject:,replace<Key>AtIndexes:with<Key>:

  • 如果上步的方法没有找到,则搜索set<Key>: 格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set<Key>:方法。 也就是说,mutableArrayValueForKey:取出的代理集合修改后,用set<Key>:重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。

  • 如果上一步的方法还还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),会按_<key><key>,的顺序搜索成员变量名,如果找到,那么发送的NSMutableArray消息方法直接交给这个成员变量处理。

  • 如果还是找不到,则调用valueForUndefinedKey:

  • 关于mutableArrayValueForKey:的适用场景,我在网上找了很多,发现其一般是用在对NSMutableArray添加Observer上。如果对象属性是个NSMutableArrayNSMutableSetNSMutableDictionary等集合类型时,你给它添加KVO时,你会发现当你添加或者移除元素时并不能接收到变化。因为KVO的本质是系统监测到某个属性的内存地址或常量改变时,会添加上- (void)willChangeValueForKey:(NSString *)key- (void)didChangeValueForKey:(NSString *)key方法来发送通知,所以一种解决方法是手动调用者两个方法,但是并不推荐,你永远无法像系统一样真正知道这个元素什么时候被改变。另一种便是利用使用mutableArrayValueForKey:了。

下面我们就直接看代码

1. JJKVCCollectionVC.h
#import <UIKit/UIKit.h>

@interface JJKVCCollectionVC : UIViewController

- (void)demoMutableArrayValueForKey;

- (void)addItem;

- (void)addAntherItem;

- (void)removeLastItem;

@end
2. JJKVCCollectionVC.m
#import "JJKVCCollectionVC.h"

@interface JJKVCCollectionVC ()

@property (nonatomic, strong) NSMutableArray *kvcArrM;

@end

@implementation JJKVCCollectionVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"kvcArrM"];
}

#pragma mark - Object Private Function

- (void)demoMutableArrayValueForKey
{
    self.kvcArrM = [NSMutableArray array];
    [self addObserver:self forKeyPath:@"kvcArrM" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

- (void)addItem
{
    [self.kvcArrM addObject:@"One"];
}

- (void)addAntherItem
{
    [[self mutableArrayValueForKey:@"kvcArrM"] addObject:@"Two"];
    [[self mutableArrayValueForKey:@"kvcArrM"] addObject:@"Three"];
}

- (void)removeLastItem
{
    [[self mutableArrayValueForKey:@"kvcArrM"] removeLastObject];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"change = %@", change);
    NSLog(@"arrM = %@", self.kvcArrM);
}

@end
3. JJKVCTestVC.m
#import "JJKVCTestVC.h"
#import "JJKVCCollectionVC.h"

@interface JJKVCTestVC ()

@end

@implementation JJKVCTestVC

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    JJKVCCollectionVC *collectionVC = [[JJKVCCollectionVC alloc] init];
    
    [collectionVC demoMutableArrayValueForKey];
    [collectionVC addItem];
    [collectionVC addAntherItem];
    [collectionVC removeLastItem];
}

@end

下面我们看输出结果

2017-09-12 17:55:38.831 JJOC[6637:175428] change = {
    indexes = "<_NSCachedIndexSet: 0x6000002232a0>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
    kind = 2;
    new =     (
        Two
    );
}
2017-09-12 17:55:38.832 JJOC[6637:175428] arrM = (
    One,
    Two
)
2017-09-12 17:55:38.832 JJOC[6637:175428] change = {
    indexes = "<_NSCachedIndexSet: 0x600000223300>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
    kind = 2;
    new =     (
        Three
    );
}
2017-09-12 17:55:38.832 JJOC[6637:175428] arrM = (
    One,
    Two,
    Three
)
2017-09-12 17:55:38.833 JJOC[6637:175428] change = {
    indexes = "<_NSCachedIndexSet: 0x600000223300>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
    kind = 3;
    old =     (
        Three
    );
}
2017-09-12 17:55:38.833 JJOC[6637:175428] arrM = (
    One,
    Two
)

从上面可以看出,可变数组里面元素的改变可以被KVO监听到了。当调用[self.kvcArrM addObject:@"One"];时不会触发KVO,只有调用方式改变为[[self mutableArrayValueForKey:@"kvcArrM"] addObject:@"Two"];才会触发KVO。


无序可变容器类

对于无序可变容器常用的就是NSMutableSet,对于可变无序容器类和上面讲的可变有序容器类的数组有点相似,主要是下面这个方法。

- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

下面看一下调用该方法底层深层次的一些东西。

  • 搜索addObject<Key>Object: , remove<Key>Object: 或者 add<Key> , remove<Key>格式的方法
    如果至少找到一个insert方法和一个remove方法,那么同样返回一个可以响应NSMutableSet所有方法代理集合(类名是NSKeyValueFastMutableSet2),那么给这个代理集合发送NSMutableSet的方法,以addObject<Key>Object: , remove<Key>Object: 或者 add<Key> , remove<Key>组合的形式调用。还有两个可选实现的接口:intersect<Key> , set<Key>:

  • 如果receiverManagedObject,那么就不会继续搜索。

  • 如果上一步的方法没有找到,则搜索set<Key>: 格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set<Key>:方法。 也就是说,mutableSetValueForKey取出的代理集合修改后,用set<Key>: 重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。

  • 如果上一步的方法还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),会按_<key>, <key>的顺序搜索成员变量名,如果找到,那么发送的NSMutableSet消息方法直接交给这个成员变量处理。

  • 如果还是找不到,调用valueForUndefinedKey:
    可见,除了检查receiverManagedObject以外,其搜索顺序和mutableArrayValueForKey基本一至。

后记

这几天回了老家没有网,博客更新的有点慢了,回来立刻就补充了一篇,希望对大家有所帮助,未完,待续~~

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

推荐阅读更多精彩内容