iOS進階_KVC(&KVC賦值取值過程分析&KVC自定義&異常處理)

KVC(Key-value coding)

鍵值編碼

基本使用

  1. 能夠?qū)ο蟮乃接谐蓡T進行取值賦值
  2. 對數(shù)值和結(jié)構(gòu)體型的屬性進行的打包解包處理

實例: WTPerson.h

#import <Foundation/Foundation.h>

@interface WTPerson : NSObject{
//    @public  //@protect默認
    NSString * _name;
}

/** name  **/
//@property(nonatomic,strong)NSString * name;

@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *text;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];
    //訪問成員變量
    //p.name = @"wt";
    //NSLog(@"%@",p.name);

    //訪問私有變量(必須要要設(shè)置為public才可訪問)
    //p->_name = @"wt";
    //NSLog(@"%@",p->_name);

    //KVC(即使不用public修飾,也可以訪問私有變量)
    [p setValue:@"wt" forKey:@"name"];
    NSLog(@"%@",[p valueForKey:@"name"]);

    [self.text setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
}

作為一個開發(fā)者憨栽,有一個學習的氛圍跟一個交流圈子特別重要谈喳,這是一個我的iOS交流群:642 363 427不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗愧驱,討論技術(shù)浙滤, 大家一起交流學習成長!

KVC賦值取值過程分析和自定義及異常處理

賦值過程

  • 1彰檬、先找相關(guān)方法set<Key>; _set<Key>; setIs<Key>;
  • 2伸刃、若是沒有相關(guān)方法+(BOOL)accessInstanceVariablesDirectly判斷是否可以直接訪問成員變量
  • 3、如果判斷NO逢倍,直接執(zhí)行KVC的setValue:forUndefinedKey:(系統(tǒng)拋出一個異常捧颅,未定義key)
  • 4、如果是YES较雕,繼續(xù)找相關(guān)變量_<key> _is<Key> <key> is<Key>
  • 5碉哑、方法或成員都不存在,setValue:forUndefinedKey:方法默認是拋出異常

實例驗證

WTPerson.h

#import <Foundation/Foundation.h>
@interface WTPerson : NSObject{
    @public  //@protect默認
    NSString * _name;
    NSString * _isName;
    NSString * name;
    NSString * isName;
}
@end

WTPerson.m

#import "WTPerson.h"
@implementation WTPerson

-(void)setName:(NSString *)name{
    NSLog(@"%s",__func__);
}

-(void)_setName:(NSString *)name{
    NSLog(@"%s",__func__);
}

-(void)setIsName:(NSString *)name{
    NSLog(@"%s",__func__);
}
@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new]; 
    //驗證KVC賦值過程
    [p setValue:@"wt" forKey:@"name"];

    NSLog(@"name = %@",p->name);
    NSLog(@"_name = %@",p->_name);
    NSLog(@"isname = %@",p->isName);
    NSLog(@"_isname = %@",p->_isName);
}

@end
  • 運行程序亮蒋,我們把WTPerson.m中的-(void)setName:(NSString *)name扣典、-(void)_setName:(NSString *)name-(void)setIsName:(NSString *)name三個方法依次注釋慎玖,我們發(fā)現(xiàn)三個方法都會被依次執(zhí)行贮尖。
  • 然后我們把WTPerson.h中的NSString * _name;NSString * _isName;趁怔、NSString * name;湿硝、NSString * isName;依次注釋,我們會發(fā)現(xiàn)4個屬性依次被賦值润努。

WTPerson.m中我們讓accessInstanceVariablesDirectly返回NO关斜,則程序直接崩潰。

+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

取值過程

  • 1铺浇、先找相關(guān)方法get<Key>,key
  • 2痢畜、若沒有相關(guān)方法,+(BOOL)accessInstanceVariabkesDirectly判斷是否可以直接訪問成員變量
  • 3鳍侣、如果是NO丁稀,直接執(zhí)行KVC的valueForUndefinedKey:(系統(tǒng)拋出一個異常,未定義key)
  • 4倚聚、如果是YES二驰,繼續(xù)找相關(guān)變量_<key>、_is<Key>秉沼、<key>、is<Key>
  • 5矿酵、方法或成員都不存在唬复,valueForUndefineKey:方法,默認是拋出異常

實例驗證

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson

//- (NSString*) getName{
//    NSLog(@"%s",__func__);
//    return @"getName";
//}

