標籤:height 執行 關鍵字 log 變化 sig 原理 添加 nil
在iOS開發中,KVC和KVO是經常被用到的。可以使用KVC對對象的屬性賦值和取得對象的屬性值,可以使用KVO監聽對象屬性值的變化。簡單介紹一下KVC和KVO。
一:索引值編碼(KVC)
KVC,全稱 Key Value Coding(索引值編碼),是OC 語言的一個特性,使用KVC,可以對對象的屬性進行動態讀寫。
KVC的操作方法由 NSKeyValueCoding協議提供,而NSObject已經實現了這個協議,因此OC中的幾乎所有對象都可以使用KVC操作。常用的KVC操作方法有:
(1)設定屬性值
setValue:value forKey:key (用於簡單路徑,也就是直接屬性)
setValue: value forKeyPath:key (用於複雜路徑,也就是間接屬性)
(2)擷取屬性值
valueForKey: key (用於擷取簡單路徑的屬性值)
valueForKeyPath: key (用於擷取複雜路徑的屬性值)
通過代碼來看KVC的具體使用:
兩個類分別是Dog類和Person類
Dog.h
1 #import <Foundation/Foundation.h>2 3 @interface Dog : NSObject4 5 @property (nonatomic, copy) NSString *name;6 7 @end
Dog.m
1 #import "Dog.h"2 3 @implementation Dog4 5 @end
Person.h
1 #import <Foundation/Foundation.h> 2 3 @class Dog; 4 5 @interface Person : NSObject 6 7 @property (nonatomic, copy) NSString *name; 8 @property (nonatomic, copy) NSString *sex; 9 10 @property (nonatomic, strong) Dog *dog;11 12 @end
Person.m
1 #import "Person.h"2 3 @implementation Person4 5 @end
KVC的使用:
1 Person *person = [[Person alloc] init]; 2 //kvc 設定值 3 [person setValue:@"Jim" forKey:@"name"]; 4 [person setValue:@"boy" forKey:@"sex"]; 5 6 //kvc 獲得值 7 NSLog(@"name = %@ sex = %@",[person valueForKey:@"name"],[person valueForKey:@"sex"]); 8 9 Dog *dog = [[Dog alloc] init];10 person.dog = dog;11 //kvc 設定複雜路徑值12 [person setValue:@"Teddy" forKeyPath:@"dog.name"];13 //kvc 獲得複雜路徑值14 NSLog(@"dog name = %@",[person valueForKeyPath:@"dog.name"]);
可以看到,KVC使用對應的函數即可設定值、獲得值。那麼,KVC是如何尋找一個屬性進行讀取呢?KVC遵循下面的規則,假設要對name屬性進行讀取:
(1)設定屬性:優先考慮setName方法;如果沒有,則搜尋成員變數_name;如果沒找到,則搜尋成員變數name;如果還沒有找到,則調用 setValue: forUndefineKey: 方法。
(2)讀取屬性:優先考慮getName方法;如果沒有,則搜尋成員變數_name;如果沒找到,則搜尋成員變數name;如果還沒有找到,則調用 valueForUndefineKey: 方法。
二:KVC中 setValuesForKeysWithDictionary: 的使用
KVC中有一個非常重要的方法: setValuesForKeysWithDictionary:dict ,該方法可以將一個字典映射到一個對象,省去了給對象一一賦值的步驟。
使用 setValuesForKeysWithDictionary:dict 的一個例子:
student.h
1 #import <Foundation/Foundation.h> 2 3 @interface Student : NSObject 4 5 /** 6 * 學號 7 */ 8 @property (nonatomic, copy) NSString *num; 9 /**10 * 姓名11 */12 @property (nonatomic, copy) NSString *name;13 /**14 * 身高15 */16 @property (nonatomic, assign) float height;17 18 /**19 * 初始化的兩個方法20 *21 */22 - (instancetype)initWithDict:(NSDictionary *)dict;23 + (instancetype)stuWithDict:(NSDictionary *)dict;24 25 27 28 @end
student.m
1 - (instancetype)initWithDict:(NSDictionary *)dict{ 2 if(self = [super init]){ 3 [self setValuesForKeysWithDictionary:dict]; 4 } 5 return self; 6 } 7 8 + (instancetype)stuWithDict:(NSDictionary *)dict 9 {10 return [[self alloc] initWithDict:dict];11 }12
用一個字典初始化對象
1 - (void)initStudent 2 { 3 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 4 @"Tom",@"name", 5 @"110",@"num", 6 @"170.0",@"height", 7 nil]; 8 Student *stu = [[Student alloc] initWithDict:dict]; 9 NSLog(@"name = %@ num = %@ height = %f",stu.name,stu.num,stu.height);10 }
setValuesForKeyWithDictionary:dict 的原理
實際上,setValuesForKeyWithDictionary:dict 方法就是遍曆dict,對dict 中的每個索引值調用 setValue: forKey: 方法。可以用下面的方法類比setValuesForKeyWithDictionary:dict:
1 - (void) setProperty:(NSDictionary *)dict2 {3 for(NSString *key in [dict allKeys])4 {5 NSString *value = [dict objectForKey:key];6 [self setValue:value forKey:key];7 }8 }
調用時:
1 - (instancetype)initWithDict:(NSDictionary *)dict{2 if(self = [super init]){3 //[self setValuesForKeysWithDictionary:dict];4 [self setProperty:dict];5 }6 return self;7 }
和setValuesForKeyWithDictionary:dict 功能是一樣的。
使用setValuesForKeyWithDictionary:dict一個需要注意的地方
當字典中有某個值,而對象沒有相應的屬性時,會發生崩潰。比如,新的字典如下:
1 - (void)initStudent 2 { 3 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 4 @"Tom",@"name", 5 @"110",@"num", 6 @"170.0",@"height", 7 @"boy",@"sex",nil]; 8 Student *stu = [[Student alloc] initWithDict:dict]; 9 NSLog(@"name = %@ num = %@ height = %f",stu.name,stu.num,stu.height);10 }
字典中有 sex屬性,但是Studeng對象中沒有這個屬性,此時會發生崩潰。解決方案:
實現 setValue: forUndefineKey: 方法,在該方法中處理出現沒有屬性的情況。
student.h中添加代碼:
1 - (void)setValue:(id)value forUndefinedKey:(NSString *)key;
student.m中添加代碼:
1 - (void)setValue:(id)value forUndefinedKey:(NSString *)key2 {3 4 }
當出現沒有屬性的情況時,就會調用 setValue: forUndefineKey: 方法。因為該方法沒做處理,所以這種情況不做處理,不會發生崩潰。需要注意的是:setValue: forUndefineKey: 方法用途很廣泛,比如說字典中某個key值 為id,但是在OC中id 是關鍵字,這種情況也可以在 setValue: forUndefineKey: 方法中處理。
三:索引值監聽(KVO)
KVO全稱 Key Value Observing。使用KVO可以實現視圖組件和資料模型的分離,視圖作為監聽器,當模型的屬性值發生變化後,監聽器可以做相應的處理。KVO的方法由NSKeyValueObserving協議提供,同樣NSObject已經實現了該協議,因此幾乎所有的對象都可以使用KVO。
使用KVO操作常用的方法如下:
註冊制定路徑的監聽器: addObserver: forKeyPath: option: context:
刪除制定路徑的監聽器:removeObserver: forKeyPath:
觸發監聽時的方法:observeValueForKeyPath: ofObject: change: context:
一個KVO的例子:
有兩個類: Dog類和People類
Dog.h
1 #import <Foundation/Foundation.h>2 3 @interface Dog : NSObject4 5 @property (nonatomic, copy) NSString *name;6 7 @end
Dog.m
1 #import "Dog.h"2 3 @implementation Dog4 5 @end
People.h
1 #import <Foundation/Foundation.h> 2 3 @class Dog; 4 5 @interface People : NSObject 6 7 @property (nonatomic , copy) NSString *name; 8 @property (nonatomic , strong) Dog *dog; 9 10 @end
People.m
1 #import "People.h" 2 #import "Dog.h" 3 4 @implementation People 5 6 /** 7 * 初始化時增加對dog的監聽 8 * 9 */10 - (void)setDog:(Dog *)dog11 {12 _dog = dog;13 [self.dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];14 }15 /**16 * 重寫observeValueForKeyPath方法17 */18 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context19 {20 if([keyPath isEqualToString:@"name"]){21 NSLog(@"newName = %@",[change objectForKey:@"new"]);22 }23 }24 25 - (void)dealloc26 {27 //移除監聽28 [self.dog removeObserver:self forKeyPath:@"name"];29 }30 31 @end
測試KVO函數:
1 - (void)testKVO 2 { 3 People *people = [[People alloc] init]; 4 people.name = @"Tom"; 5 Dog *dog = [[Dog alloc] init]; 6 dog.name = @"Teddy"; 7 people.dog = dog; 8 9 //執行到這一步後,會觸發監聽器的函數(observeValueForKeyPath)10 dog.name = @"testChange";11 }
在代碼中,當修改dog 的name屬性時,就會觸發監聽方法,也就是
observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context 方法。
iOS開發中KVC、KVO簡介