美文网首页
SDWebImage学习笔记之KVC

SDWebImage学习笔记之KVC

作者: Mr杰杰 | 来源:发表于2018-07-06 11:26 被阅读11次

    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<Student *> *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<NSString *, id> SDCallbacksDictionary;
    
    @property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *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<id> *)callbacksForKey:(NSString *)key {
        LOCK(self.callbacksLock);
        NSMutableArray<id> *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

          本文链接:https://www.haomeiwen.com/subject/bxpvuftx.html