OC底层原理(17)KVC

539 阅读14分钟

1.KVC 定义

官文说明

Xnip2022-07-22_18-25-52.png

翻译:

键值编码是由NSKeyValueCoding非正式协议启用的一种机制,对象采用该协议来提供对其属性的间接访问。当一个对象符合键值编码时,它的属性可以通过一个简洁、统一的消息传递接口通过字符串参数寻址。这种间接访问机制补充了实例变量及其关联访问器方法所提供的直接访问。

KVCOC中的定义:

Xnip2022-07-22_18-30-40.png

扯这么多,这不就是个 NSObject 的分类么,然后里面定义了一些列的方法。。。

2 KVC提供的API

2.1 常用方法

对于所有继承了NSObject的类型,也就是几乎所有的Objective-C对象都能使用KVC,下面是KVC最为重要的四个方法:

// 直接通过Key来取值
- (nullable id)valueForKey:(NSString *)key;              
// 通过Key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          
// 通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;      
// 通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  

2.2 其它方法

当然NSKeyValueCoding类别中还有其他的一些方法,这些方法在碰到特殊情况或者有特殊需求还是会用到的,这里做简要说明

// 默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;

// KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

// 这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

// 如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;

// 和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

// 如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;

// 输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

3. KVC设值/取值 顺序

KVC在内部是怎么根据这个key来设定值,又是怎么取值的?这是我们要探索的重点。

3.1 设值

文档说明

当调用setValue:forKey:代码时,底层的执行机制是怎样的呢?在官方文档中有相关的说明,见下图

Xnip2022-07-22_21-33-50.png

翻译: setValue:forKey:的默认实现,给定keyvalue参数作为输入,尝试在接收调用的对象内部设置一个名为key的属性为value 使用以下过程: 按照这个顺序查找名为set<Key>:_set<Key>的第一个访问器。如果找到,使用输入value调用它并完成。如果没有找到简单的访问器,并且类方法accessInstanceVariablesDirectly 返回 YES,那么按顺序查找一个名称为_<key>、_is< key>、<key>is< key>的实例变量。如果找到,直接用输入value设置变量,然后完成。 当发现没有访问器或实例变量时,调用setValue:forUndefinedKey:。这将在默认情况下引发异常,但NSObject的子类可能会提供特定于键的行为。

总结下官文,大概就是说:

  1. 首先按顺序查找名为set<Key>_set<Key> 的 setter访问器顺序查找,如果找到就调用它,去设置值

  2. 如果没有找到这些setter方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法的返回值

    • 返回YES,默认该方法会返回YES,执行第3条
    • 返回NO 执行 第4条
  3. 按顺序查找一个名称为_<key>、_is<key>、<key>is<key>的实例变量。如果找到,直接赋值value。没找到执行 第4条

  4. 系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

实例验证

[person setValue:@"张三" forKey:@"name"];为例,按照上面的总结来一一验证:

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    YJPerson *person = [[YJPerson alloc] init];
    [person setValue:@"张三" forKey:@"name"];
}
@end

运行项目:

Xnip2022-07-22_22-10-38.png

set<Key>_set<Key> 都存在,确实优先调用的 set<Key>,接下来把set<Key>去掉,看是否能按预期调用 _set<Key>

Xnip2022-07-22_22-13-02.png

都是ok的,我们把 setter 方法都去掉,给YJPerson添加实例变量_name、_isName、name、isName 大概这样:

@interface YJPerson : NSObject {
@public
    NSString *_isName;
    NSString *name;
    NSString *isName;
    NSString *_name;
}
@end
@implementation YJPerson @end

@interface ViewController () @end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    YJPerson *person = [[YJPerson alloc] init];
    [person setValue:@"张三" forKey:@"name"];

    NSLog(@"_isName = %@", person->_isName);
    NSLog(@"name    = %@", person->name);
    NSLog(@"isName  = %@", person->isName);
    NSLog(@"_name   = %@", person->_name);
}
@end

文档中的顺序_<key>、_is<key>、<key>is<key>

Xnip2022-07-22_22-33-12.png

正常,把 _name 去掉,再来:

Xnip2022-07-22_22-32-22.png

正常,把 _isNname 去掉,再来:

Xnip2022-07-22_22-34-40.png

正常,把 name 去掉,再来:

Xnip2022-07-22_22-35-31.png

正如上面总结的 第3条,是按那个顺序查找的赋值的。把这四个实例变量都去掉,看看有没有调用 setValue:forUndefinedKey:

Xnip2022-07-22_22-45-35.png

流程图总结:

333.png

3.2 取值

文档说明

当调用valueForKey:的代码时,底层的执行机制又是怎样的呢?在官方文档中有相关的说明,见下图:

11.png

根据上的官方内容,翻译之后可以得出如下实现机制: 的默认实现valueForKey:,给定一个key参数作为输入,执行以下过程,从接收valueForKey:调用的类实例中进行操作。

  1. 在实例中搜索找到的第一个名称为get<Key><key>is<Key>、 或 的访问器方法_<key>,按该顺序。如果找到,则调用它并使用结果继续执行步骤 5。否则继续下一步。

  2. 如果没有找到简单的访问器方法,则在实例中搜索名称与模式countOf<Key>objectIn<Key>AtIndex:(对应于NSArray类定义的原始方法)和<key>AtIndexes:(对应于NSArray方法objectsAtIndexes:)的方法。

    如果找到这些中的第一个和其他两个中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则,继续执行步骤 3。

    代理对象随后将任何NSArray接收到的一些组合的消息countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:消息给键-值编码创建它兼容的对象。如果原始对象还实现了一个可选的方法,其名称类似于get<Key>:range:,则代理对象也会在适当的时候使用它。实际上,与键值编码兼容的对象一起工作的代理对象允许底层属性表现得好像它是NSArray,即使它不是。

  3. 如果没有找到简单的访问方法或第2条中的访问方法组,寻找一个三重的方法命名countOf<Key>enumeratorOf<Key>memberOf<Key>:(对应于由所定义的原始的方法NSSet类)。

    如果找到所有三个方法,则创建一个响应所有NSSet方法的集合代理对象并返回该对象。否则,继续执行步骤 4。

    此代理对象随后将任何NSSet接收到的一些组合信息countOf<Key>enumeratorOf<Key>memberOf<Key>:消息以创建它的对象。实际上,与键值编码兼容的对象一起工作的代理对象允许底层属性表现得好像它是NSSet,即使它不是。

  4. 如果发现收集的访问方法没有简单的存取方法或者组,如果接收器的类方法accessInstanceVariablesDirectly返回YES,搜索名为实例变量_<key>_is<Key><key>,或者is<Key>,按照这个顺序。如果找到,直接获取实例变量的值并进行步骤5,否则进行步骤6。

  5. 如果检索到的属性值是一个对象指针,只需返回结果即可。 如果该值是 支持的标量类型NSNumber,则将其存储在一个NSNumber实例中并返回该实例。 如果结果是 NSNumber 不支持的标量类型,则转换为NSValue对象并返回。

  6. 如果所有其他方法都失败,请调用valueForUndefinedKey:. 默认情况下,这会引发异常,但 的子类NSObject可能会提供特定于键的行为。

[person valueForKey:@"name"];为例

  • getter方法的调用顺序是:

    1. getName
    2. name
    3. isName
    4. _name
  • 没有找到上面访问器方法,继续找:

    • countOf<Key>
    • objectIn<Key>AtIndex: 对于NSArrayobjectAtIndex
    • <key>AtIndexes: 对于NSArrayobjectsAtIndexes
  • 没找到上面的 objectIn<Key>AtIndex:<key>AtIndexes: 继续找:

    • countOf<Key>
    • enumeratorOf<Key> 对应 NSSetobjectEnumerator 方法
    • memberOf<Key>: 对应 NSSetmember 方法
  • 如果以上方法没有找到,判断accessInstanceVariablesDirectly 返回 YES,则直接返回成员变量,获取顺序依然是:

    1. _name
    2. _isName
    3. name
    4. isName
  • 以上都有没,则执行 valueForUndefinedKey:

