一馆截、 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.2return 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.2return 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:
補充:被觀察的對象釋放以后,記得移除監(jiān)聽
上面都是自己查找資料以及自己試驗之后的總結(jié)疚脐,如有不對敬請指正亿柑。