2.KVC-KVO基本使用及底層探究

基礎(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ě)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末催烘,一起剝皮案震驚了整個(gè)濱河市蕉扮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颗圣,老刑警劉巖喳钟,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異在岂,居然都是意外死亡奔则,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蔽午,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)易茬,“玉大人,你說(shuō)我怎么就攤上這事及老〕槔常” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵骄恶,是天一觀的道長(zhǎng)食铐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)僧鲁,這世上最難降的妖魔是什么虐呻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮寞秃,結(jié)果婚禮上斟叼,老公的妹妹穿的比我還像新娘。我一直安慰自己春寿,他們只是感情好朗涩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著绑改,像睡著了一般谢床。 火紅的嫁衣襯著肌膚如雪兄一。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天萤悴,我揣著相機(jī)與錄音,去河邊找鬼皆的。 笑死覆履,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的费薄。 我是一名探鬼主播硝全,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼楞抡!你這毒婦竟也來(lái)了伟众?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤召廷,失蹤者是張志新(化名)和其女友劉穎凳厢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竞慢,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡先紫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筹煮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遮精。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖败潦,靈堂內(nèi)的尸體忽然破棺而出本冲,到底是詐尸還是另有隱情,我是刑警寧澤劫扒,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布檬洞,位于F島的核電站,受9級(jí)特大地震影響沟饥,放射性物質(zhì)發(fā)生泄漏疮胖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一闷板、第九天 我趴在偏房一處隱蔽的房頂上張望澎灸。 院中可真熱鬧,春花似錦遮晚、人聲如沸性昭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)糜颠。三九已至汹族,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間其兴,已是汗流浹背顶瞒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留元旬,地道東北人榴徐。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匀归,于是被迫代替她去往敵國(guó)和親坑资。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355