实例验证

getter访问器方法,顺序:getNamenameisName_name

@implementation YJPerson
- (NSString *)getName{
    return NSStringFromSelector(_cmd);
}
- (NSString *)name {
    return NSStringFromSelector(_cmd);
}
- (NSString *)isName {
    return NSStringFromSelector(_cmd);
}
- (NSString *)_name {
    return NSStringFromSelector(_cmd);
}
@end

运行代码,输出: Xnip2022-07-24_21-38-54.png

去掉 getName 继续: Xnip2022-07-24_21-40-35.png

去掉 name 继续: Xnip2022-07-24_21-42-08.png

去掉 isName 继续: Xnip2022-07-24_21-43-42.png

没毛病,getter访问器方法,确实是按顺序 getName name isName``_name 来查找的,返回的

countOf<Key> objectIn<Key>AtIndex: <key>AtIndexes: 验证

先把上面 所有 getter 访问器方法去掉,改为:

@implementation YJPerson
// countOf<Key>
- (NSInteger)countOfName {
    NSLog(@"%s", __func__ );
    return 3;
}
// objectIn<Key>AtIndex:
- (id)objectInNameAtIndex:(NSInteger)index {
    NSLog(@"%s index = %ld", __func__ , (long)index);
    return [NSString stringWithFormat:@"name_%ld", (long)index];
}
// <key>AtIndexes:
- (NSArray *)nameAtIndexes:(NSIndexSet *)indexes {
    NSLog(@"%s indexes.count = %lu", __func__ , (unsigned long)indexes.count);
    return @[[NSString stringWithFormat:@"name_%ld", (long)indexes.firstIndex]];
}
@end

运行代码,输出: Xnip2022-07-24_22-07-31.png

去掉 - (id)objectInNameAtIndex:,继续运行: Xnip2022-07-24_22-08-42.png

obj类型: Xnip2022-07-24_22-12-44.png

countOf<Key> 确定个数,接着优先 objectIn<Key>AtIndex: 方法,没找到,继续找<key>AtIndexes:

countOf<Key>enumeratorOf<Key>、 memberOf<Key>: 验证

修改代码为:

// countOf<Key>
- (NSInteger)countOfName {
    NSLog(@"%s", __func__ );
    return 3;
}
// enumeratorOf<Key>
- (NSEnumerator *)enumeratorOfName {
    NSLog(@"%s", __func__);
    NSSet *set = [NSSet setWithArray:@[@"name_1", @"name_2", @"name_3"]];
    NSEnumerator *enumerator = [set objectEnumerator];
    return enumerator;
}
// memberOf<Key>:
- (id)memberOfName:(id)obj {
    NSLog(@"%s obj = %@", __func__, obj);
    return @"name1";
}

运行代码,输出: Xnip2022-07-24_22-21-49.png

valueForUndefinedKey 验证

把上面代码都删掉,YJPerson中只实现 valueForUndefinedKey:

Xnip2022-07-24_22-25-23.png

KVC取值流程图 222.png

4. 异常处理

当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个异常,并且应用程序Crash。见下图:

Xnip2022-07-23_20-48-15.png

我们可以重写下面两个方法:

-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"出现异常,不存在名字为  %@ 的 key", key);
    return nil;
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
     NSLog(@"出现异常,不存在名字为  %@ 的 key", key);
}

重写这两个方法之后,运行程序不再崩溃,见下图:

Xnip2022-07-23_20-54-47.png 我们可以根据业务需要,合理的处理KVC导致的异常

5. 自定义KVC

根据苹果官方文档提供的设值、取值规则,我们可以自己进行KVC的自定义实现。见下面实现代码:

// KVC 自定义
@implementation NSObject (YJKVC)

