SDWebImage学习笔记之KVC

KVC概述

KVC全称是Key-Value-Coding,NSObject类及其子类和内建基本数据类型都可以通过KVC的方法赋值和取值,不需要通过get和set。


KVC使用

以一个保存多个字典的数组的取值为例:

NSArray *jjArray = @[@{@"name": @"aa", @"age": @(15)},
                    @{@"name": @"bb", @"age": @(16)},
                    @{@"name": @"cc", @"age": @(17)}];
// 遍历数组获取key为"name"时对应的value
NSMutableArray *nameArray1 = [NSMutableArray array];
for (NSDictionary *dict in jjArray) {
    [nameArray1 addObject:dict[@"name"]];
}
NSLog(@"nameArray1: %@", nameArray1);
// KVC方法筛选key为"name"时对应的value,返回一个数组
NSArray *nameArray2 = [jjArray valueForKey:@"name"];
NSLog(@"nameArray2: %@", nameArray2);

日志输出:

nameArray1: (
    aa,
    bb,
    cc
)
nameArray2: (
    aa,
    bb,
    cc
)

遍历数组和KVC的方式得到了相同的结果,但明显KVC方式的代码量要远小于遍历的方式。

对于复杂的数据结构,例如自定义类,KVC也可以快速的取值。

// 定义一个Teacher类
@interface Teacher : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

// 定义一个Student类
@interface Student : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

// ViewController.m
NSMutableArray *aArray = [[NSMutableArray alloc] initWithCapacity:0];
Teacher *teacher = [[Teacher alloc] init];
// 属性赋值
teacher.name = @"teacher";
teacher.age = 30;
// KVC赋值
//    [teacher setValue:@"teacher" forKey:@"name"];
//    [teacher setValue:@30 forKey:@"age"];
[aArray addObject:teacher];
for (int i = 0; i < 3; i++) {
    Student *student = [[Student alloc] init];
    student.name = [NSString stringWithFormat:@"student%d", i];
    student.age = i;
    [aArray addObject:student];
}
// KVC方法筛选每个对象中key值为"name"的value
NSArray *teacherArray = [aArray valueForKey:@"name"];
// aArray保存了1个Teacher对象和3个Student对象,每个对象中都有name属性,所以teacherArray数组会有4个值。
NSLog(@"teacherArray: %@", teacherArray);

日志输出:

nameArray: (
    teacher,
    student0,
    student1,
    student2
)

对于自定义类中含有自定义类的情况,valueForKey: 方法已经无法获取到正确的值了,需要调用valueForKeyPath: 方法传入属性的路径来获取(xxx.xxx.xxx)。

// Teacher.h
@interface Teacher : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
// 在Teacher类中增加一个Student类型的数据属性students
@property (nonatomic, strong) NSMutableArray *students;

@end

// ViewController.m
NSMutableArray *aArray = [[NSMutableArray alloc] initWithCapacity:0];
Teacher *teacher = [[Teacher alloc] init];
teacher.name = @"teacher";
teacher.age = 30;
teacher.students = [NSMutableArray array];
[aArray addObject:teacher];
for (int i = 0; i < 3; i++) {
    Student *student = [[Student alloc] init];
    student.name = [NSString stringWithFormat:@"student%d", i];
    student.age = i;
    [teacher.students addObject:student];
}
NSArray *nameArray = [aArray valueForKey:@"name"];
NSArray *studentNameArray = [aArray valueForKeyPath:@"students.name"];
NSLog(@"nameArray: %@, studentNameArray: %@", nameArray, studentNameArray);

日志输出:

nameArray: (
    teacher
), 
studentNameArray: (
        (
        student0,
        student1,
        student2
    )
)

在这段代码中,[aArray valueForKey:@"name"]方法只获取到了teacher对象name属性的值,而[aArray valueForKeyPath:@"students.name"]才能获取到student对象name属性的值。


KVC的实现

在网上查了一些资料,对KVC的键值查找方式、KVC的实现原理和内部机制做了详细的阐述,本文直接引用过来。

