iOS底层探索20、KVC 原理

Key-Value Coding Programming Guide 苹果文档
KVC: Key-Value Coding

image.png

一、KVC 的简单使用

1、普通赋值

1.1、setter -- llvm
    MyPerson *person = [[MyPerson alloc] init];
    person.name      = @"setName";
    person.age       = 18;
    person->myName   = @"yName";
    NSLog(@"%@ - %d - %@",person.name,person.age,person->myName);
// 输出:setName - 18 - yName
1.2、Key-Value Coding KVC
[person setValue:@"张三" forKey:@"name"];

2、集合类型

    person.array = @[@"1",@"2",@"3"];
    // 修改数组
    // person.array[0] = @"100";
    // 1: 建一个新的数组 - KVC 赋值
    NSArray *array = [person valueForKey:@"array"];
    array = @[@"100",@"2",@"3"];
    [person setValue:array forKey:@"array"];
    NSLog(@"%@",[person valueForKey:@"array"]);
    // 输出:100 2 3
    // 2
    NSMutableArray *mArray = [person mutableArrayValueForKey:@"array"];
    mArray[0] = @"200";
    NSLog(@"%@",[person valueForKey:@"array"]);
    // 输出:200 2 3

3、访问非对象属性

以结构体为例:

// 结构体
typedef struct {
    float x, y, z;
} ThreeFloats;


    ThreeFloats floats = {1.,2.,3.};
    NSValue *value     = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [person setValue:value forKey:@"threeFloats"];// 设置
    NSValue *value1    = [person valueForKey:@"threeFloats"];// 取值
    NSLog(@"%@",value1);
    // 输出:{length = 12, bytes = 0x0000803f0000004000004040}
    ThreeFloats th;
    [value1 getValue:&th];
    NSLog(@"%.2f-%.2f-%.2f",th.x,th.y,th.z);
    // 输出:1.00-2.00-3.00

4、keyPath

    MyStudent *student = [MyStudent alloc];
    student.subject    = @"subStr吗";
    person.student     = student;
    [person setValue:@"subStr啊" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
    // 输出:subStr啊

5、数组Array的操作

#pragma mark - array 取值
- (void)arrayDemo{
    MyStudent *p = [MyStudent new];
    p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
    NSArray *arr = [p valueForKey:@"penArr"]; // 动态成员变量
    NSLog(@"pens = %@", arr);
    /* pens = (
        pen0,
        pen1,
        pen2,
        pen3
    )*/
    //NSLog(@"%@",arr[0]);
    NSLog(@"%d",[arr containsObject:@"pen9"]);// 0 false
    // 遍历
    NSEnumerator *enumerator = [arr objectEnumerator];
    NSString* str = nil;
    while (str = [enumerator nextObject]) {
        NSLog(@"%@", str);
    }
}

6、字典Dictionary

- (void)dictionaryDemo {
    
    // 1:
    NSDictionary *dict = @{
                           @"name":@"李四",
                           @"nick":@"小四",
                           @"subject":@"iOS",
                           @"age":@18,
                           @"length":@180
                           };
    MyStudent *p = [[MyStudent alloc] init];
    // 字典转模型
    [p setValuesForKeysWithDictionary:dict];
    /**
      (lldb) p *$0
      (NSDictionary) $1 = {
        [0] = {
          key = 0x000000010585e3e0 @"age"
          value = 0x947567b2920d34b5 (int)18
        }
        [1] = {
          key = 0x000000010585e3a0 @"subject"
          value = 0x000000010585e3c0 @"iOS"
        }
        [2] = {
          key = 0x000000010585e360 @"nick"
          value = 0x000000010585e380 @"小四"
        }
        [3] = {
          key = 0x000000010585e0c0 @"name"
          value = 0x000000010585e340 @"李四"
        }
        [4] = {
          key = 0x000000010585e400 @"length"
          value = 0x947567b2920d3ed5 (int)180
        }
      }
    */


    // 2:
    // 数组 转 模型 到 字典
    NSArray *array = @[@"name",@"age"];
    NSDictionary *dict2 = [p dictionaryWithValuesForKeys:array];
    NSLog(@"%@",dict2);
    /**
    (lldb) p *$2
    (NSDictionary) $3 = {
      [0] = {
        key = 0x000000010585e0c0 @"name"
        value = 0x000000010585e340 @"李四"
      }
      [1] = {
        key = 0x000000010585e3e0 @"age"
        value = 0x947567b2920d34b5 (int)18
      }
    }
    */
}

7、KVC 消息传递

- (void)arrayMessagePass{
    NSArray *array = @[@"hank",@"anmyli",@"kod",@"CC"];
    NSArray *lenStr= [array valueForKeyPath:@"length"];
    NSLog(@"%@",lenStr);// 消息从 array 传递给了 string
    // string.length
//    (
//        4,
//        6,
//        3,
//        2
//    )
    NSArray *nStr= [array valueForKeyPath:@"uppercaseString"];// lowercaseString uppercaseString
    NSLog(@"%@",nStr);// 字母大写
    // 输出:
//    (
//        HANK,
//        ANMYLI,
//        KOD,
//        CC
//    )
}

8、NSSet

8.1)setKVC
- (void)setNesting{

//    NSSet *s = [NSSet setWithArray:@[@"1",@"3",@"8",@"4"]];
//    NSLog(@"%@",s);// 
    
    NSMutableSet *personSet1 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        MyStudent *person = [MyStudent new];
        NSDictionary* dict = @{
            @"name":@"Tom",
            @"age":@(18+i),
            @"nick":@"Cat",
            @"length":@(175 + 2*arc4random_uniform(6)),
        };
        [person setValuesForKeysWithDictionary:dict];
        [personSet1 addObject:person];
    }
    NSLog(@"personSet1 = %@", [personSet1 valueForKey:@"length"]);