// 设置
- (void)yj_setValue:(nullable id)value forKey:(NSString *)key{
    // 1: 判断什么 key
    if (key == nil || key.length == 0) {
        return;
    }

    // 2: setter set<Key>: or _set<Key>,
    // key 要大写
    NSString *Key = key.capitalizedString;

    // 拼接方法
    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];

    // 是否存在方法
    if ([self yj_performSelectorWithMethodName:setKey value:value]) {
        NSLog(@"*********%@**********",setKey);
        return;
    }else if ([self yj_performSelectorWithMethodName:_setKey value:value]) {
        NSLog(@"*********%@**********",_setKey);
        return;
    }else if ([self yj_performSelectorWithMethodName:setIsKey value:value]) {
        NSLog(@"*********%@**********",setIsKey);
        return;
    }

    // 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
    // 3:判断是否能够直接赋值实例变量——NO
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"YJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];

    }

    // 4: 间接变量
    // 获取 ivar -> 遍历 containsObjct -
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    // 拼接成员变量
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];

    // 是否存在对应的变量
    if ([mArray containsObject:_key]) {
        // 4.2 获取相应的 ivar
       Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        // 4.3 对相应的 ivar 设置值
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:_isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:key]) {
       Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }

    // 5:如果找不到相关实例

    @throw [NSException exceptionWithName:@"YJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}

// 取值
- (nullable id)yj_valueForKey:(NSString *)key{

    // 1:刷选key 判断非空
    if (key == nil  || key.length == 0) {
        return nil;
    }

    // 2:找到相关方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex
    // key 要大写
    NSString *Key = key.capitalizedString;

    // 拼接方法
    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
        if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
            int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
            for (int i = 0; i<num-1; i++) {
                num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            }

            for (int j = 0; j<num; j++) {
                id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
                [mArray addObject:objc];
            }
            return mArray;
        }
    }
#pragma clang diagnostic pop


    // 3:判断是否能够直接赋值实例变量-YES、NO
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"YJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }

    // 4.找相关实例变量进行赋值
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];

    // _<key> _is<Key> <key> is<Key>
    // _name -> _isName -> name -> isName
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    
    // 判断是否存在对应的成员变量
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }
    
    return @"";
}

#pragma mark - 相关方法
- (BOOL)yj_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

- (id)performSelectorWithMethodName:(NSString *)methodName{

    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
    }
    return nil;
}

- (NSMutableArray *)getIvarListName{
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
        NSLog(@"ivarName == %@",ivarName);
        [mArray addObject:ivarName];
    }

    free(ivars);
    return mArray;
}
@end

6. 使用 KVC

创建 YJPersonYJStudent 类用于后面的测试

typedef struct {
    float x, y, z;
} YJThreeFloats;

@interface YJPerson : NSObject
@property (nonatomic, copy)   NSString          *name;
@property (nonatomic, copy)   NSString          *nick;
@property (nonatomic, assign) int               age;
@property (nonatomic, assign) int               length;
@property (nonatomic, copy)   NSString          *subject;
@property (nonatomic, strong) YJStudent         *student;
@property (nonatomic, strong) NSArray           *array;
@property (nonatomic, strong) NSMutableArray    *mArray;
@property (nonatomic, assign) YJThreeFloats     threeFloats;
@end

@interface YJStudent : NSObject
@property (nonatomic, copy)   NSString          *name;
@property (nonatomic, copy)   NSString          *nick;
@property (nonatomic, assign) int               age;
@property (nonatomic, assign) int               length;
@end

6.1 一般使用

创建 YJPerson 实例 person

YJPerson *person = [[YJPerson alloc] init];

基本类型

[person setValue:@"托尼" forKey:@"name"];
NSString *pName = [person valueForKey:@"name"];
NSLog(@"pName = %@", pName);

运行代码,输出: Xnip2022-07-23_23-27-06.png

集合类型

person.array = @[@"1",@"2",@"3"];
// 修改数组 person.array[0] = @"100";
// 方式一: 搞一个新的数组 - KVC 赋值就OK
NSArray *array = [person valueForKey:@"array"];
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"person.array = %@", [person valueForKey:@"array"]);

// 方式二:生成可变数组,替换指定索引值
NSMutableArray *mArray = [person mutableArrayValueForKey:@"array"];
mArray[0] = @"200";
NSLog(@"person.array = %@", [person valueForKey:@"array"]);

运行代码,输出: Xnip2022-07-23_23-29-14.png

结构体类型 !!!

