iOS KVC KVO的簡單使用

一馆截、 kvc

1. KVC(Key-value coding)鍵值編碼

通過對象的屬性名(不管該屬性是否暴露)直接訪問該屬性仙畦,或者給該對象賦值
這邊獲和賦值我這邊分開來寫付鹿。方便理解

簡單使用的話這幾個方法就行了
 //直接通過Key來取值
- (nullable id)valueForKey:(NSString *)key;                         
 //通過Key來設(shè)值
- (void)setValue:(nullable id)value forKey:(NSString *)key; 
//通過KeyPath來取值 
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  
 //通過KeyPath來設(shè)值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; 
//返回一個布爾值涂乌,該值指示鍵值編碼方法在沒有找到屬性的訪問器方法時是否應(yīng)該直接訪問對應(yīng)的實例變量催享。
+ (BOOL)accessInstanceVariablesDirectly;    
//當(dāng)value(forKey:)沒有發(fā)現(xiàn)與給定鍵相對應(yīng)的屬性時調(diào)用靠欢。
 -(id)valueForUndefinedKey:(NSString *)key;
//當(dāng)setValue:(forKey:)沒有發(fā)現(xiàn)與給定鍵相對應(yīng)的屬性時調(diào)用廊敌。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
2. 調(diào)用 - (void)setValue:(nullable id)value forKey:(NSString *)key;

當(dāng)調(diào)用 - (void)setValue:(nullable id)value forKey:(NSString *)key; 的時候程序都干了些什么呢?
下面是測試代碼

#import <Foundation/Foundation.h>
//KVC給屬性賦值
@interface Test: NSObject {
    NSString *name;
    NSString *_name;
    NSString *isName;
    NSString *_isName;
}

-(void)backName;
@end

@implementation Test

+(BOOL)accessInstanceVariablesDirectly{
    NSLog(@"調(diào)用了accessInstanceVariablesDirectly");
    return YES;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"調(diào)用了setValue:forUndefinedKey:");
}
-(void)setName:(NSString *)name{
    NSLog(@"調(diào)用了setName");
    _name = name;
}
-(void)setIsName:(NSString *)isName{
    NSLog(@"調(diào)用了setIsName");
    _isName = isName;
}
-(void)backName{
    NSLog(@"name  - %@",name);
    NSLog(@"_name  - %@",_name);
    NSLog(@"isName  - %@",isName);
    NSLog(@"_isName  - %@",_isName);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test * s = [Test new];
        [s setValue:@"Jobs" forKey:@"name"];
        [s backName];
    }
    return 0;
}

當(dāng)運行到 [s setValue:@"Jobs" forKey:@"name"]的時候程序處理和調(diào)用順序
1门怪、先找-(void)setName:(NSString *)name 骡澈,找到賦值結(jié)束
2、再找-(void)setIsName:(NSString *)isName 薪缆,找到賦值結(jié)束
3秧廉、上面兩個方法都找不到的時候調(diào)用+ (BOOL)accessInstanceVariablesDirectly