//    输出:
//    personSet1 = {(
//        175,
//        181,
//        177,
//        179
//    )}

    NSMutableSet *personSet2 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        MyPerson *person = [MyPerson new];
        NSDictionary* dict = @{
            @"name":@"jerry",
            @"age":@(18+i),
//            @"nick":@"Cat",
//            @"length":@(175 + 2*arc4random_uniform(6)),
        };
        [person setValuesForKeysWithDictionary:dict];
        [personSet2 addObject:person];
    }
    NSLog(@"personSet2 = %@", [personSet2 valueForKey:@"age"]);
//    输出:
//    personSet2 = {(
//        21,
//        20,
//        23,
//        19,
//        22,
//        18
//    )}

    // 嵌套set
    NSSet* nestSet = [NSSet setWithObjects:personSet1, personSet2, nil];
    // 交集
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.name"];
    NSLog(@"arr1 = %@", arr1);
//    输出:
//    arr1 = {(
//        Tom,
//        jerry
//    )}
}
8.2)setarray 区别:

1、代码调试
执行如下代码:

    NSMutableSet *personSet1 = [NSMutableSet set];
    NSMutableArray *personArr1 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        MyStudent *person = [MyStudent new];
        NSDictionary* dict = @{
            @"name":@"Tom",
            @"age":@(18+i),
            @"nick":@"Cat",
            @"length":@(175 + 2*arc4random_uniform(6)),
        };
        [person setValuesForKeysWithDictionary:dict];
        [personSet1 addObject:person];
        [personArr1 addObject:person];
    }

打个断点在 for 所在行,调试信息如下:

(lldb) p malloc_size((__bridge void *)personArr1)
(size_t) $0 = 48
(lldb) p malloc_size((__bridge void *)personSet1)
(size_t) $1 = 32
(lldb) 

继续执行,并调试:

(lldb) po personArr1
<__NSArrayM 0x600003e33f60>(
<MyStudent: 0x600003e307e0>,
<MyStudent: 0x600003e33f90>,
<MyStudent: 0x600003e33fc0>,
<MyStudent: 0x600003e30750>,
<MyStudent: 0x600003e324f0>,
<MyStudent: 0x600003e308a0>
)

