iOS開(kāi)發(fā)之-KVC 問(wèn)答

1. KVC 用來(lái)做什么?

和對(duì)象的點(diǎn)語(yǔ)法類(lèi)似勺鸦,都是用來(lái)賦值和取值的并巍。

2. 為什么要用 KVC?

通常祝旷,兩種情況下我們需要重新審視自己的賦值方式履澳。

  • 訪(fǎng)問(wèn)私有屬性/變量。正常的情況下怀跛,我們根據(jù)設(shè)計(jì)原型距贷,開(kāi)開(kāi)心心的搭建面,初始化各種控件吻谋,UIView / UIButton / UILabel忠蝗,然后設(shè)置其相應(yīng)的屬性,以使界面滿(mǎn)足我們的需求漓拾。假如界面很簡(jiǎn)單阁最,效果不是很炫酷,那我們只需要通過(guò)設(shè)置子控件的各種屬性骇两,調(diào)整背景顏色速种,切個(gè)圓角,改變透明度等等低千,就可以將界面搞定配阵。可是,有些效果棋傍,偏偏是通過(guò)蘋(píng)果暴露給我們的接口不能滿(mǎn)足的救拉,比如,修改文字輸入框(UITextField)的占位文字的顏色瘫拣,這個(gè)時(shí)候怎么辦亿絮?想要 textField.placeholederLabel.textColor = [UIColor redColor] 沒(méi)有這個(gè)屬性啊,怎么實(shí)現(xiàn)麸拄?另外還有一些只讀屬性派昧,無(wú)法通過(guò)點(diǎn)語(yǔ)法正常賦值,怎么辦感帅?
  • 請(qǐng)求服務(wù)器數(shù)據(jù)之后的處理斗锭。沒(méi)有KVC,我們?nèi)绻虢o某個(gè)屬性賦值失球,則需要通過(guò) object.property = value; 這種方式進(jìn)行賦值。試想一下帮毁,如果是一個(gè)對(duì)象的幾個(gè)屬性這樣設(shè)置還行得通实苞,可是通常客戶(hù)端的數(shù)據(jù)都是從服務(wù)器端獲取烈疚,格式一般是 json/xml黔牵,我們需要通過(guò)字典轉(zhuǎn)模型,將字符串化的鍵值對(duì)轉(zhuǎn)換成對(duì)象的屬性值進(jìn)行保存和使用爷肝,通常這種數(shù)據(jù)量是很大的猾浦,而且還是帶各種嵌套的,難道這個(gè)時(shí)候我們還要先創(chuàng)建各種對(duì)象灯抛,然后根據(jù)鍵值對(duì)的鍵金赦,創(chuàng)建對(duì)應(yīng)的屬性,之后再手動(dòng)一個(gè)個(gè) model.property = value;保存嗎对嚼?肯定是不現(xiàn)實(shí)的夹抗。那怎么辦?

3. 有了 KVC 之后纵竖,上述問(wèn)題怎么解決漠烧?

  • [textField setValue:[UIColor purpleColor] forKeyPath:@"_placeholderLabel.textColor"]; 當(dāng)然,這里邊靡砌,找到正確的沒(méi)有暴露的接口也是個(gè)問(wèn)題已脓,以后再說(shuō)吧,大致就是利用 Runtime 通殃,給程序打斷點(diǎn)度液,然后將 UITextFiled 的所有變量(包括未暴露的私有變量)打印,之后根據(jù)名字猜測(cè)+嘗試+運(yùn)氣通常就能找到我們需要修改的屬性了。
    這里為什么要使用 setValue: forKeyPath: 而不是setValue: forKey:呢恨诱?因?yàn)?_placeholederLabelUITextField的屬性媳瞪,而我們要設(shè)置的是_placeholederLabel 的屬性,像這種有多層屬性嵌套的情況就需要使用 setValue: forKeyPath:照宝。
    經(jīng)過(guò)上邊一行簡(jiǎn)單的代碼我們就可以很優(yōu)雅的實(shí)現(xiàn)修改占位文字的顏色問(wèn)題了蛇受。這里需要說(shuō)明的一點(diǎn)是:占位文字的顏色和大小還可以通過(guò)設(shè)置 attributedPlaceholder 屬性設(shè)置,也是比較方便的厕鹃。代碼如下:
    NSString *placeholder = @"我是占位文字";
    NSDictionary *attr = @{NSForegroundColorAttributeName : [UIColor orangeColor],
                           NSFontAttributeName : [UIFont systemFontOfSize:15] };
    textField.attributedPlaceholder = [[NSAttributedString alloc]initWithString:placeholder attributes:attr];
  • 關(guān)于字典轉(zhuǎn)模型兢仰。這里先以很簡(jiǎn)單的單層數(shù)據(jù)為例:

JSON 數(shù)據(jù)

然后是模型TestModel的頭文件:

@interface TestModel : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *detail;
@property (copy, nonatomic) NSString *postscript;

- (instancetype)initWithDict:(NSDictionary *)dict;
@end

TestModel的實(shí)現(xiàn)文件:

@implementation TestModel
- (instancetype)initWithDict:(NSDictionary *)dict {
    self = [super init];
    if (self) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
@end

最后是ViewController控制器中的調(diào)用代碼:

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1. 加載本地 json 數(shù)據(jù),正常應(yīng)是請(qǐng)求服務(wù)器
    [self loadData];
}

- (void)loadData {
    NSString *jsonPath = [[NSBundle mainBundle] pathForResource:@"test.json" ofType:nil];
    // 2. 解析數(shù)據(jù)
    NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:jsonPath] options:NSJSONReadingMutableContainers error:nil];
    // 3. 轉(zhuǎn)換剂碴,只要屬性定義好把将,直接調(diào)用初始化方法即可
    TestModel *model = [[TestModel alloc]initWithDict:jsonDict];
    // 4. 打印驗(yàn)證
    NSLog(@"name - %@, detail - %@, postscript - %@", model.name, model.detail, model.postscript);
}
@end

打印結(jié)果驗(yàn)證:

打印結(jié)果

通過(guò)上邊這個(gè)十分簡(jiǎn)單的例子,我們就可以看到 KVC 在數(shù)據(jù)轉(zhuǎn)模型時(shí)的便捷了忆矛。
不過(guò)這么簡(jiǎn)單的數(shù)據(jù)實(shí)際開(kāi)發(fā)中很難遇到察蹲,這里這里只是演示一下,如果有多層嵌套的話(huà)催训,就需要在模型類(lèi)中重寫(xiě)setValue: forKey方法洽议,然后通過(guò)判斷key,對(duì)嵌套的字段再進(jìn)行轉(zhuǎn)換漫拭。

嵌套的 JSON 數(shù)據(jù)

最外層模型TestModel的頭文件:

@interface TestModel : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *detail;
@property (copy, nonatomic) NSString *postscript;
// 多了一個(gè)嵌套的模型屬性
@property (strong, nonatomic) NestedTestModel *nestedModel;

- (instancetype)initWithDict:(NSDictionary *)dict;
@end

最外層模型TestModel的實(shí)現(xiàn)文件:

@implementation TestModel
- (instancetype)initWithDict:(NSDictionary *)dict {
    self = [super init];
    if (self) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

- (void)setValue:(id)value forKey:(NSString *)key {
    // 單獨(dú)處理嵌套的 key
    if ([key isEqualToString:@"nested"]) {
        self.nestedModel = [[NestedTestModel alloc]initWithDict:value];
    } else {
        // 別忘了調(diào)用父類(lèi)的方法亚兄,否則其他不需要單獨(dú)處理的屬性就不解析了
        [super setValue:value forKey:key];
    }
}
@end

控制器的代碼:

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1. 加載本地 json 數(shù)據(jù),正常應(yīng)是請(qǐng)求服務(wù)器
    [self loadData];
}

- (void)loadData {
    NSString *jsonPath = [[NSBundle mainBundle] pathForResource:@"test.json" ofType:nil];
    // 2. 解析數(shù)據(jù)
    NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:jsonPath] options:NSJSONReadingMutableContainers error:nil];
    // 3. 轉(zhuǎn)換采驻,只要屬性定義好审胚,直接調(diào)用初始化方法即可
    TestModel *model = [[TestModel alloc]initWithDict:jsonDict];
    // 4. 打印驗(yàn)證
    NSLog(@"name - %@, detail - %@, postscript - %@, nestedName - %@, nestedDetail - %@, nestedPostscript - %@, ", model.name, model.detail, model.postscript, model.nestedModel.nestedName, model.nestedModel.nestedDetail, model.nestedModel.nestedPostscript);
}
@end
打印結(jié)果