3.1 return NO;的時候 拣帽,不讓訪問屬性 疼电。異常處理調(diào)用-(void)setValue:(id)value forUndefinedKey:(NSString *)key
3.2 return YES;的時候减拭。 先查找_name蔽豺,找不到則查找_isName,還沒有找到則找name拧粪,最后找isName修陡,找到賦值結(jié)束沧侥。
3.3、以上都找不`到則異常處理調(diào)用-(void)setValue:(id)value forUndefinedKey:(NSString *)key

3. 調(diào)用 - (nullable id)valueForKey:(NSString *)key;

其實和調(diào)用setValue(forkey:)是一樣的魄鸦。
下面是測試代碼以

#import <Foundation/Foundation.h>

@interface Test: NSObject {
    NSString *name;
    NSString *_name;
    NSString *isName;
    NSString *_isName;
}

-(void)backName;
@end

@implementation Test
-(instancetype)init{
    if (self = [super init]) {
        name = @"Jobs1";
        _name = @"Jobs2";
        isName = @"Jobs3";
        _isName = @"Jobs4";
    }
    return self;
}

+(BOOL)accessInstanceVariablesDirectly{
    NSLog(@"調(diào)用了accessInstanceVariablesDirectly");
    return YES;
}
-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"調(diào)用了valueForUndefinedKey");
    return nil;
}
-(NSString *)getName{
    NSLog(@"調(diào)用了getName"); //1
    return _name;
}
-(NSString *)name{
    NSLog(@"調(diào)用了name");//2
    return _name;
}
-(NSString *)isName{
    NSLog(@"調(diào)用了isName");//3
    return _isName;
}

-(void)backName{
    NSLog(@"name  - %@",name);
    NSLog(@"_name  - %@",_name);
    NSLog(@"isName  - %@",isName);
    NSLog(@"_isName  - %@",_isName);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test * s = [Test new];
        NSLog(@"[s valueForKey:@\"name\"] :: %@",[s valueForKey:@"name"]);
        [s backName];
    }
    return 0;
}

當(dāng)運行到 [s valueForKey:@"name"]的時候程序處理和調(diào)用順序
1宴杀、先找-(NSString *)getName, 找到獲取結(jié)束
2、再找-(NSString *)name 拾因,找到獲取結(jié)束
3旺罢、再找-(NSString *)isName ,找到獲取結(jié)束
4绢记、上面兩個方法都找不到的時候調(diào)用+ (BOOL)accessInstanceVariablesDirectly扁达,

4.1 return NO;的時候 ,不讓訪問屬性 蠢熄。異常處理調(diào)用-(id)valueForUndefinedKey:(NSString *)key跪解。
4.2 return YES;的時候。 先查找_name签孔,找不到則查找_isName叉讥,還沒有找到則找name,最后找isName骏啰,找到獲取結(jié)束节吮。
4.3抽高、以上都找不`到則異常處理調(diào)用-(id)valueForUndefinedKey:(NSString *)key

4. 調(diào)用- (nullable id)valueForKeyPath:(NSString *)keyPath; 和- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

上面的講的很明白 判耕,這兩個我就放在一起講 這個和上面的原理是一樣的
下面是一段測試代碼

#import <Foundation/Foundation.h>

@interface Hand : NSObject{
    NSString *_desc;
}
@end
@implementation Hand

@end
//----------------------
@interface People: NSObject{
    Hand *_hand;
}
@end

@implementation People

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        People *p = [People new];
        Hand *h = [Hand new];
        [p setValue:h forKey:@"hand"];
        [p setValue:@"這是我的手" forKeyPath:@"hand.desc"];
        NSLog(@"%@",[p valueForKeyPath:@"hand.desc"]);
    }
    return 0;
}

原理就是根據(jù)hand.desc 中的 '.' ,來分割;兩個key翘骂。其他的是還是和
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
這兩個方法是一樣的原理

二壁熄、KVO

1. KVO 即 Key-Value Observing

鍵值觀察,對目標(biāo)對象的某屬性添加觀察碳竟,當(dāng)該屬性發(fā)生變化時草丧,通過觸發(fā)觀察者對象實現(xiàn)的KVO接口方法,來自動的通知觀察者莹桅。

主要方法
//注冊監(jiān)聽
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
//移除監(jiān)聽
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
//監(jiān)聽回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change  context:(void *)context;
//value將要改變
- (void)willChangeValueForKey:(NSString *)key;
//value已經(jīng)改變
- (void)didChangeValueForKey:(NSString *)key;

實現(xiàn)原理:

KVO 是通過 isa-swizzling 實現(xiàn)的昌执。
基本的流程就是編譯器自動為被觀察對象創(chuàng)造一個派生類,并將被觀察對象的isa 指向這個派生類诈泼。如果用戶注冊了對某此目標(biāo)對象的某一個屬性的觀察懂拾,那么此派生類會重寫這個方法,并在其中添加進(jìn)行通知的代碼铐达。Objective-C 在發(fā)送消息的時候岖赋,會通過 isa 指針找到當(dāng)前對象所屬的類對象。而類對象中保存著當(dāng)前對象的實例方法瓮孙,因此在向此對象發(fā)送消息時候唐断,實際上是發(fā)送到了派生類對象的方法选脊。由于編譯器對派生類的方法進(jìn)行了 override,并添加了通知代碼脸甘,因此會向注冊的對象發(fā)送通知恳啥。注意派生類只重寫注冊了觀察者的屬性方法。

普通的屬性賦值 以及打印結(jié)果:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface People: NSObject

@property (nonatomic,assign) NSInteger age;

@end

@implementation People

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"keyPath - object : %@  -  %@",keyPath,object);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *p = [People new];
//        [p addObserver:p forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        p.age = 12;
        //打印p指向的對象
        NSLog(@"p指向的對象 :%@", [p class]);
        //打印p中isa指針指向的對象
        NSLog(@"p中isa指針指向的對象 :%@", object_getClass(p));

    }
    return 0;
}

