版本记录
版本号 | 时间 |
---|---|
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;
}
从上面我们可以看出对于有序不可变容器类NSArray
和NSDictionary
等等,都可以使用valueForKey :
这个方法来获取该属性。
有序可变容器类
下面我们看一下可变容器类,比如说NSMutableArray
和NSMutableDictionary
。比如对于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
上。如果对象属性是个NSMutableArray
、NSMutableSet
、NSMutableDictionary
等集合类型时,你给它添加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>:
。如果
receiver
是ManagedObject
,那么就不会继续搜索。如果上一步的方法没有找到,则搜索
set<Key>:
格式的方法,如果找到,那么发送给代理集合的NSMutableSet
最终都会调用set<Key>:
方法。 也就是说,mutableSetValueForKey
取出的代理集合修改后,用set<Key>:
重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。如果上一步的方法还没有找到,再检查类方法
+ (BOOL)accessInstanceVariablesDirectly
,如果返回YES
(默认行为),会按_<key>
,<key>
的顺序搜索成员变量名,如果找到,那么发送的NSMutableSet
消息方法直接交给这个成员变量处理。如果还是找不到,调用
valueForUndefinedKey:
可见,除了检查receiver
是ManagedObject
以外,其搜索顺序和mutableArrayValueForKey
基本一至。
后记
这几天回了老家没有网,博客更新的有点慢了,回来立刻就补充了一篇,希望对大家有所帮助,未完,待续~~