如何理解iOS的KVO和KVC?

第一部分:KVO能夠實現什麼功能?

1.1 KVO 本質

    1. KVO 全稱Key-Value-Observing
    1. KVO 觀察一個對象的屬性,註冊一個指定的路徑,若這個對象的的屬性被修改,則 KVO 會自動通知觀察者;KVO 是一個觀察者模式
    1. KVO 只能對屬性【對象下面的屬性 】作出反應,不會用來對方法或者動做作出反應。
    1. 每一次屬性值改變都是自動發送通知,不須要開發者手動實現。
    1. 注意:任何對象都容許觀察其餘對象的屬性,而且能夠接收其餘對象狀態變化的通知。
    1. 當你觀察一個對象時,一個新的類會動態被建立。這個類繼承自該對象的本來的類,並重寫了被觀察屬性的 setter 方法。天然,重寫的 setter 方法會負責在調用原 setter方法以前和以後,通知全部觀察對象值的更改。最後把這個對象的 isa 指針 ( isa 指針告訴 Runtime 系統這個對象的類是什麼 ) 指向這個新建立的子類,對象就神奇的變成了新建立的子類的實例。

1.2 KVO 例子

  • 例子1:
_person = [[Person alloc] init]; 
     
/** 
 *  添加觀察者 
 * 
 *  @param observer 觀察者 
 *  @param keyPath  被觀察的屬性名稱 
 *  @param options  觀察屬性的新值、舊值等的一些配置(枚舉值,能夠根據須要設置,例如這裏可使用兩項) 
 *  @param context  上下文,能夠爲nil。 
 */ 
[_person addObserver:self 
          forKeyPath:@"age" 
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 
             context:nil]; 

/** 
 *  KVO 回調方法 
 * 
 *  @param keyPath 被修改的屬性 
 *  @param object  被修改的屬性所屬對象 
 *  @param change  屬性改變狀況(新舊值) 
 *  @param context context 傳過來的值 
 */ 
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary<NSString *,id> *)change 
                       context:(void *)context 
{ 
    NSLog(@"%@ 對象的%@屬性改變了:%@",object,keyPath,change); 
} 

/** 
 *  移除觀察者 
 */ 
- (void)dealloc 
{ 
    [self.person removeObserver:self forKeyPath:@"age"]; 
} 

複製代碼

  • 例子2:監聽 ScrollView 的 contentOffSet 屬性
[scrollview addObserver:self 
             forKeyPath:@「contentOffset                    
                options:NSKeyValueObservingOptionNew 
                context:nil]; 
                
複製代碼

1.3 KVO 的語法

【註冊】1.// 註冊觀察者,實施監聽; 
[self.person addObserver:self 
              forKeyPath:@"age" 
                 options:NSKeyValueObservingOptionNew 
                 context:nil]; 

【觀察】2.// 觀察方法,回調方法,在這裏處理屬性發生的變化; 
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary<NSString *,id> *)change 
                       context:(void *)context 

【移除】3.// 移除觀察者; 
[self removeObserver:self forKeyPath:@「age"]; 

複製代碼

1.4 KVO與runtime

  • 1.當觀察對象A時,KVO機制動態建立一個新的名爲:NSKVONotifying_A 的新類,該類繼承自對象A的本類,且 KVO 爲 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會負責在調用原 setter 方法以前和以後,通知全部觀察對象屬性值的更改狀況。
  • 2.被觀察對象的 isa 指針會指向一箇中間類,而不是原來真正的類。

第二部分:KVC能夠實現什麼功能?

2.1 KVC 本質

    1. KVC 全稱NSKeyValueCoding,中文:鍵值編碼
    1. 經過字符串的名字(key)來訪問類屬性;
    1. 不經過調用Setter、Getter方法訪問;
    1. 在運行時動態的訪問和修改對象的屬性,不是在編譯時肯定;
  • KVC 缺點數組

      1. 執行效率要低於 setter 和 getter 方法。由於使用 KVC 鍵值編碼,它必須先解析字符串,而後在設置或者訪問對象的實例變量。
      1. 使用 KVC 會破壞類的封裝性。
      1. 難排查錯誤

2.2 功能1:對集合操做

  • 例子1:求最大值
NSArray *a = @[@4, @84, @2]; 
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]); 

複製代碼
  • 例子2:有一個 Transaction 對象的數組,對象有屬性 amount 的話;當咱們調用 [a valueForKeyPath:@"@max.amount"] 的時候,它會在數組 a 的每一個元素中調用 -valueForKey:@"amount" 而後返回最大的那個。
NSArray *a = @[transaction1, transaction2, transaction3]; 
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]); 

複製代碼