打印結(jié)果:
MyTextKVCKVO[94189:4661264] p指向的對象 :People
MyTextKVCKVO[94189:4661264] p中isa指針指向的對象 :People

添加監(jiān)聽之后的賦值

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface People: NSObject

@property (nonatomic,assign) NSInteger age;

@end

@implementation People

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"keyPath - object : %@  -  %@",keyPath,object);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *p = [People new];
        [p addObserver:p forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        p.age = 12;
        //打印p指向的對象
        NSLog(@"p指向的對象 :%@", [p class]);
        //打印p中isa指針指向的對象
        NSLog(@"p中isa指針指向的對象 :%@", object_getClass(p));

    }
    return 0;
}

打印結(jié)果:
MyTextKVCKVO[94199:4662213] keyPath - object : age - <People: 0x100760f90>
MyTextKVCKVO[94199:4662213] p指向的對象 :People
MyTextKVCKVO[94199:4662213] p中isa指針指向的對象 :NSKVONotifying_People

而我們也知道丹诀,所謂的OC的消息機制是通過isa去查找實現(xiàn)的角寸,那么我們現(xiàn)在可以進(jìn)行大膽的猜想:
其實KVO的實現(xiàn)可能是:
添加Observer通過runtime偷偷實現(xiàn)了一個子類,并且以NSKVONotifying_+類名來命名,將之前那個對象的isa指針指向了這個子類忿墅。,重寫了觀察的對象setter方法扁藕,并且在重寫的中添加了willChangeValueForKey:以及didChangeValueForKey:

屏幕快照 2019-08-22 上午11.59.54.png

補充:被觀察的對象釋放以后,記得移除監(jiān)聽

上面都是自己查找資料以及自己試驗之后的總結(jié)疚脐,如有不對敬請指正亿柑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市棍弄,隨后出現(xiàn)的幾起案子望薄,更是在濱河造成了極大的恐慌,老刑警劉巖呼畸,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痕支,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛮原,警方通過查閱死者的電腦和手機卧须,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儒陨,“玉大人花嘶,你說我怎么就攤上這事”哪” “怎么了椭员?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長笛园。 經(jīng)常有香客問我隘击,道長,這世上最難降的妖魔是什么研铆? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任埋同,我火速辦了婚禮,結(jié)果婚禮上蚜印,老公的妹妹穿的比我還像新娘莺禁。我一直安慰自己,他們只是感情好窄赋,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布哟冬。 她就那樣靜靜地躺著楼熄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浩峡。 梳的紋絲不亂的頭發(fā)上可岂,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音翰灾,去河邊找鬼缕粹。 笑死,一個胖子當(dāng)著我的面吹牛纸淮,可吹牛的內(nèi)容都是我干的平斩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咽块,長吁一口氣:“原來是場噩夢啊……” “哼绘面!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起侈沪,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤揭璃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亭罪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘦馍,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年应役,在試婚紗的時候發(fā)現(xiàn)自己被綠了情组。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡扛吞,死狀恐怖呻惕,靈堂內(nèi)的尸體忽然破棺而出荆责,到底是詐尸還是另有隱情滥比,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布做院,位于F島的核電站盲泛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏键耕。R本人自食惡果不足惜寺滚,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屈雄。 院中可真熱鬧村视,春花似錦、人聲如沸酒奶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杠氢,卻和暖如春站刑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鼻百。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工绞旅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人温艇。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓因悲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勺爱。 傳聞我的和親對象是個殘疾皇子囤捻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,101評論 1 32
  • KVO (Key-Value Observing),俗稱“鍵值監(jiān)聽”邻寿,能夠用來監(jiān)聽對象屬性的變化蝎土,也是 Objec...
    valentizx閱讀 357評論 0 3
  • 源碼加翻譯 #import <Foundation/NSArray.h> #import <Foundation/...
    CAICAI0閱讀 1,153評論 0 50
  • 上面一篇文章沒有分析完yymodel 。 接著上篇接著分析 static void ModelSetValueFo...
    充滿活力的早晨閱讀 1,059評論 0 0
  • 上學(xué)時绣否,冬天總是很漫長誊涯,冬天上學(xué)很冷,我討厭穿很多衣服蒜撮。特別怕母親要我穿上厚厚的棉褲暴构,那么肥,又笨重段磨,母親拿著棉褲...
    樹兜把閱讀 376評論 0 6