- (NSString*) name {
    return @"name";
}

//+ (BOOL)accessInstanceVariablesDirectly{
//    return NO;
//}
@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *text;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];

    //驗證KVC取值過程
    NSLog(@"name = %@",[p valueForKey:@"name"]);
}

@end

取值方式與賦值方式大致相同全肮。

KVC自定義

自定義KVC代碼實現(xiàn)

創(chuàng)建分類NSObject+KVC

NSObject+KVC.h

#import <Foundation/Foundation.h>

@interface NSObject (KVC)

- (void)wt_setValue:(nullable id)value forKey:(NSString *)key;

- (id)wt_valueForKey:(NSString *)key;

@end

NSObject+KVC.m

#import "NSObject+KVC.h"
#import <objc/runtime.h>

@implementation NSObject (KVC)

- (id)wt_valueForKey:(NSString *)key{
    //判斷是否合法
    if (key == nil && key.length ==0) {
        return nil;
    }

    //Key
    NSString * Key = key.capitalizedString;

    //先找相關(guān)方法 get<Key>,key
    NSString * getKey = [NSString stringWithFormat:@"get%@:",Key];

    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }

    if ([self respondsToSelector:NSSelectorFromString(key)]) {
        return [self performSelector:NSSelectorFromString(key)];
    }

    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * exception = [NSException exceptionWithName:@"NSUnknownKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }

    //再找相關(guān)變量
    //獲取所有的成員變量
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    NSMutableArray * arr = [[NSMutableArray alloc]init];
    for (int i = 0; i<count; i++) {
        Ivar var = ivars[i];
        const char * varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }

    //_<key> _is<Key> <key> is<Key>
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",Key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@",key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",Key]]) {
            return object_getIvar(self, ivars[i]);
        }
    }
    free(ivars);
    return nil;
}

- (void)wt_setValue:(nullable id)value forKey:(NSString *)key{

    //判斷是否合法
    if (key == nil && key.length ==0) {
        return;
    }

    //Key
    NSString * Key = key.capitalizedString;

    //先找相關(guān)方法 set<Key>; _set<Key>; setIs<Key>;
    NSString * setKey = [NSString stringWithFormat:@"set%@:",Key];

    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }

    NSString * _setKey = [NSString stringWithFormat:@"_set%@:",Key];

    if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
        [self performSelector:NSSelectorFromString(_setKey) withObject:value];
        return;
    }

    NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
        return;
    }

    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * exception = [NSException exceptionWithName:@"NSUnknownKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }

    //再找相關(guān)變量
    //獲取所有的成員變量
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    NSMutableArray * arr = [[NSMutableArray alloc]init];
    for (int i = 0; i<count; i++) {
        Ivar var = ivars[i];
        const char * varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }

    //_<key> _is<Key> <key> is<Key>
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@",key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    [self setValue:value forUndefinedKey:Key];
    free(ivars);
}
@end

驗證

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"
#import "NSObject+KVC.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    WTPerson * p =[WTPerson new];
    [p wt_setValue:@"wt" forKey:@"name"];

    NSLog(@"name-KVC = %@",[p wt_valueForKey:@"name"]);
    NSLog(@"_name = %@",p->_name);
    NSLog(@"_isName = %@",p->_isName);
    NSLog(@"name = %@",p->name);
    NSLog(@"isName = %@",p->isName);
}
@end

在項目中 commond+shift+o 搜索setValue:forKey發(fā)現(xiàn)在Foundation框架下的NSKeyValueCoding文件下

ht3bbp1s51 (1).png

我們查看這個文件中的方法敞咧,發(fā)現(xiàn)這個文件中是一些分類的集合

zsormyk4s5 (1).png

KVC異常處理及正確性驗證

KVC異常處理

  • 1、賦值為空 setNilValueForKey
  • 2辜腺、Key值不存在 setValue:forUndefinedKey

正確性驗證

validateValue 該方法的工作原理:

  • 1休建、先找一下你的類中是否實現(xiàn)了方法 -(BOOL)validate<Key>:error;
  • 2乍恐、如果實現(xiàn)了就會根據(jù)實現(xiàn)方法里面的自定義邏輯返回NO或者YES;如果沒有實現(xiàn)這個方法测砂,則系統(tǒng)默認返回YES

示例代碼

WTPerson…h(huán)

#import <Foundation/Foundation.h>

@interface WTPerson : NSObject

/** name  **/
@property(nonatomic,strong)NSString * name;