// 结构体 - NSValue
YJThreeFloats floats = {1.1, 2.2, 3.3};
NSValue *value = [NSValue valueWithBytes:&floats objCType: @encode(YJThreeFloats)];
[person setValue:value forKey:@"threeFloats"];

// NSValue - 结构体
NSValue *personTfsValue = [person valueForKey:@"threeFloats"];
YJThreeFloats tfs;
[personTfsValue getValue:&tfs];
NSLog(@"tfs.x = %f, tfs.y = %f, tfs.z = %f", tfs.x, tfs.y, tfs.z);

运行代码,输出: Xnip2022-07-23_23-33-26.png

keypath 访问

YJStudent *student = [YJStudent alloc];
student.name       = @"张三";
person.student     = student;
[person setValue:@"李四" forKeyPath:@"student.name"];
NSLog(@"person.student.name  = %@", [person valueForKeyPath:@"student.name"]);

运行代码,输出: Xnip2022-07-23_23-35-22.png

6.2 字典操作

- (void)dictionaryTest
{
    NSDictionary* dict = @{@"name" : @"托尼",
                           @"nick" : @"Tony",
                           @"subject" : @"理发师",
                           @"age" : @18,
                           @"length" : @180};
    YJPerson *person = [[YJPerson alloc] init];
    // 字典转模型
    [person setValuesForKeysWithDictionary:dict];
    NSLog(@"person.name = %@", person.name);
    NSLog(@"person.nick = %@", person.nick);
    NSLog(@"person.subject = %@", person.subject);
    NSLog(@"person.age = %@",  @(person.age));
    NSLog(@"person.length = %@",  @(person.length));

    // 键数组 转到 字典
    NSArray *array = @[@"nick", @"age"];
    NSDictionary *dic = [person dictionaryWithValuesForKeys:array];
    NSLog(@"dic = %@", dic);
}

运行代码,输出: Xnip2022-07-23_23-40-09.png

6.3 传递消息

- (void)arrayMessagePass
{
    NSArray *array = @[@"ZhangSan", @"LiSi", @"WangWu", @"ZhaoLiu"];
    // 读取 array 中每个 字符串的长度,存入 lenStr 中
    NSArray *lenStr = [array valueForKeyPath:@"length"];
    NSLog(@"%@", lenStr);// 消息从array传递给了string

    // 将 array 中每个 字符串的转为小写,存入 lowStr 中
    NSArray *lowStr = [array valueForKeyPath:@"lowercaseString"];
    NSLog(@"%@", lowStr);
}

运行代码,输出: Xnip2022-07-23_23-43-03.png

6.4 聚合操作符

- (void)aggregationOperator
{
    NSMutableArray *studentArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        YJStudent *student = [YJStudent new];
        NSDictionary* dict = @{@"name" : @"张三",
                               @"age" : @(18+i),
                               @"nick" : @"zhangsan",
                               @"length" : @(175 + 2*arc4random_uniform(6))};
        [student setValuesForKeysWithDictionary:dict];
        [studentArray addObject:student];
    }

    NSLog(@"各身高 :%@", [studentArray valueForKey:@"length"]);
    int count = [[studentArray valueForKeyPath:@"@count.length"] intValue];
    NSLog(@"总个数 :%d", count);
    float avg = [[studentArray valueForKeyPath:@"@avg.length"] floatValue];
    NSLog(@"平均身高 :%f", avg);
    int sum = [[studentArray valueForKeyPath:@"@sum.length"] intValue];
    NSLog(@"身高总和 :%d", sum);
    int max = [[studentArray valueForKeyPath:@"@max.length"] intValue];
    NSLog(@"最大身高 :%d", max);
    int min = [[studentArray valueForKeyPath:@"@min.length"] intValue];
    NSLog(@"最小身高 :%d", min);
}

运行代码,输出: Xnip2022-07-23_23-49-26.png

6.5 集合操作符

  • NSArray操作符:
    • @unionOfObjects:返回操作对象指定属性的集合
    • @distinctUnionOfObjects:返回操作对象指定属性的集合 且 去重
  • NSSet 操作符:@distinctUnionOfSets:返回操作对象指定属性的集合 且 去重

