KVO提供了一種允許一個(gè)對(duì)象觀察另一個(gè)對(duì)象的屬性變化的機(jī)制,常用于model層和controller層之間的通信。
觀察者類型:
1.controller觀察model中的屬性
2.model觀察另一個(gè)model中的屬性
3.model觀察自身的屬性
可觀察的屬性類型:
1.簡(jiǎn)單的屬性
2.to-one relationships
3.to-many relationships
KVO實(shí)現(xiàn)原理
KVO通過(guò)isa-swizzling技術(shù)實(shí)現(xiàn)
當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí),系統(tǒng)會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,并將被觀察對(duì)象的isa指向這個(gè)派生類。
如果觀察者注冊(cè)了當(dāng)前對(duì)象的某個(gè)屬性的觀察,派生類會(huì)重寫該屬性的setter方法。
派生類在重寫的setter方法中實(shí)現(xiàn)真正的通知機(jī)制(我們也可以實(shí)現(xiàn)手動(dòng)通知)。
如果不使用setter方法改變屬性值,而是直接修改屬性對(duì)應(yīng)的成員變量,則不會(huì)觸發(fā)通知。
KVC(Key-Value Coding)
KVC定義了一種按名稱訪問(wèn)對(duì)象屬性的機(jī)制,支持這種訪問(wèn)的主要方法有
1. - (id)valueForKey:(NSString *)key;
2. - (void)setValue:(id)value forKey:(NSString *)key;
3. - (id)valueForKeyPath:(NSString *)keyPath;
4. - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
key對(duì)應(yīng)屬性名稱字符串,keyPath是一個(gè)被點(diǎn)操作符隔開(kāi)的字符串,用于訪問(wèn)對(duì)象屬性的屬性,如address.street將會(huì)訪問(wèn)消息接收對(duì)象的address屬性所包含的street屬性。(address和street不一定是property,但必須具有訪問(wèn)器方法)
KVC使用的一個(gè)舉例
@interface DetailViewController ()
@property (weak, nonatomic) IBOutlet UITextField *nameField;
@property (weak, nonatomic) IBOutlet UITextField *nicknameField;
@property (weak, nonatomic) IBOutlet UITextField *emailField;
@property (weak, nonatomic) IBOutlet UITextField *cityField;
@end
@implementation DetailViewController
- (NSArray *)contactStringKeys;
{
return @[@"name", @"nickname", @"email", @"city"];
}
- (UITextField *)textFieldForModelKey:(NSString *)key;
{
return [self valueForKey:[key stringByAppendingString:@"Field"]];
}
@end
textFieldForModelKey方法根據(jù)key值返回DetailViewController的UITextField屬性。
Key-Value Coding Accessor Method
通過(guò)Accessor來(lái)實(shí)現(xiàn)KVC
常用的訪問(wèn)器方法
-<key> //用于返回一個(gè)object/變量/struct
-is<Key> //用于返回布爾值
-set<key>
KVC按順序使用如下技術(shù):
1.檢查是否存在-<key>/-is<Key>/-get<key>的訪問(wèn)器方法,如果有則用這些方法返回值;檢查是否存在
-set<key>:,如果有則使用它設(shè)置值;(-is<Key>/-get<key>/-set<key>方法將大些字符串的第一個(gè)字母)
2.如果上述方法不可用,則檢查名為-_<key>、-_is<key>(只針對(duì)布爾值有效)、-_get<key>和-set<key>:方法
3.如果沒(méi)有找到訪問(wèn)器方法,可以嘗試直接訪問(wèn)實(shí)例變量。實(shí)例變量可以是名為:<key>或<key>;
4.如果仍未找到,則調(diào)用valueForUndefinedKey:和setValue:forUndefinedKey:方法。這些方法的默認(rèn)實(shí)現(xiàn)都是拋出異常,我們可以根據(jù)需要重寫它們。
to-Many屬性的集合訪問(wèn)器
使用集合訪問(wèn)器有以下好處:
1.處理可變集合(NSMutableArray/NSMutableSet/NSMutableOrderedSet)時(shí)可以得到性能的提升
2.實(shí)現(xiàn)一些適當(dāng)?shù)姆椒ǎ梢灾С炙袑?duì)集合對(duì)象的調(diào)用,但是不需要真的實(shí)現(xiàn)一個(gè)集合對(duì)象
3.可以使用集合訪問(wèn)器來(lái)改變集合,并發(fā)送KVO通知
| Getter Indexed Accessors | 注解 |
|---|---|
| -countOf<Key> | 必須,相當(dāng)于NSArray的Count屬性 |
| -objectIn<Key>AtIndex: or -<key>AtIndexs | 二者必實(shí)現(xiàn)一個(gè),相當(dāng)于objectAtIndex:/objectsAtIndex方法 |
| -get<key>:range: | 可選,可以增強(qiáng)性能,相當(dāng)于NSArray的getObjects:range:方法 |
| Mutable Indexed Accessors | 注解 |
|---|---|
| -insertObject:in<Key>AtIndex: or -insert<Key>:atIndexes: | 2選1,相當(dāng)于NSMutableArray的insertObject:atIndex: 和insertObjects:atIndexes: |
| -removeObjectFrom<Key>AtIndex: or -remove<Key>AtIndexes: | 2選1,相當(dāng)于NSMutableArray的removeObjectAtIndex:和removeObjectsAtIndexes: |
| -replaceObjectIn<Key>AtIndex:withObject: or -replace<Key>AtIndexes:with<Key>: | 可選,增強(qiáng)性能 |
鍵值驗(yàn)證 Key-Value Validation
KVC提供了驗(yàn)證Key對(duì)應(yīng)的Value是否可用的方法
- (BOOL)validateValue:(inout id *)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
該方法默認(rèn)的實(shí)現(xiàn)是調(diào)用一個(gè)如下格式的方法
- (BOOL)validate<Key>:error:
KVO鍵值觀察
某個(gè)對(duì)象anObserver通過(guò)以下方法注冊(cè)為觀察者:
- (void)addObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
在keyPath的值改變時(shí),觀察者的以下方法會(huì)被觸發(fā):
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
調(diào)用以下方法來(lái)移除觀察著:
- (void)removeObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
注冊(cè)依賴鍵
Foundation框架提供的表示屬性依賴的機(jī)制如下:
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
具體實(shí)現(xiàn)如下
+ (NSSet *)keyPathsForValuesAffecting<Key>
舉一個(gè)例子
+ (NSSet *)keyPathsForValuesAffectingRedComponent
{
return [NSSet setWithObject:@"lComponent"];
}
+ (NSSet *)keyPathsForValuesAffectingGreenComponent
{
return [NSSet setWithObjects:@"lComponent", @"aComponent", nil];
}
+ (NSSet *)keyPathsForValuesAffectingBlueComponent
{
return [NSSet setWithObjects:@"lComponent", @"bComponent", nil];
}
+ (NSSet *)keyPathsForValuesAffectingColor
{
return [NSSet setWithObjects:@"redComponent", @"greenComponent", @"blueComponent", nil];
}
上述代碼建立了color、{redComponent,greenComponent,blueComponent}和{lComponent、aComponent、bComponent}之間的依賴關(guān)系;如果某個(gè)對(duì)象注冊(cè)為color屬性的觀察者,那么lComponent、aComponent、bComponent任一屬性發(fā)生改變,都會(huì)觸發(fā)觀察者的observeValueForKeyPath方法。
屬性依賴的注冊(cè)方法在程序啟動(dòng)時(shí)即執(zhí)行,后續(xù)運(yùn)行中不再執(zhí)行以上方法;且l,a,b任一項(xiàng)發(fā)生改變,僅計(jì)算依賴它的屬性,不相關(guān)的屬性不會(huì)再次計(jì)算。
手動(dòng)通知
KVO實(shí)現(xiàn)原理為runtime動(dòng)態(tài)地重寫被觀察屬性的setter方法,我們也可以自己重寫setter方法來(lái)達(dá)到手動(dòng)通知的目的:
//關(guān)閉自動(dòng)調(diào)用
+ (BOOL)automaticallyNotifiesObserversForLComponent;
{
return NO;
}
- (void)setLComponent:(double)lComponent;
{
if (_lComponent == lComponent) {
return;
}
[self willChangeValueForKey:@"lComponent"];
_lComponent = lComponent;
[self didChangeValueForKey:@"lComponent"];
}