「iOS」——KVC

iOS学习

前言

对KVC模式的简单学习和总结。


KVC模式

KVC(Key-Value Coding,键值编码)是一种通过字符串来访问对象属性的机制,允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。可以在运行时动态地访问和修改对象的属性。而不是在编译时确定。

以下是KVC的常用方法:

objectivec 复制代码
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath设置值
- (void)setValue:(id)value forKey:(NSString *)key;//通过key设置值
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath获取值
- (id)valueForKey:(NSString *)key;//通过key获取值

KVC设值

我们通过- (void)setValue:(id)value forKey:(NSString *)key;方法来为KVC设值,下面给出代码演示:

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface AUser : NSObject

@property (nonatomic, copy) NSString *str1;
@property (nonatomic, copy) NSString *str2;

@end

#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *user = [[AUser alloc] init];
        [user setValue:@"Astr1" forKey:@"str1"];
        [user setValue:@"Astr2" forKey:@"str2"];
        
        NSLog(@"str1:%@",[user valueForKey:@"str1"]);
        NSLog(@"str2:%@",[user valueForKey:@"str2"]);
         
    }
    return 0;
}

运行结果为:

那么KVC设置的逻辑原理是什么呢?

如上图所示:

  1. 首先会按照setKey、_setKey的顺序查找方法,如找到方法,则直接调用方法并赋值;
  2. 未找到方法,则调用+ (BOOL)accessInstanceVariablesDirectly(是否可以直接访问成员变量,默认返回YES);
  3. 若accessInstanceVariablesDirectly方法返回YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到直接赋值,找不到则抛出NSUnknowKeyExpection异常;
  4. 若accessInstanceVariablesDirectly方法返回NO,那么就会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常;

我们来验证一些处理逻辑:

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface AUser1 : NSObject{
    @package
    NSString *name;
    NSString *_name;
}
@end


#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser1 *aUser = [[AUser1 alloc] init];
        
        [aUser setValue:@"strName1" forKey:@"_name"];
        [aUser setValue:@"strName2" forKey:@"name"];
        
        NSLog(@"name = %@", aUser->name);
        NSLog(@"_name = %@", aUser->_name);
        
    }
    return 0;
}

KVC取值

我们通过- (id)valueForKey:(NSString *)key; 方法来获取值。

下面来探究取值顺序:

objectivec 复制代码
#import "AUser.h"

@implementation AUser

-(int) getAge{
    return 9999;
}

-(int) age
{
    return 999;
}

-(int) isAge
{
    return 99;
}
@end


#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *user = [[AUser alloc] init];

        [user setValue:@"9" forKey:@"age"];
        
        NSLog(@"age:%@",[user valueForKey:@"age"]);
        
    }
    return 0;
}

运行后:

注释掉getAge后:

注释掉age方法后:

再注释掉isAge方法后:

原理:

  1. 首先会按照getKey、key、isKey、_key的顺序查找方法,找到直接调用取值
  2. 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出NSUnknowKeyExpection异常;
  3. 若返回的YES,则按照_ key、_isKey、key、isKey的顺序查找成员变量,找到则取值;
  4. 找不到则调用valueForUndefinedKey:抛出NSUnknowKeyExpection异常;

KVC使用keyPath

面对复杂的嵌套属性进行初始化赋值时,如果使用key一层层赋值十分麻烦。我们可以采用keyPath来访问对象的嵌套属性。

keyPath的两个方法:

objectivec 复制代码
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath设置值
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath获取值

下面我们来以代码举例:

我们先创建一个BUser:

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface BUser : NSObject
@property (nonatomic, copy) NSString *strb1;
@property (nonatomic, copy) NSString *strb2;
@end

我们再创建一个AUser嵌套一个BUser:

objectivec 复制代码
#import <Foundation/Foundation.h>
#import "BUser.h"

@interface AUser : NSObject
{
    BUser *bUser;
}
@end

此时,AUser对象中,含有一个嵌套属性,我们可以使用keyPath进行为嵌套对象赋值并且取值:

objectivec 复制代码
#import <Foundation/Foundation.h>
#import "AUser.h"
#import "BUser.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *aUser = [[AUser alloc] init];
        BUser *bUser = [[BUser alloc] init];
        [aUser setValue:bUser forKey:@"bUser"];
        [aUser setValue:@"b1" forKeyPath:@"bUser.strb1"];
        [aUser setValue:@"b2" forKeyPath:@"bUser.strb2"];
        NSLog(@"b1:%@",[aUser valueForKeyPath:@"bUser.strb1"]);
        NSLog(@"b1:%@",[aUser valueForKeyPath:@"bUser.strb2"]);

    }
    return 0;
}

打印结果:

KVC处理异常

处理不存在的key

在上面我们说过KVC设置的顺序。如果最后没有找到相应的成员变量,则会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常来结束程序。