数组操作符

// 数组操作符 @distinctUnionOfObjects @unionOfObjects
- (void)arrayOperator
{
    NSMutableArray *studentArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        YJStudent *student = [YJStudent new];
        NSDictionary* dict = @{@"name" : @"张三",
                               @"age" : @(18+i),
                               @"nick" : @"zhangsan",
                               @"length" : @(175 + 2*arc4random_uniform(6))};
        [student setValuesForKeysWithDictionary:dict];
        [studentArray addObject:student];
    }
    
    // valueForKey 获取
    NSLog(@"身高集合 :%@", [studentArray valueForKey:@"length"]);
    // 返回操作对象指定属性的集合
    NSArray * arr1 = [studentArray valueForKeyPath:@"@unionOfObjects.length"];
    NSLog(@"身高集合 :%@", arr1);
    // 返回操作对象指定属性的集合 -- 去重
    NSArray* arr2 = [studentArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
    NSLog(@"身高集合 去重后 :%@", arr2);
}

运行代码,输出: Xnip2022-07-23_23-53-04.png

嵌套 NSArray

// 嵌套数组操作 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
- (void)arrayNesting
{
    NSMutableArray *studentArray1 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        YJStudent *student = [YJStudent new];
        NSDictionary* dict = @{@"name" : @"张三",
                               @"age" : @(18+i),
                               @"nick" : @"zhangsan",
                               @"length" : @(175 + 2*arc4random_uniform(6))};
        [student setValuesForKeysWithDictionary:dict];
        [studentArray1 addObject:student];
    }

    NSMutableArray *studentArray2 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        YJStudent *student = [YJStudent new];
        NSDictionary* dict = @{@"name" : @"张三",
                               @"age" : @(18+i),
                               @"nick" : @"zhangsan",
                               @"length" : @(175 + 2*arc4random_uniform(6))};
        [student setValuesForKeysWithDictionary:dict];
        [studentArray2 addObject:student];
    }
    // 嵌套数组
    NSArray *nestArray = @[studentArray1, studentArray2];
    NSArray* arr = [nestArray valueForKeyPath:@"@unionOfArrays.length"];
    NSLog(@"身高集合 :%@", arr);
    NSArray *arr1 = [nestArray valueForKeyPath:@"@distinctUnionOfArrays.length"];
    NSLog(@"身高集合 去重后 :%@", arr1);
}

运行代码,输出: Xnip2022-07-24_00-01-29.png

嵌套 NSSet

NSArrayNSSet 区别:

  • NSArray 自然顺序
  • NSSet 是无序的
  • 注意 : 这个是最为重要的功能 NSSet中不能够存储重复的数据,可以用它来去除重复的值

```js
// 嵌套 NSSet
- (void)setNesting
{
    NSMutableSet *studentSet1 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        YJStudent *student = [YJStudent new];
        NSDictionary* dict = @{@"name" : @"张三",
                               @"age" : @(18+i),
                               @"nick" : @"zhangsan",
                               @"length" : @(175 + 2*arc4random_uniform(6))};
        [student setValuesForKeysWithDictionary:dict];
        [studentSet1 addObject:student];
    }
    NSLog(@"studentSet1 = %@", [studentSet1 valueForKey:@"length"]);
    NSMutableSet *studentSet2 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        YJStudent *student = [YJStudent new];
        NSDictionary* dict = @{@"name" : @"张三",
                               @"age" : @(18+i),
                               @"nick" : @"zhangsan",
                               @"length" : @(175 + 2*arc4random_uniform(6))};
        [student setValuesForKeysWithDictionary:dict];
        [studentSet2 addObject:student];
    }
    NSLog(@"studentSet2 = %@", [studentSet2 valueForKey:@"length"]);
    // 嵌套 set
    NSSet* nestSet = [NSSet setWithObjects:studentSet1, studentSet2, nil];
    // 去重
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.length"];
    NSLog(@"arr1 = %@", arr1);
}

运行代码,输出: Xnip2022-07-24_00-17-06.png