iOS KVC(一)基本了解
iOS KVC (二) 不可不知的赋值深层次原理
iOS KVC (三)不可不知的取值深层次原理
iOS KVC (四)keyPath的深度解析
iOS KVC (五)KVC几种典型的异常处理
iOS KVC (六) KVC容器类及深层次原理
iOS KVC(七) KVC正确性的验证
iOS KVC (八) KVC几种常见应用
iOS KVC (九) KVC模型转化(1) 模型打印 description, debugDescription
iOS KVC (十)模型转换(2)模型转换
容器类
容器类主要是指NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等有序容器和NSSet等无序容器。
有序不可变容器类
对于NSArray可以通过valueForKey:进行访问,如下代码所示
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *kvcArr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self demoArray];
}
- (void)demoArray
{
self.kvcArr = @[@1, @2, @3, @4];
NSObject *obj = [self valueForKey:@"kvcArr"];
NSLog(@"obj = %@", obj);
}
打印数据:
2018-05-17 14:46:38.862732+0700 KVC[30470:807433] obj = (
1,
2,
3,
4
)
对于NSDictionary也是一样的,下面看一下代码。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSDictionary *kvcDict;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self demoDictionary];
}
- (void)demoDictionary{
self.kvcDict = @{@"1" : @"One", @"2" : @"Two", @"3" : @"Three"};
NSObject *obj = [self valueForKey:@"kvcDict"];
NSLog(@"obj = %@", obj);
}
打印数据:
2018-05-17 14:49:18.219101+0700 KVC[30534:809791] obj = {
1 = One;
2 = Two;
3 = Three;
}
从上面我们可以看出对于有序不可变容器类NSArray和NSDictionary等等,都可以使用valueForKey :这个方法来获取该属性。
有序可变容器类
下面我们看一下可变容器类,比如说NSMutableArray和NSMutableDictionary。比如对于NSMutableArray可以使用下面方法获取数组。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *kvcArrM;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self demoNSMutableArray];
}
- (void)demoNSMutableArray
{
NSArray *arr = @[@1, @2, @3, @4];
self.kvcArrM = [NSMutableArray arrayWithArray:arr];
NSObject *obj = [self mutableArrayValueForKey:@"kvcArrM"];
NSLog(@"obj = %@", obj);
}
打印数据:
2018-05-17 15:37:17.358111+0700 KVC[31200:836978] 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:了。
下面我们就直接看代码
#import <UIKit/UIKit.h>
@interface ZFSecondVC : UIViewController
- (void)demoMutableArrayValueForKey;
- (void)addItem;
- (void)addAntherItem;
- (void)removeLastItem;
@end
#import "ZFSecondVC.h"
@interface ZFSecondVC ()
@property (nonatomic, strong) NSMutableArray *kvcArrM;
@end
@implementation ZFSecondVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (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);
}
#import "ViewController.h"
#import "ZFSecondVC.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
ZFSecondVC *VC = [[ZFSecondVC alloc] init];
[VC demoMutableArrayValueForKey];
[VC addItem];
[VC addAntherItem];
[VC removeLastItem];
}
打印数据:
2018-05-17 16:03:54.130555+0700 KVC[31726:857467] change = {
indexes = "<_NSCachedIndexSet: 0x600000222520>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind = 2;
new = (
Two
);
}
2018-05-17 16:03:54.130703+0700 KVC[31726:857467] arrM = (
One,
Two
)
2018-05-17 16:03:54.130910+0700 KVC[31726:857467] change = {
indexes = "<_NSCachedIndexSet: 0x600000222820>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
kind = 2;
new = (
Three
);
}
2018-05-17 16:03:54.131154+0700 KVC[31726:857467] arrM = (
One,
Two,
Three
)
2018-05-17 16:03:54.131295+0700 KVC[31726:857467] change = {
indexes = "<_NSCachedIndexSet: 0x600000222820>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
kind = 3;
old = (
Three
);
}
2018-05-17 16:03:54.131417+0700 KVC[31726:857467] 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基本一至。
网友评论