這里嵌套內(nèi)部的模型NestedTestModel基本跟單層 JSON 中的模型的代碼一致,也就是打印驗(yàn)證那一點(diǎn)礼旅,也就不再貼代碼了膳叨。文章最底有g(shù)it地址。

其實(shí)各淀,例子中的數(shù)據(jù)解析跟實(shí)際的復(fù)雜度也差好多懒鉴,這時(shí)候我們往往會(huì)使用第三方框架,簡(jiǎn)單方便碎浇。這里推薦一個(gè)比較小眾的第三方框架:NSObject-ObjectMap临谱,可以看一下它的源文件,寫(xiě)的很規(guī)整奴璃,就一個(gè)分類(lèi)悉默,可以實(shí)現(xiàn) json/xml 到對(duì)象的轉(zhuǎn)換,當(dāng)然集合類(lèi)的屬性也可以自動(dòng)轉(zhuǎn)換(需要多寫(xiě)一點(diǎn)代碼)苟穆,里邊有好多方法用都牽扯到了 Runtime 和 OC 中的反射機(jī)制抄课。

4. KVC 這么牛掰唱星,系統(tǒng)是怎么實(shí)現(xiàn)的呢?

當(dāng)我們調(diào)用setValue: forKey:testKey方法時(shí)跟磨,系統(tǒng)會(huì)通過(guò)以下查找順序進(jìn)行賦值:

setValue:forKey:順序官方文檔

由上圖可以看出间聊,在使用 KVC 賦值時(shí)的查找順序依次為:
setTestKey方法 --> _testKey --> _isTestKey --> testKey --> isTestKey
后邊尋找實(shí)例變量的過(guò)程抵拘,有一個(gè)大的前提:對(duì)象的accessInstanceVariablesDirectly方法返回YES哎榴,當(dāng)然默認(rèn)情況下這個(gè)方法就是返回YES的,所以并不需要我們擔(dān)心僵蛛。如果都找不到尚蝌,那就調(diào)用 setValue:forUndefinedKey直接拋出異常。有時(shí)候如果我們不想讓程序因?yàn)檫@個(gè)原因拋出異常充尉,可以在類(lèi)的實(shí)現(xiàn)文件中重寫(xiě)該方法即可飘言。

代碼Git地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驼侠,隨后出現(xiàn)的幾起案子姿鸿,更是在濱河造成了極大的恐慌,老刑警劉巖泪电,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件般妙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡相速,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)鲜锚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)突诬,“玉大人,你說(shuō)我怎么就攤上這事芜繁⊥叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵骏令,是天一觀(guān)的道長(zhǎng)蔬捷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)榔袋,這世上最難降的妖魔是什么周拐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮凰兑,結(jié)果婚禮上妥粟,老公的妹妹穿的比我還像新娘。我一直安慰自己吏够,他們只是感情好勾给,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布滩报。 她就那樣靜靜地躺著,像睡著了一般播急。 火紅的嫁衣襯著肌膚如雪脓钾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天桩警,我揣著相機(jī)與錄音可训,去河邊找鬼。 笑死生真,一個(gè)胖子當(dāng)著我的面吹牛沉噩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柱蟀,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼川蒙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了长已?” 一聲冷哼從身側(cè)響起畜眨,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎术瓮,沒(méi)想到半個(gè)月后康聂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胞四,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年恬汁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辜伟。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氓侧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出导狡,到底是詐尸還是另有隱情约巷,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布旱捧,位于F島的核電站独郎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏枚赡。R本人自食惡果不足惜氓癌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望标锄。 院中可真熱鬧顽铸,春花似錦、人聲如沸料皇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鬼譬,卻和暖如春娜膘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背优质。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工竣贪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巩螃。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓演怎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親避乏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爷耀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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