1 KVC(KeyValueCoding)
1.1 KVC 常用的方法
(1)賦值類方法
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
(2)取值類方法
// 能取得私有成員變量的值
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
1.2 KVC 底層實現原理
當一個對象調用setValue:forKey: 方法時,方法內部會做以下操作:
1.判斷有沒有指定key的set方法,如果有set方法,就會調用set方法,給該屬性賦值
2.如果沒有set方法,判斷有沒有跟key值相同且帶有下劃線的成員屬性(_key).如果有,直接給該成員屬性進行賦值
3.如果沒有成員屬性_key,判斷有沒有跟key相同名稱的屬性.如果有,直接給該屬性進行賦值
4.如果都沒有,就會調用 valueforUndefinedKey 和setValue:forUndefinedKey:方法
1.3 KVC 的使用場景
1.3.1 賦值
(1) KVC 簡單屬性賦值
Person *p = [[Person alloc] init];
// p.name = @"jack";
// p.money = 22.2;
使用setValue: forKey:方法能夠給屬性賦值,等價于直接給屬性賦值
[p setValue:@"rose" forKey:@"name"];
[p setValue:@"22.2" forKey:@"money"];
(2) KVC復雜屬性賦值
//給Person添加 Dog屬性
Person *p = [[Person alloc] init];
p.dog = [[Dog alloc] init];
// p.dog.name = @"阿黃";
1)setValue: forKeyPath: 方法的使用
//修改p.dog 的name 屬性
[p.dog setValue:@"wangcai" forKeyPath:@"name"];
[p setValue:@"阿花" forKeyPath:@"dog.name"];
2)setValue: forKey: 錯誤用法
[p setValue:@"阿花" forKey:@"dog.name"];
NSLog(@"%@", p.dog.name);
3)直接修改私有成員變量
[p setValue:@"旺財" forKeyPath:@"_name"];
(3) 添加私有成員變量
Person 類中添加私有成員變量_age
[p setValue:@"22" forKeyPath:@"_age"];
1.3.2 字典轉模型
(1)簡單的字典轉模型
+(instancetype)videoWithDict:(NSDictionary *)dict
{
JLVideo *videItem = [[JLVideo alloc] init];
//以前
// videItem.name = dict[@"name"];
// videItem.money = [dict[@"money"] doubleValue] ;
//KVC,使用setValuesForKeysWithDictionary:方法,該方法默認根據字典中每個鍵值對,調用setValue:forKey方法
// 缺點:字典中的鍵值對必須與模型中的鍵值對完全對應,否則程序會崩潰
[videItem setValuesForKeysWithDictionary:dict];
return videItem;
}
(2)復雜的字典轉模型
注意:復雜字典轉模型不能直接通過KVC 賦值,KVC只能在簡單字典中使用,比如:
NSDictionary *dict = @{
@"name" : @"jack",
@"money": @"22.2",
@"dog" : @{
@"name" : @"wangcai",
@"money": @"11.1",
}
};
JLPerson *p = [[JLPerson alloc]init]; // p是一個模型對象
[p setValuesForKeysWithDictionary:dict];
內部轉換原理:
// [p setValue:@"jack" forKey:@"name"];
// [p setValue:@"22.2" forKey:@"money"];
// [p setValue:@{
// @"name" : @"wangcai",
// @"money": @"11.1",
//
// } forKey:@"dog"]; //給 dog賦值一個字典肯定是不對的
(3)KVC解析復雜字典的正確步驟
NSDictionary *dict = @{
@"name" : @"jack",
@"money": @"22.2",
@"dog" : @{
@"name" : @"wangcai",
@"price": @"11.1",
},
//人有好多書
@"books" : @[
@{
@"name" : @"5分鐘突破iOS開發",
@"price" : @"19.8"
},
@{
@"name" : @"3分鐘突破iOS開發",
@"price" : @"24.8"
},
@{
@"name" : @"1分鐘突破iOS開發",
@"price" : @"29.8"
}
]
};
XMGPerson *p = [[XMGPerson alloc] init];
p.dog = [[XMGDog alloc] init];
[p.dog setValuesForKeysWithDictionary:dict[@"dog"]];
//保存模型的可變數組
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in dict[@"books"]) {
//創建模型
Book *book = [[Book alloc] init];
//KVC
[book setValuesForKeysWithDictionary:dict];
//將模型保存
[arrayM addObject:book];
}
p.books = arrayM;
備注:
(1)當字典中的鍵值對很復雜,不適合用KVC;
(2)服務器返還的數據,你可能不會全用上,如果在模型一個一個寫屬性非常麻煩,所以不建議使用KVC字典轉模型
1.3.3 取值
(1) 模型轉字典
Person *p = [[Person alloc]init];
p.name = @"jack";
p.money = 11.1;
//KVC取值
NSLog(@"%@ %@", [p valueForKey:@"name"], [p valueForKey:@"money"]);
//模型轉字典, 根據數組中的鍵獲取到值,然后放到字典中
NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"money"]];
NSLog(@"%@", dict);
(2) 訪問數組中元素的屬性值
Book *book1 = [[Book alloc] init];
book1.name = @"5分鐘突破iOS開發";
book1.price = 10.7;
Book *book2 = [[Book alloc] init];
book2.name = @"4分鐘突破iOS開發";
book2.price = 109.7;
Book *book3 = [[Book alloc] init];
book3.name = @"1分鐘突破iOS開發";
book3.price = 1580.7;
// 如果valueForKeyPath:方法的調用者是數組,那么就是去訪問數組元素的屬性值
// 取得books數組中所有Book對象的name屬性值,放在一個新的數組中返回
NSArray *books = @[book1, book2, book3];
NSArray *names = [books valueForKeyPath:@"name"];
NSLog(@"%@", names);
//訪問屬性數組中元素的屬性值
Person *p = [[Person alloc]init];
p.books = @[book1, book2, book3];
NSArray *names = [p valueForKeyPath:@"books.name"];
NSLog(@"%@", names);
2 KVO (Key Value Observing)
2.1 KVO 的底層實現原理
(1)KVO 是基于 runtime 機制實現的
(2)當一個對象(假設是person對象,對應的類為 JLperson)的屬性值age發生改變時,系統會自動生成一個繼承自JLperson的類NSKVONotifying_JLPerson,在這個類的 setAge 方法里面調用
[super setAge:age];
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];
三個方法,而后面兩個方法內部會主動調用
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context方法,在該方法中可以拿到屬性改變前后的值.
2.2 KVO的作用
- 作用:能夠監聽某個對象屬性值的改變
// 利用KVO監聽p對象name 屬性值的改變
Person *p = [[XMGPerson alloc] init];
p.name = @"jack";
/* 對象p添加一個觀察者(監聽器)
Observer:觀察者(監聽器)
KeyPath:屬性名(需要監聽哪個屬性)
*/
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"123"];
/**
* 利用KVO 監聽到對象屬性值改變后,就會調用這個方法
*
* @param keyPath 哪一個屬性被改了
* @param object 哪一個對象的屬性被改了
* @param change 改成什么樣了
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
// NSKeyValueChangeNewKey == @"new"
NSString *new = change[NSKeyValueChangeNewKey];
// NSKeyValueChangeOldKey == @"old"
NSString *old = change[NSKeyValueChangeOldKey];
NSLog(@"%@-%@",new,old);
}
- 作者開發經驗總結的文章推薦,持續更新學習心得筆記
Runtime 10種用法(沒有比這更全的了
成為iOS頂尖高手,你必須來這里(這里有最好的開源項目和文章)
iOS逆向Reveal查看任意app 的界面
JSPatch (實時修復App Store bug)學習(一)
iOS 高級工程師是怎么進階的(補充版20+點)
擴大按鈕(UIButton)點擊范圍(隨意方向擴展哦)
最簡單的免證書真機調試(原創)
通過分析微信app,學學如何使用@2x,@3x圖片
TableView之MVVM與MVC之對比
使用MVVM減少控制器代碼實戰(減少56%)
ReactiveCocoa添加cocoapods 配置圖文教程及坑總結