KVC键值查找

setValue:forKey:搜索方式

1、首先搜索setKey:方法。(key指成员变量名,首字母大写)

2、上面的setter方法没找到,如果类方法accessInstanceVariablesDirectly返回YES。那么按 _key,_isKey,key,iskey的顺序搜索成员名。(NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)

3、如果没有找到成员变量,调用setValue:forUnderfinedKey:

valueForKey:的搜索方式

1、首先按getKey,key,isKey的顺序查找getter方法,找到直接调用。如果是BOOL、int等内建值类型,会做NSNumber的转换。

2、上面的getter没找到,查找countOfKey、objectInKeyAtindex、KeyAtindexes格式的方法。如果countOfKey和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法。

3、还没找到,查找countOfKey、enumeratorOfKey、memberOfKey格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合。
4、还是没找到,如果类方法accessInstanceVariablesDirectly返回YES。那么按 _key,_isKey,key,iskey的顺序搜索成员名。

5、再没找到,调用valueForUndefinedKey。
KVC实现原理

KVC运用了isa-swizzing技术。isa-swizzing就是类型混合指针机制。KVC通过isa-swizzing实现其内部查找定位。isa指针(is kind of 的意思)指向维护分发表的对象的类,该分发表实际上包含了指向实现类中的方法的指针和其他数据。

比如说如下的一行KVC代码:

[site setValue:@"sitename" forKey:@"name"];

//会被编译器处理成

SEL sel = sel_get_uid(setValue:forKey);
IMP method = objc_msg_loopup(site->isa,sel);
method(site,sel,@"sitename",@"name");

每个类都有一张方法表,是一个hash表,值是还书指针IMP,SEL的名称就是查表时所用的键。
SEL数据类型:查找方法表时所用的键。定义成char*,实质上可以理解成int值。
IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。

KVC内部机制

一个对象在调用setValue的时候进行了如下操作:

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

引用地址:http://www.cnblogs.com/zy1987/p/4616063.html


KVC在SDWebImage中的应用

SDWebImage库Downloader模块中的SDWebImageDownloaderOperation类负责执行下载任务,它定义了一个属性callbackBlocks

typedef NSMutableDictionary SDCallbacksDictionary;

@property (strong, nonatomic, nonnull) NSMutableArray *callbackBlocks;

callbackBlocks是一个可变素组,其中每个元素是SDCallbacksDictionary类型的字典,用键值对的方式保存每个下载任务的progressBlock和completedBlock。progressBlock和completedBlock由外部传入,负责下载过程中和下载完成时或下载异常情况的处理。

假如想取到其中所有的progressBlock或completedBlock,一种方法是遍历callbackBlocks数组,根据key来获取value,保存到一个新的数组中。另一种快速的方式就是KVC,源代码如下:

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    LOCK(self.callbacksLock);
    [self.callbackBlocks addObject:callbacks];
    UNLOCK(self.callbacksLock);
    return callbacks;
}

- (nullable NSArray *)callbacksForKey:(NSString *)key {
    LOCK(self.callbacksLock);
    NSMutableArray *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
    UNLOCK(self.callbacksLock);
    // We need to remove [NSNull null] because there might not always be a progress block for each callback
    [callbacks removeObjectIdenticalTo:[NSNull null]];
    return [callbacks copy]; // strip mutability here
}

callbacksLock属性是一个信号量锁,初始值为1,表示同时期只能有一个线程来访问callbacks,保证callbacks数组在赋值和取值过程中的线程安全。

@property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock;
// 设置信号量为1 
_callbacksLock = dispatch_semaphore_create(1);

总结

虽然KVC在效率上要优于遍历的方式,但是滥用KVC会导致异常问题的出现,假如valueForKey: 或valueForKeyPath:传入了不存在的key值,那么就会导致程序崩溃。所以在没有把握的情况下,慎用KVC!

你可能感兴趣的:(SDWebImage学习笔记之KVC)