基礎(chǔ)使用
KVC的使用
- 簡(jiǎn)單賦值
- 復(fù)雜賦值
- 修改私有變量
- 模型和字典的互相轉(zhuǎn)換
- 取出多個(gè)模型中的某個(gè)屬性的值
- 你以為就這了柏副?
KVO的使用
- 注冊(cè)成為觀察者
- kvo的回調(diào)方法
- 移除觀察者,釋放資源
底層探究
- KVO背后原理
- KVC賦值過(guò)程
- KVC取值過(guò)程
- 貼代碼了
異常情況
- value為空
- key不存在
其他如NSArray捺典,NSSet,NSNumber纵刘,有興趣的自行研究穴翩,調(diào)用方法有所不同
KVC正確性的驗(yàn)證,有興趣的同上
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
基礎(chǔ)使用
- 基礎(chǔ)部分犬第,model類(lèi)中的屬性就不贅述了
- 簡(jiǎn)單賦值
- (void)easyValuation
{
Major *major = [[Major alloc]init];
//常規(guī)賦值
major.heig = @"1.98";
//KVC賦值
[major setValue:@"0.98" forKey:@"heig"];
[major setValue:@"1.88" forKeyPath:@"heig"];
//另外通過(guò)KVC可以進(jìn)行自動(dòng)類(lèi)型轉(zhuǎn)換,age是int類(lèi)型,傳入的依舊可以是字符串
[major setValue:@"18" forKey:@"age"];
[major setValue:@"28" forKeyPath:@"age"];
NSLog(@"%@",major.heig);
}
- 復(fù)雜賦值
- (void)complexValuation
{
//forKeyPath 包含forKey 的功能,所以使用forKeyPath 就行
//forKeyPath 可以進(jìn)行內(nèi)部點(diǎn)語(yǔ)法
//屬性一定要有芒帕,不然會(huì)崩潰
//Student類(lèi)中有一個(gè)Major類(lèi)的屬性
Student * stud = [[Student alloc]init];
stud.major.age = 22;
//KVC賦值的3中方式
[stud.major setValue:@"33" forKeyPath:@"age"];
[stud.major setValue:@"33" forKey:@"age"];
[stud setValue:@"33" forKeyPath:@"major.age"];
NSLog(@"%d",stud.major.age);
}
- 修改私有變量
- (void)modifiPrivatelyVariable
{
//修改類(lèi)的私有成員變量
Major *major = [[Major alloc]init];
//Major類(lèi)中有一個(gè)私有屬性MajorName歉嗓,強(qiáng)行賦值
[major setValue:@"33" forKey:@"MajorName"];
NSLog(@"%@",major);
}
- 模型和字典的互相轉(zhuǎn)換
- (void)transformModelAndDic
{
Major *major = [[Major alloc]init];
// 字段快速賦值對(duì)應(yīng)的屬性(適用于簡(jiǎn)單的字典轉(zhuǎn)模型) 不建議使用
//原因1:字典中的的key,在類(lèi)的屬性列表中必須有(可以不使用)背蟆,不然會(huì)崩潰
//2.如果模型中帶有模型(如字典中嵌套數(shù)組或字典)鉴分,子模型則賦值不成功
NSDictionary *dic = @{@"heig":@"188",
@"age":@18};
//字典轉(zhuǎn)模型
[major setValuesForKeysWithDictionary:dic];
//模型轉(zhuǎn)字典
NSDictionary * dic2 = [major dictionaryWithValuesForKeys:@[@"heig",@"age"]];
//關(guān)于字典轉(zhuǎn)模型,MJExtension,YYModel 等優(yōu)秀第三方框架了解一下
//Mantle需要模型類(lèi)繼承Mantle 带膀, JSONModel需要模型類(lèi)繼承JSONModel
}
- 取出多個(gè)模型中的某個(gè)屬性的值
//有這么個(gè)場(chǎng)景志珍,電話(huà)薄的索引要返回一個(gè)字符串?dāng)?shù)組,就可以從模型數(shù)組中這么取出
- (void)sdadwx
{
Major *major1 = [[Major alloc]init];
major1.heig = @"198";
Major *major2 = [[Major alloc]init];
major2.heig = @"197";
Major *major3 = [[Major alloc]init];
major3.heig = @"199";
NSArray *majorAry = @[major1,major2,major3];
//取出同一屬性的值
NSArray *allHeig = [majorAry valueForKeyPath:@"heig"];
NSLog(@"%@",allHeig);
}
- 你以為就這了垛叨?
//這里拋出一個(gè)引子
//點(diǎn)擊屏幕改變按鈕顏色伦糯,當(dāng)然也可以訪(fǎng)問(wèn)其私有屬性進(jìn)行設(shè)置
self.btn = [UIButton buttonWithType:UIButtonTypeCustom];
self.btn.backgroundColor = [UIColor redColor];
self.btn.frame = CGRectMake(0, 500, 100, 40);
[self.view addSubview:self.btn];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.btn setValue:[UIColor blueColor] forKeyPath:@"backgroundColor"];
}
KVO的使用
- KVO-鍵值觀察
//注冊(cè)成為觀察者,監(jiān)聽(tīng)對(duì)象的某個(gè)屬性的值的改變
- (void)regisObsever
{
[self.student addObserver:self forKeyPath:@"major.MajorName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
//3秒后改變major.MajorName的值
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.student setValue:@"計(jì)算機(jī)BBB" forKeyPath:@"major.MajorName"];
});
}
- kvo的回調(diào)方法
//監(jiān)聽(tīng)KeyPath 值的變化
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqual:@"major.MajorName"]) {
//獲取前后變化的值
NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- 移除觀察者,釋放資源
-(void)dealloc
{
[self.student removeObserver:self forKeyPath:@"major.MajorName"];
}
擴(kuò)展
KVO 生效
1.使用 setter 方法改變值 ,KVO 生效會(huì)生效
2.使用 setValue:forKey 即 KVC 改變值 KVO 也會(huì)生效嗽元,因?yàn)?KVC 會(huì)去 調(diào)用 setter 方法
底層探究
- KVO背后原理
當(dāng)某個(gè)類(lèi)的對(duì)象第一次被觀察時(shí)敛纲,系統(tǒng)就會(huì)在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建對(duì)應(yīng)于該類(lèi)的一個(gè)派生類(lèi),在這個(gè)派生來(lái)中还棱,系統(tǒng)會(huì)重寫(xiě)父類(lèi)中被觀察屬性的setter方法
//模擬kvo內(nèi)部代碼载慈,實(shí)際上在派生類(lèi)中
- (void)setHeig:(NSString *)heig
{
//屬性改變前調(diào)用惭等,通知系統(tǒng)改屬性即將發(fā)生變化
[self willChangeValueForKey:@"heig"];
_heig = heig;
//屬性改變前調(diào)用珍手,通知系統(tǒng)改屬性已經(jīng)發(fā)生變化
[self didChangeValueForKey:@"heig"];
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"heig"]) {
//檢測(cè)到后,取消變更發(fā)送通知的操作
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
父類(lèi)會(huì)將對(duì)象的isa指針指向新創(chuàng)建的派生類(lèi),因此琳要,這個(gè)類(lèi)的對(duì)象就成了派生類(lèi)的對(duì)象了寡具,之后調(diào)用屬性的setter方法實(shí)際上調(diào)用的是重寫(xiě)后的setter方法,從而實(shí)現(xiàn)了鍵值通知機(jī)制稚补,此外童叠,派生類(lèi)還重寫(xiě)了dealloc方法來(lái)釋放內(nèi)存資源
- KVC賦值過(guò)程
1.查找類(lèi)中是否存在與key對(duì)應(yīng)的 setKey _setKey setIsKey方法,存在直接調(diào)用,進(jìn)行賦值
2.若找不到课幕,查看accessInstanceVariablesDirectly方法返回值厦坛,默認(rèn)是YES,開(kāi)啟間接訪(fǎng)問(wèn)成員變量乍惊,為NO則為不允許
3.如果為NO杜秸,就會(huì)調(diào)用 setValue: forUndefinedKey:方法,并拋出一個(gè)異常润绎,如有需要可以重寫(xiě)這個(gè)方法
4.如果為YES撬碟,繼續(xù)尋找成員變量_key _isKey key isKey
5.若未找到,就會(huì)調(diào)用 setValue: forUndefinedKey:方法莉撇,并拋出一個(gè)異常呢蛤,如有需要可以重寫(xiě)這個(gè)方法
6.若找到,找到即停棍郎,進(jìn)行賦值其障,取值的時(shí)候,找對(duì)應(yīng)的get方法,比如找到_key,并賦值涂佃,
取值就會(huì)調(diào)用_key方法取值(只有g(shù)et方法中_key是有值的)静秆,而不是從最開(kāi)始的get方法 getKey 中取值
- KVC取值過(guò)程
1.查找類(lèi)中是否存在與key對(duì)應(yīng)的 getKey key isKey _key 依次調(diào)用這些get方法,找到的話(huà)直接調(diào)用巡李,
2.若找不到抚笔,查看accessInstanceVariablesDirectly 方法返回值,默認(rèn)是YES侨拦,開(kāi)啟間接訪(fǎng)問(wèn)成員變量累舷,為NO則為不允許
3.如果為NO印衔,就會(huì)調(diào)用 valueForUndefinedKey:方法,并拋出一個(gè)異常,如有需要可以重寫(xiě)這個(gè)方法
4.如果為YES诅炉,繼續(xù)尋找成員變量_key _isKey key isKey
5.若未找到,就會(huì)調(diào)用 valueForUndefinedKey:方法飞主,并拋出一個(gè)異常逆屡,如有需要可以重寫(xiě)這個(gè)方法
6.若找到,找到即停与涡,進(jìn)行取值的
- 貼代碼了
- Student .h
#import <Foundation/Foundation.h>
@interface Student : NSObject
{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@property (nonatomic, strong)Major *major;
@end
- Student .m
#import "Student.h"
@implementation Student
//蘋(píng)果默認(rèn)you實(shí)現(xiàn)
//set相關(guān)
- (void)setName:(NSString *)name
{
NSLog(@"%s",__func__);
}
- (void)_setName:(NSString *)name
{
NSLog(@"%s",__func__);
}
- (void)_setIsName:(NSString *)isName
{
NSLog(@"%s",__func__);
}
//get 相關(guān)
- (NSString *)getName{
NSLog(@"%s",__func__);//[Student getName]
//將SEL對(duì)象轉(zhuǎn)為NSString對(duì)象
NSLog(@"%@",NSStringFromSelector(_cmd));//_cmd代表當(dāng)前方法惹谐,輸出轉(zhuǎn)化的字符串:getName
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
//是否開(kāi)啟間接訪(fǎng)問(wèn)持偏,默認(rèn)返回yes
//如果關(guān)閉就不會(huì)找 _key _isKey key isKey這些成員變量
+ (BOOL)accessInstanceVariablesDirectly
{
return YES;
}
@end
- VC中使用
根據(jù)賦值流程和取值流程,依次斷點(diǎn)查看即可氨肌,下面列出為找到get相關(guān)方法時(shí)鸿秆,賦值取值情況
self.student->_name = @"_name";
self.student->_isName = @"_isName";
self.student->name = @"name";
self.student->isName = @"isName";
由于賦值先賦值的是 _name,所以怎囚,下面輸出是 _name卿叽,而不是name
NSLog(@"男男女女女女%@",[self.student valueForKeyPath:@"name"]);
異常情況
- value為空
1.如果給對(duì)象賦值可以為nil
2.可是給值類(lèi)型(基本數(shù)據(jù)類(lèi)型)賦值nil,會(huì)報(bào)錯(cuò)setNilValueForKey - key不存在
1:賦值的key不存在恳守,setValue:forUndefinedKey:方法來(lái)捕獲異常
2:取值的key不存在考婴,valueForUndefinedKey方法來(lái)捕獲異常,必要可重寫(xiě)