來源:http://
引言
kvc是每個(gè)iOS開發(fā)者在學(xué)習(xí)obj-C時(shí)都學(xué)過的特性,但是由于obj-C學(xué)起來并不難,所以很多初學(xué)者把大部分時(shí)間都放在熟悉cocoa框架以及對(duì)iOS開發(fā)相關(guān)的API掌握上。其實(shí)在項(xiàng)目中巧用kvc可以大大提高開發(fā)效率減少代碼量,下面我們進(jìn)入正題。
定義
KVC(Key-Value-Coding)意思是鍵值編碼。在iOS中,提供了一種方法通過使用屬性的名稱(也就是Key)來間接訪問對(duì)象的屬性方法。
說的有點(diǎn)兒拗口,實(shí)際上就是通過類定義我們可以看到類的各種屬性,那么使用屬性的名稱我們就能訪問到類實(shí)例化后的對(duì)象的這個(gè)屬性值。
KVC特性
1.無setter/getter方法也可以直接去找對(duì)應(yīng)名稱的變量操作
KVC中最常見的也是最基本的調(diào)用方法:
//調(diào)用這個(gè)方法找到與key匹配的變量并把value賦值給它 - (void)setValue:(nullable id)value forKey:(NSString *)key; //調(diào)用這個(gè)方法找到與key匹配的變量并返回 - (nullable id)valueForKey:(NSString *)key;
那么上面的方法對(duì)比直接調(diào)用getter/setter方法時(shí)有什么區(qū)別呢?
第一點(diǎn)印證實(shí)例:
@interface LXObj : NSObject { NSString *ivar;} @end
在.m文件中的代碼空空如也:
@implementation LXObj @end
然后執(zhí)行以下代碼:
LXObj *obj = [[LXObj alloc] init];obj.ivar = @'i am a ivar';
發(fā)現(xiàn)靜態(tài)解析器編譯不過去…… 如果按照以下代碼去操作ivar變量即可使程序正常運(yùn)行:
LXObj *obj = [[LXObj alloc] init]; [obj setValue:@'i am a ivar' forKey:@'ivar'];NSLog(@'%@', [obj valueForKey:@'ivar']);
運(yùn)行結(jié)果如下:
那么我們?cè)賮砜匆幌翶VC訪問下的所謂obj-C私有變量,首先在類中添加私有變量:
@interface LXObj : NSObject { @private NSString *privateIvar;}
或者在類擴(kuò)展中聲明變量:
@interface LXObj () @property (nonatomic, copy) NSString *privateIvar; @end
為了解釋的更明白,讓所有人(包括一些初學(xué)obj-C的小伙伴)看懂,先用普通的方式訪問:
都編譯不過去,靜態(tài)分析器就幫我們找到了錯(cuò)誤,那么我們換KVC方式訪問:
LXObj *obj = [[LXObj alloc] init];[obj setValue:@'i am a private ivar' forKey:@'privateIvar'];NSLog(@'%@', [obj valueForKey:@'privateIvar']);
然后運(yùn)行,發(fā)現(xiàn)可以正常運(yùn)行:
看到這,你會(huì)得出或者印證你以前學(xué)習(xí)obj-C時(shí)書上提到的:obj-C實(shí)際上并不存在真正的私有變量,因?yàn)橹灰雷兞棵Q就可以訪問且操作這個(gè)變量。
你不要萌萌的瞪著眼睛問我:我們?yōu)樯哆€要遵守規(guī)則把變量寫在.m內(nèi)的類擴(kuò)展中呢?
因?yàn)楫?dāng)你的app提交到appstore中被人下載得到下載包后,別人反編譯分分鐘就能看到你的項(xiàng)目頭文件,類名和方法。
2.使用KVC會(huì)自動(dòng)開/封箱
如果你想設(shè)置一個(gè)標(biāo)準(zhǔn)量,在調(diào)用- (void)setValue:(nullable id)value forKey:(NSString *)key方法之前需要將它們封箱: 先給LXObj類添加一個(gè)floatNum屬性:
@property (nonatomic, assign) float floatNum;
然后執(zhí)行下面代碼:
LXObj *obj = [[LXObj alloc] init];[obj setValue:[NSNumber numberWithFloat:0.1] forKey:@'floatNum'];
這時(shí),- (void)setValue:(nullable id)value forKey:(NSString *)key方法會(huì)先開箱取出該值,再調(diào)用- (void)setFloatNum:方法或者直接更改floatNum實(shí)例變量,反之:
LXObj *obj = [[LXObj alloc] init];NSLog(@'%@', [obj valueForKey:@'floatNum']);
代碼中[obj valueForKey:@'floatNum']方法會(huì)先取出floatNum屬性的值并封箱打印出來。
3.鍵路徑
當(dāng)類中包含其他類類型的屬性時(shí),可以直接使用鍵路徑來操作這個(gè)屬性內(nèi)部的變量。 先定義一個(gè)LXSubObj類,其中包含一個(gè)屬性subIvar:
@interface LXSubObj : NSObject@property (nonatomic, copy) NSString *subIvar;@end
然后往LXObj中添加一個(gè)LXSubObj類型的屬性:
@class LXSubObj;@interface LXObj : NSObject@property (nonatomic, strong) LXSubObj *subObj;@end
此時(shí)要操作LXObj對(duì)象中的subObj屬性的subIvar可以使用
LXObj *obj = [[LXObj alloc] init];obj.subObj = [[LXSubObj alloc] init];[obj setValue:@'operate subObj's subIvar' forKeyPath:@'subObj.subIvar'];NSLog(@'%@', [obj valueForKeyPath:@'subObj.subIvar']);
運(yùn)行結(jié)果:
Ps:需要注意,如果LXObj下有一組類型為LXSubObj的數(shù)組作為屬性,那么NSArray實(shí)現(xiàn)valueForKeyPath:的方法是循環(huán)遍歷它的內(nèi)容并向每個(gè)對(duì)象發(fā)送信息。也就是說NSArray會(huì)向每個(gè)在自身之中的LXSubObj對(duì)象發(fā)送參數(shù)以subIvar作為鍵路徑的valueForKeyPath:消息。
4.批處理
這個(gè)由于各位在初學(xué)iOS階段可能就用到過,而且平時(shí)開發(fā)也會(huì)使用到,就一筆帶過吧:
// 根據(jù)所給字典一一對(duì)應(yīng)的設(shè)置接收消息對(duì)象內(nèi)的屬性值 - (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues; // 根據(jù)數(shù)組keys一一對(duì)應(yīng)的從接收消息對(duì)象內(nèi)取出對(duì)應(yīng)的值生成字典返回 - (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
5.快速運(yùn)算
通過快速運(yùn)算特性可以節(jié)約開發(fā)成本,簡化代碼,先把上面LXObj內(nèi)部的subObj改為NSArray類型(這里用到了泛型語法):
@class LXSubObj; @interface LXObj : NSObject @property (nonatomic, strong) NSArray *subObjs; @end
.m中初始化數(shù)組subObjs:
@implementation LXObj - (NSArray *)subObjs{ if (!_subObjs) { LXSubObj *subObj1 = [[LXSubObj alloc] init]; LXSubObj *subObj2 = [[LXSubObj alloc] init]; _subObjs = @[subObj1, subObj2]; } return _subObjs;} @end
然后執(zhí)行下面的代碼:
LXObj *obj = [[LXObj alloc] init]; NSLog(@'%@', [obj valueForKeyPath:@'subObjs.@count']);
運(yùn)行結(jié)果:
類似@count的例子還有很多:
用kvc實(shí)現(xiàn)一個(gè)萬能容器對(duì)象的方法
終于到的正題,這里我們?yōu)榱诵蜗笠恍密囎觼砼e例。下面的LXCar類沒有任何屬性但是理論上可以存任意數(shù)量的任意類型的屬性。 LXCar.h文件內(nèi)代碼:
@interface LXCar : NSObject // 父老鄉(xiāng)親們,看清楚,這容器就是空的?。?! @end
LXCar.m內(nèi)代碼:
@interface LXCar ()// 用來放置屬性鍵值對(duì)的字典@property (nonatomic, strong) NSMutableDictionary *mPropertiesDict;@end@implementation LXCar// 沒有對(duì)應(yīng)key的setter方法且沒有找到對(duì)應(yīng)key的屬性時(shí)調(diào)用- (void)setValue:(id)value forUndefinedKey:(NSString *)key{ if (!key || [key isEqualToString:@'']) return; if (!_mPropertiesDict) { _mPropertiesDict = [NSMutableDictionary dictionary]; } [_mPropertiesDict setValue:value forKey:key];}// 沒有對(duì)應(yīng)key的getter方法且沒有找到對(duì)應(yīng)key的屬性時(shí)調(diào)用- (id)valueForUndefinedKey:(NSString *)key{ if (!key || [key isEqualToString:@'']) return nil; return [_mPropertiesDict valueForKey:key];}@end
然后執(zhí)行代碼:
LXCar *car = [[LXCar alloc] init];[car setValue:@'保時(shí)捷 卡宴 3.0T 鉑金版' forKey:@'name'];[car setValue:@'保時(shí)捷' forKey:@'brand'];[car setValue:@'卡宴' forKey:@'categroy'];NSLog(@'car name = %@, car brand = %@, categroy = %@', [car valueForKey:@'name'], [car valueForKey:@'brand'], [car valueForKey:@'categroy']);
下面是執(zhí)行結(jié)果,你會(huì)發(fā)現(xiàn)原本空空的LXCar類被我們利用KVC特性打造成了萬能的容器,可以放下原本沒有的name/brand/categroy屬性:
其實(shí)相信只要是耐著性子從頭看到現(xiàn)在的小伙伴們都能看的懂上面的代碼:利用接收對(duì)象既沒有相應(yīng)的setter/getter方法又無對(duì)象屬性時(shí)調(diào)用- (void)setValue:(id)value forUndefinedKey:(NSString *)key以及- (id)valueForUndefinedKey:(NSString *)key兩個(gè)方法的特性,給對(duì)象加入了一個(gè)可變字典作為填充屬性的區(qū)域?qū)崿F(xiàn)這樣一個(gè)萬能容器。
歸納kvc的優(yōu)缺點(diǎn)
看了文章上面所講的,你可能已經(jīng)愛上KVC了。但是請(qǐng)清醒一下,萬物都有兩面性,如果濫用KVC的話也不是什么好事:
寫在最后
就像本文前面說的一樣,這種萬能容器對(duì)象可以用在后臺(tái)接口平時(shí)還算規(guī)范但是偶爾會(huì)多返回一些出參的情況。所以不論是runtime還是KVC,是底層知識(shí)還是語言特性,一定要學(xué)以致用。畢竟空懂一腔理論知識(shí)卻沒有解決問題的能力學(xué)再多的東西也沒有用……
|