我们只需要重写- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key 这个方法,则不会产生Crash。

objectivec 复制代码
#import "AUser1.h"
@implementation AUser1{
    int age;
}

- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key {
    NSLog(@"重写了setValue:value forUndefinedKey方法");
}
@end

#import <Foundation/Foundation.h>
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        AUser1 *aUser = [[AUser1 alloc] init];
        //处理不存在的key
        [aUser setValue:@"strName1" forKey:@"1"];
        
    }
    return 0;
}

运行结果:

处理nil异常

我们可以将nil赋值给字符串类型,但是不能复制给int或NSInteger类型。如果需要为对象赋nil时,则需要自己处理一下nil异常的部分,例如给int类型赋nil的情况。

当我们给int类型赋值nil时,就会出现异常,会执行-(void)setNilValueForKey:(NSString *)key这个方法,使程序崩溃,所以我们通常需要重写这个方法来处理nil异常。

objectivec 复制代码
#import "AUser1.h"

@implementation AUser1{
    int age;
}

-(void) setNilValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"age"]) {
        age = 0;
    } else {
        [super setNilValueForKey:key];
    }
}
@end


#import <Foundation/Foundation.h>
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser1 *aUser = [[AUser1 alloc] init];
        
        [aUser setValue:nil forKey:@"age"];
        
        NSLog(@"age = %@", [aUser valueForKey:@"age"]);
    }
    return 0;
}

运行结果:

KVC处理字典

我们可以通过字典进行批量的设值取值操作。

  • setValuesForKeysWithDictionary: 方法用于将字典中的值赋给对象的属性,
  • dictionaryWithValuesForKeys: 方法则用于根据属性键数组获取对象的属性值并返回对应的字典。
objectivec 复制代码
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, assign) NSInteger age;

@end

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        //使用KVC批量存值
        [person setValue:@"Kobe" forKey:@"name"];
        [person setValue:@"man" forKey:@"sex"];
        [person setValue:@"24" forKey:@"age"];
        NSDictionary* firstDictionary = [person dictionaryWithValuesForKeys:@[@"name", @"sex", @"age"]];
        NSLog(@"dictonary = %@", firstDictionary);
        
        //使用KVC批量赋值
        NSDictionary* secondDictionary = @{@"name":@"瑞娜", @"age":@2, @"sex": @"woman"};
        Person* secondPerson = [[Person alloc] init];
        [secondPerson setValuesForKeysWithDictionary:secondDictionary];
        NSLog(@"name  = %@, age = %ld, sex = %@", secondPerson.name, secondPerson.age, secondPerson.sex);
        
    }
    return 0;
}

KVC高阶消息传递

通俗来讲就是让数组中的每一个元素都执行某个方法,并把结果返回到新的数组中。这里我们实现将数组中的每个首字母大写,并且返回长度。

objectivec 复制代码
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSArray* arrStr = @[@"reyna",@"jett",@"Neon"];
        NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
        for (NSString* str  in arrCapStr) {
            NSLog(@"%@",str);
        }
        NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
        for (NSNumber* length  in arrCapStrLength) {
            NSLog(@"%ld",(long)length.integerValue);
        }
        
    }
    return 0;
}

运行结果:


总结

KVC相比于setter和getter方法,虽然在性能上差一点,但是在编码上更加的灵活,简洁,可以批量操作并且可以在运行时动态地访问和操作对象的属性。

相关推荐
TheNextByte118 分钟前
如何安全有效地清除iPad数据以便出售?
安全·ios·ipad
十二测试录1 小时前
Android和iOS测试区别
android·经验分享·ios·职场发展·ab测试
张飞签名上架4 小时前
深耕全球市场:App上架iOS与Google Play全流程指南
macos·ios·cocoa·ios上架·上架·谷歌上架
Digitally6 小时前
iPhone 无法向安卓设备发送图片:轻松解决
android·ios·iphone
DisonTangor6 小时前
Mac Studio配备1.5 TB显存——基于雷电5的远程直接内存访问技术
人工智能·macos·开源·aigc
天庭鸡腿哥6 小时前
输入序列号,可激活正版软件!
microsoft·macos·visual studio·everything
阿里云云原生6 小时前
RUM 助力 iOS 应用稳定性:从异常捕获到堆栈还原的全流程分析
人工智能·阿里云·ios·云原生·rum
初级代码游戏16 小时前
iOS只剩美工了吗?时间都被遮盖看不清了
ios·界面设计·美工
FreeBuf_18 小时前
朝鲜黑客组织“传染性面试“瞄准macOS:新型“DriverFixer“窃密工具浮出水面
macos·面试·职场和发展
2501_915918411 天前
iOS 开发中证书创建与管理中的常见问题
android·ios·小程序·https·uni-app·iphone·webview