(lldb) po personSet1
{(
    <MyStudent: 0x600003e33fc0>,
    <MyStudent: 0x600003e33f90>,
    <MyStudent: 0x600003e30750>,
    <MyStudent: 0x600003e324f0>,
    <MyStudent: 0x600003e307e0>,
    <MyStudent: 0x600003e308a0>
)}

8.2)官方文档 OC 下 - Values and Collections

NSSet - NSSet :

image.png

NSArray:

image.png

这里把字典的图示也放在这里便于比较,Dictionary:

image.png

setarray主要区别:
array有序,数据可重复;
set无序,数据不重复。

二、KVC 原理探究 基于苹果官方文档

Accessor Search Patterns 文档地址

1、Search Pattern for the Basic Setter

image.png

setter 的流程:

  1. 先找set<Key>: or _set<Key>
  2. 若没找到且accessInstanceVariablesDirectly returns YES,则继续找_<key>, _is<Key>, <key>, or is<Key>;
  3. 没找着则: invoke setValue:forUndefinedKey:.。默认情况下会引发一个异常,但NSObject的子类可能提供特定于键的行为

2、Search Pattern for the Basic Getter

image.png

getter流程:

  1. 实例中搜索,先找 get<Key>, <key>, is<Key>, or _<key>,找到了就调用去·5·中处理结果;没找着则继续下一步;
  2. 搜索名称与模式 countOf<Key>objectIn<Key>AtIndex: (对应于NSArray类定义的基本方法) 以及 <key>AtIndexes: (对应于NSArray 方法 objectsAtIndexes:);
    2.1.、如果找到第1个或至少两个中的一个,则创建一个集合代理对象,该对象响应所有NSArray方法并返回该方法。否则,继续执行步骤3;
    2.2、代理对象随后将接收到的任何NSArray消息转换为countOf<Key>objectIn<Key>AtIndex:<Key> AtIndexes:的一些组合,这些组合将消息发送给创建它的符合键值编码的对象。如果原始对象还实现了一个名为get<Key>:range:的可选方法,代理对象也会在适当的时候使用它。实际上,代理对象与键值编码兼容的对象一起工作,允许底层属性像NSArray一样工作,即使它不是NSArray
  3. 如果没有找到简单的访问方法或数组访问方法组,查找下面三个方法,countOf<Key>enumeratorOf<Key>,memberOf<Key>:(对应于NSSet类定义的原语方法)。
    3.1、如果这三个方法都找到了,创建一个集合代理对象,它响应所有NSSet方法并返回那个。否则,继续执行步骤4。
    3.2、这个代理对象随后将它接收到的任何NSSet消息转换为countOf<Key>enumeratorOf<Key>memberOf<Key>:消息的组合,并发送给创建它的对象。实际上,代理对象与遵循键值编码的对象一起工作,允许底层属性像NSSet一样运行,即使它不是NSSet
  4. 如果没有找到简单的访问方法或集合访问方法组,并且如果接收方的类方法accessinstancevariables返回YES,那么按照顺序搜索一个实例变量:_<key>_is< key><key>,或is< key>。如果找到,直接获取实例变量的值并继续执行步骤5。否则,继续执行步骤6。
  5. 如果检索到的属性值是一个对象指针,只需返回结果。
    如果值是NSNumber支持的标量类型,将其存储在NSNumber实例中并返回。
    如果结果是NSNumber不支持的标量类型,转换为NSValue对象并返回它。
  6. 如果所有其他方法都失败,则调用valueForUndefinedKey:。这在默认情况下会引发一个异常,但是NSObject的一个子类可能提供特定于键的行为。

3、Getting/Setting Attribute Values Using Keys

对应的 Apple 地址

image.png

image.png

更多信息在苹果文档中比较详细,这里不再多做介绍。

三、自定义 KVC

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