/** age  **/
@property(nonatomic,assign)int age;

@end

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson

//對非對象類型茵烈,值不能為空
- (void) setNilValueForKey:(NSString *)key{
    NSLog(@"%@ 值不能為空",key);
}

//賦值的key不存在
- (void) setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"key = %@值不存在",key);
}

//取值的key不存在
- (id) valueForUndefinedKey:(NSString *)key{
    NSLog(@"key = %@值不存在",key);
    return nil;
}

//正確性驗證
- (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError{
    NSNumber* value = (NSNumber*)*ioValue;
    NSLog(@"%@",value);
    if ([value integerValue] >= 0 && [value integerValue] <= 200) {
        return YES;
    }
    return NO;
}

@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"
#import "WTContainer.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];

    //異常處理
    [p setValue:@18 forKey:@"name"];
    [p setValue:nil forKey:@"name"];
    NSLog(@"name = %@",p.name);

    [p setValue:nil forKey:@"age"];
    NSLog(@"age = %d",p.age);

    [p setValue:@"hello" forKey:@"name1"];

    NSLog(@"name = %@",[p valueForKey:@"name1"]);

    //萬能容器
    WTContainer * container = [WTContainer new];

    [container setValue:@"wt" forKey:@"name"];
    [container setValue:@18 forKey:@"age"];

    NSLog(@"name = %@,age = %@",[container valueForKey:@"name"],[container valueForKey:@"age"]);

    //正確性驗證
    NSNumber * value = @200;
    NSNumber * value1 = @199;

    if ([p validateValue:&value1 forKey:@"age" error:NULL]) {

        [p setValue:value1 forKey:@"age"];
    }

    NSLog(@"%@",[p valueForKey:@"age"]);
}

@end

原文:GitHub

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砌些,隨后出現(xiàn)的幾起案子呜投,更是在濱河造成了極大的恐慌,老刑警劉巖存璃,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仑荐,死亡現(xiàn)場離奇詭異,居然都是意外死亡纵东,警方通過查閱死者的電腦和手機粘招,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偎球,“玉大人洒扎,你說我怎么就攤上這事√鸪鳎” “怎么了逊笆?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岂傲。 經(jīng)常有香客問我难裆,道長,這世上最難降的妖魔是什么镊掖? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任乃戈,我火速辦了婚禮,結(jié)果婚禮上亩进,老公的妹妹穿的比我還像新娘症虑。我一直安慰自己,他們只是感情好归薛,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布谍憔。 她就那樣靜靜地躺著,像睡著了一般主籍。 火紅的嫁衣襯著肌膚如雪习贫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天千元,我揣著相機與錄音苫昌,去河邊找鬼。 笑死幸海,一個胖子當著我的面吹牛祟身,可吹牛的內(nèi)容都是我干的奥务。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼袜硫,長吁一口氣:“原來是場噩夢啊……” “哼氯葬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起父款,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤溢谤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后憨攒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體世杀,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年肝集,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞻坝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡杏瞻,死狀恐怖所刀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捞挥,我是刑警寧澤浮创,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站砌函,受9級特大地震影響斩披,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜讹俊,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一垦沉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仍劈,春花似錦厕倍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至这溅,卻和暖如春闸婴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芍躏。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留降狠,地道東北人对竣。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓庇楞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親否纬。 傳聞我的和親對象是個殘疾皇子吕晌,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 學習參考,如有錯誤临燃,歡迎批評指正>Σ怠!膜廊! KVC Key-Value Coding 鍵值編碼乏沸,可以通過一個ke...
    人間四月天_Andy閱讀 624評論 0 2
  • 在編程中,最常見的就是程序的流程取決于你所使用的各種變量和屬性的值爪瓜,根據(jù)變量和屬性的值確定后面運行的代碼蹬跃,有時會檢...
    pro648閱讀 1,644評論 2 27
  • 賦值過程: 先找相關(guān)方法:set<key>:,_set<key>:,setIs<key>: 若沒有相關(guān)方法則:+ ...
    wp_Demo閱讀 689評論 0 1
  • UI總結(jié)-KVC賦值 在實際的項目階段,后臺給我們的數(shù)據(jù)都是以字典的形式.我們在拿到數(shù)據(jù)的時候...
    Dear丶Musk閱讀 370評論 0 1
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭铆铆,有人歡樂有人憂愁蝶缀,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,544評論 28 53