2.3 功能2:對私有變量{如readonly變量—如private變量}賦值

  • 修改系統的一些類的私有屬性,必須先要拿到屬性的名字,通常都是下劃線開頭的屬性名
    1. 需改私有屬性-例子1:
@interface Teacher : NSObject 
{ 
    @private int _age;// 私有變量,通常外部不能改變,經過kvc能夠改變,前提你知道這個私有變量的名字; 
} 

@property (nonatomic, strong, readonly) NSString *name;// 只讀變量,一個外部不能直接賦值,經過kvc能夠改變 

@property (nonatomic, assign, getter = isMale) BOOL male; 

- (void)log; 

@end 

複製代碼

  • 使用kvc前: 用通常的 setter 和 getter,在類外部是不能訪問到私有變量的,不能設值給只讀變量
  • 使用kvc後:
Teacher *teacher = [Teacher new]; 
[teacher log]; 

// 設置 readonly value 
[teacher setValue:@"Jack" forKey:@"name"]; 
// teacher.name = @"Jack"; 
    
// 設置 private value 
[teacher setValue:@24 forKey:@"age"]; 
// teacher.age = 24; 
[teacher setValue:@1 forKey:@"male"]; 
[teacher log]; 

// 獲取 readonly value 
NSLog(@"name: %@", [teacher valueForKey:@"_name"]); 
// 獲取 private value 
NSLog(@"age: %d", [[teacher valueForKey:@"_age"] intValue]); 
NSLog(@"male: %d", [[teacher valueForKey:@"isMale"] boolValue]); 

複製代碼

    1. 需改私有屬性-例子2:修改 TextField 的 placeholder: 注意這裏用了keypath
[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];   
[_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@「_placeholderLabel.font"]; 

複製代碼

    1. 需改私有屬性-例子3:修改 UIPageControl 的圖片:
[_pageControl setValue:[UIImage imageNamed:@"selected"] forKeyPath:@"_currentPageImage"]; 
[_pageControl setValue:[UIImage imageNamed:@"unselected"] forKeyPath:@"_pageImage"]; 

複製代碼

2.4 功能3:字典轉模型

- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues; 

複製代碼

2.4.1 kvc 語法覆蓋

  • key 和keypath的區別?markdown

    • key只能接受當前類所具備的屬性,無論是本身的,仍是從父類繼承過來的,如view.setValue(CGRectZero(),key: "frame");函數

    • keypath: 除了能接受當前類的屬性,還能接受當前類屬性的屬性,便可以接受關係鏈,如view.setValue(5,keypath: "layer.cornerRadius」)編碼

  • 舉個例子說明問題1:atom

  • 例子:好比person有個屬性是address,address有個屬性是town,如今咱們如何經過person訪問town屬性?spa

  • 答:若是經過key來訪問指針

id address = [person valueForKey:@"address"]; 
id town = [address valueForKey:@"town」]; 
// 若是經過keypath來訪問 
id town = [person valueForKeyPath:@"address.town"]; 

複製代碼

2.4.2 語法(設置值,設置數據)

// value 的值爲OC對象,若是是基本數據類型要包裝成NSNumber 
- (void)setValue:(id)value forKey:(NSString *)key; 

// keyPath 鍵路徑,類型爲xx.xx 
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath; 

// 它的默認實現是拋出異常,能夠重寫這個函數作錯誤處理。 
- (void)setValue:(id)value forUndefinedKey:(NSString *)key; 

複製代碼

2.4.3 語法 (獲取值,獲取數據)

- (id)valueForKey:(NSString *)key; 
- (id)valueForKeyPath:(NSString *)keyPath; 

// 若是Key不存在,且沒有KVC沒法搜索到任何和Key有關的字段或者屬性,則會調用這個方法,默認是拋出異常 
- (id)valueForUndefinedKey:(NSString *)key; 

複製代碼

2.4.4 其餘語法

// 容許直接訪問實例變量,默認返回YES。若是某個類重寫了這個方法,且返回NO,則KVC不能夠訪問該類。 
+ (BOOL)accessInstanceVariablesDirectly; 

// 這是集合操做的API,裏面還有一系列這樣的API,若是屬性是一個NSMutableArray,那麼能夠用這個方法來返回 
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; 

// 若是你在setValue方法時面給Value傳nil,則會調用這個方法 
- (void)setNilValueForKey:(NSString *)key; 

// 輸入一組key,返回該組key對應的Value,再轉成字典返回,用於將Model轉到字典。 
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys; 

// KVC 提供屬性值確認的API,它能夠用來檢查set的值是否正確、爲不正確的值作一個替換值或者拒絕設置新值並返回錯誤緣由。 
- (BOOL)validateValue:(id)ioValue forKey:(NSString *)inKey error:(NSError)outError; 

複製代碼
相關文章
相關標籤/搜索