@interface BankAccount: NSobject
@property (nonatomic) NSNumber *currentBalance; // An attribute
@property (nonatomic) Person *owner; // A to-one relation
@property (nonatomic) NSArray <Transaction *>*transactions; // A to-many relation
@end
複製代碼
currentBalance
/owner
/transactions
都是BankAccount
的屬性。owner
屬性是一個對象,和BankAccount
構成一對一的關係,owner對象中的屬性改變後並不會影響到owner自己。html
爲了保持封裝,對象一般爲其接口上的屬性提供訪問器方法(accessor methods)。在使用訪問器方法時必須在編譯以前將屬性名稱寫入代碼中。訪問器方法的名稱成爲使用它的代碼的靜態部分。例如: [myAccount setCurrentBalance:@(100.0)];
這樣缺少靈活性,KVC提供了使用字符串標識符訪問對象屬性的更通用的機制。git
key: 標識特定屬性的字符串。一般表示屬性的key是代碼中顯示的屬性自己的名稱。 key必須使用ASCII編碼,可能不包含空格,而且一般是以小寫字母開頭(URL除外)。 上面的賦值過程使用KVC表示: [myAccount setValue:@(100.0) forKey:@"currentBalance"];
github
key path: 用來指定要遍歷的對象屬性序列的一串使用「.」分隔的key。序列中的第一個鍵的屬性是相對於接受者的,而且每一個後續鍵是相對於前一個屬性的值的。當須要使用一個方法來向下逐級獲取對象層次結構時,key path特別有用。 例如,owner.address.street
應用於銀行帳戶實例的key path是指存儲在銀行帳戶全部者地址中的street
字符串的值。數組
- (void)getAttributeValuesUsingKeys {
Account *myAccount = [[Account alloc] init];
myAccount.currBalance = @100;
Person *owner = [[Person alloc] init];
Address *address = [[Address alloc] init];
address.street = @"第三大道";
owner.address = address;
myAccount.owner = owner;
Transaction *t1 = [[Transaction alloc] init];
Person *p1 = [[Person alloc] init];
p1.name = @"p1";
t1.payee = p1;
Transaction *t2 = [[Transaction alloc] init];
Person *p2 = [[Person alloc] init];
p2.name = @"p2";
t2.payee = p2;
NSArray *ts = @[t1, t2];
myAccount.transactions = ts;
NSNumber *currBalance = [myAccount valueForKey:@"currBalance"];
NSLog(@"currBalance = %@", currBalance); // currBalance = 100
NSString *street = [myAccount valueForKeyPath:@"owner.address.street"];
NSLog(@"street = %@", street); // street = 第三大道
NSDictionary *values = [myAccount dictionaryWithValuesForKeys:@[@"currBalance", @"owner"]];
NSLog(@"values = %@", values); // values = {currBalance = 100; owner = "<Person: 0x60000179af40>";}
NSArray *payees = [myAccount valueForKeyPath:@"transactions.payee.name"];
NSLog(@"payees = %@", payees); // payees = (p1, p2)
// Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Account 0x600002685ee0> valueForUndefinedKey:]'
// [myAccount valueForKey:@"owner.address.street"];
// [myAccount valueForKey:@"test"];
// [myAccount dictionaryWithValuesForKeys:@[@"currBalance", @"transactions.payee.name"]];
}
複製代碼
- (void)settingAttributeValuesUsingKeys {
Account *myAccount = [[Account alloc] init];
[myAccount setValue:@100.0 forKey:@"currBalance"];
NSLog(@"currBalance = %@", myAccount.currBalance); // currBalance = 100
// operationTimes是非引用類型,這裏進行了和NSNumber的自動轉換
[myAccount setValue:@10 forKey:@"operationTimes"];
NSLog(@"operationTimes = %ld", myAccount.operationTimes); // operationTimes = 10
Person *owner = [[Person alloc] init];
Address *address = [[Address alloc] init];
[myAccount setValue:address forKeyPath:@"owner.address"]; // 這時候owner仍是null
NSLog(@"address = %@", myAccount.owner.address); // address = (null)
[myAccount setValue:owner forKeyPath:@"owner"];
[myAccount setValue:address forKeyPath:@"owner.address"];
NSLog(@"address = %@", myAccount.owner.address); // address = <Address: 0x600001a43550>
[myAccount setValuesForKeysWithDictionary:@{@"currBalance": @200.0, @"owner": owner}];
NSLog(@"currBalance = %@, owner = %@", myAccount.currBalance, myAccount.owner); // currBalance = 200, owner = <Person: 0x600001478ee0>
// Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Account 0x6000029c2490> setValue:forUndefinedKey:]: xxx'
// [myAccount setValue:@"value" forUndefinedKey:@"undefinedKey"];
// [myAccount setValuesForKeysWithDictionary:@{@"currBalance": @200.0, @"owner.address.street": @"第一大道"}];
}
複製代碼
符合鍵值編碼的對象以與公開其餘屬性相同的方式公開其多對多屬性。您可使用valueForKey:
或setValue:forKey:
來獲取或設置集合屬性。可是,當你想要操做這些集合內容的時候,使用協議定義的可變代理方法一般是最有效的。 該協議爲集合對象訪問定義了三種不一樣的代理方法,每種方法都有一個key和key path變量:安全
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
返回一個行爲相似NSMutableArray
的代理對象mutableSetValueForKey:
和mutableSetValueFOrKeyPath:
返回一個行爲相似NSMutableSet
的代理對象mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
返回一個行爲相似NSMutableOrderedSet
的代理對象 當您對代理對象進行操做,向對象添加元素,從中刪除元素或者替換其中的元素時,協議的默認實現會相應地修改基礎屬性。這比使用valueForKey:
獲取一個不可變的集合對象,再建立一個可修改的集合,而後把修改後的集合經過setValue:forKey:
更有效。在許多狀況下,它比直接使用可變屬性也是更有效的。這些方法爲持有集合對象的對象們提供了維護KVO特性的好處。- (void)accessingCollectionProperties {
Transaction *t1 = [[Transaction alloc] init];
Transaction *t2 = [[Transaction alloc] init];
Account *myAccount = [[Account alloc] init];
[myAccount addObserver:self forKeyPath:@"transactions" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
[myAccount setValue:@[t1, t2] forKey:@"transactions"];
NSLog(@"1st transactions = %@", myAccount.transactions); // 1st transactions = ("<Transaction: 0x6000009d1400>","<Transaction: 0x6000009d1420>")
NSMutableArray <Transaction *>*transactions = [myAccount mutableArrayValueForKey:@"transactions"];
[transactions addObject:[[Transaction alloc] init]];
NSLog(@"2nd transactions = %@", myAccount.transactions); // 2nd transactions = ("<Transaction: 0x6000009d1400>","<Transaction: 0x6000009d1420>","<Transaction: 0x6000009cabf0>")
[transactions removeLastObject];
NSLog(@"3th transactions = %@", myAccount.transactions); // 3th transactions = ("<Transaction: 0x6000009d1400>","<Transaction: 0x6000009d1420")
}
複製代碼
當您向valueForKeyPath:
消息發送符合鍵值編碼的對象時,能夠在key path中嵌入集合運算符。集合運算符是一個小的關鍵字列表之一,前面是一個@符號,它指定了getter應該執行的操做,以便在返回以前以某種方式操做數據。NSObject
爲valueForKeyPath:
提供了默認實現。 當key path包含集合運算符時,運算符以前的部分稱爲左鍵路徑,指示相對於消息接受者操做的集合,當你直接向一個集合(例如NSArray
)發送消息時左鍵路徑或許能夠省略。操做符以後的部分稱爲右鍵路徑,指定操做符應處理的集合中的屬性,除了@count
以外的全部操做符都須要一個右鍵路徑。 app
@count
是一個例外,它沒有正確的關鍵路徑並始終將返回一個NSNumber
實例。包括:@avg
/@count
/@max
/@min
/@sum
。NSArray
實例,該實例包含命名集合中保存的對象的某個子集。包含:@distinctUnionOfObjects
/@unionOfObjects
。NSArray
或NSSet
實例,它以某種方式組合嵌套集合的對象。包含:@distinctUnionOfArrays
/@unionOfArrays
/@distinctUnionOfSets
。- (void)usingCollectionOperators {
Transaction *t1 = [Transaction transactionWithPayee:@"Green Power" amount:@(120.00) date:[NSDate dateWithTimeIntervalSinceNow:100]];
Transaction *t3 = [Transaction transactionWithPayee:@"Green Power" amount:@(170.00) date:[NSDate dateWithTimeIntervalSinceNow:300]];
Transaction *t5 = [Transaction transactionWithPayee:@"Car Loan" amount:@(250.00) date:[NSDate dateWithTimeIntervalSinceNow:500]];
Transaction *t6 = [Transaction transactionWithPayee:@"Car Loan" amount:@(250.00) date:[NSDate dateWithTimeIntervalSinceNow:600]];
Transaction *t13 = [Transaction transactionWithPayee:@"Animal Hospital" amount:@(600.00) date:[NSDate dateWithTimeIntervalSinceNow:500]];
NSArray *transactions = @[t1, t3, t5, t6, t13];
/* 聚合運算符 * 聚合運算符能夠處理數組或屬性集,從而生成反映集合某些方面的單個值。 */
// @avg 平均值
NSNumber *transactionAverage = [transactions valueForKeyPath:@"@avg.amount"];
NSLog(@"transactionAverage = %@", transactionAverage); // transactionAverage = 278
// @count 個數
NSNumber *numberOfTransactions = [transactions valueForKeyPath:@"@count"];
NSLog(@"numberOfTransactions = %@", numberOfTransactions); // numberOfTransactions = 5
// @max 最大值 使用compare:進行比較
NSDate *latestDate = [transactions valueForKeyPath:@"@max.date"];
NSLog(@"latestDate = %@", latestDate); // latestDate = Thu Nov 1 15:05:59 2018
// @min 最小值 使用compare:進行比較
NSDate *earliestDate = [transactions valueForKeyPath:@"@min.date"];
NSLog(@"earliestDate = %@", earliestDate);// earliestDate = Thu Nov 1 14:57:39 2018
// @sum 總和
NSNumber *amountSum = [transactions valueForKeyPath:@"@sum.amount"];
NSLog(@"amountSum = %@", amountSum); // amountSum = 1390
/* 數組運算符 * * 數組運算符致使valueForKeyPath:返回與右鍵路徑指示的特定對象集相對應的對象數組。 * 若是使用數組運算符時任何葉對象爲nil,則valueForKeyPath:方法會引起異常。 **/
// @distinctUnionOfObjects 建立並返回一個數組,該數組包含與右鍵路徑指定的屬性對應的集合的不一樣對象。會刪除重複對象。
NSArray *distinctPayees = [transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
NSLog(@"distinctPayees = %@", distinctPayees); // distinctPayees = ("Green Power", "Animal Hospital", "Car Loan")
// @unionOfObjects 建立並返回一個數組,該數組包含與右鍵路徑指定的屬性對應的集合的全部對象。不刪除重複對象
NSArray *payees = [transactions valueForKeyPath:@"@unionOfObjects.payee"];
NSLog(@"payees = %@", payees); // payees = ("Green Power", "Green Power", "Car Loan", "Car Loan", "Animal Hospital")
/** 嵌套運算符 * * 嵌套運算符對嵌套集合進行操做,集合中的每一個條目都包含一個集合。 * 若是使用數組運算符時任何葉對象爲nil,則valueForKeyPath:方法會引起異常。 **/
Transaction *moreT1 = [Transaction transactionWithPayee:@"General Cable - Cottage" amount:@(120.00) date:[NSDate dateWithTimeIntervalSinceNow:10]];
Transaction *moreT2 = [Transaction transactionWithPayee:@"General Cable - Cottage" amount:@(1550.00) date:[NSDate dateWithTimeIntervalSinceNow:3]];
Transaction *moreT7 = [Transaction transactionWithPayee:@"Hobby Shop" amount:@(600.00) date:[NSDate dateWithTimeIntervalSinceNow:160]];
NSArray *moreTransactions = @[moreT1, moreT2, moreT7];
NSArray *arrayOfArrays = @[transactions, moreTransactions];
// @distinctUnionOfArrays 指定@distinctUnionOfArrays運算符時,valueForKeyPath:建立並返回一個數組,該數組包含與右鍵路徑指定的屬性對應的全部集合的組合的不一樣對象。
NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
NSLog(@"collectedDistinctPayees = %@", collectedDistinctPayees); // collectedDistinctPayees = ( "General Cable - Cottage", "Animal Hospital", "Hobby Shop", "Green Power", "Car Loan")
// @unionOfArrays 與@distinctUnionOfArrays 不一樣的是不會刪除相同的元素
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
NSLog(@"collectedPayees = %@", collectedPayees); // collectedPayees = ("Green Power", "Green Power", "Car Loan", "Car Loan", "Animal Hospital", "General Cable - Cottage", "General Cable - Cottage", "Hobby Shop")
// @distinctUnionOfSets 與@distinctUnionOfArrays做用相同,只是用於NSSet對象而不是NSArray
}
複製代碼
NSObject提供的NSkeyValueCoding協議的默認實現使用明肯定義的規則集將基於鍵的訪問器調用映射到對象的基礎屬性。這些協議方法使用「key」在其本身的對象實例中搜索訪問器、實例變量以及遵循某個命名規則的相關方法。雖然您不多修改此默認搜索,但瞭解它的工做方式會有所幫助,既能夠跟蹤鍵值編碼對象的行爲,也可使您本身的對象兼容KVC。ui
valueForKey:
的默認實現是,給定key
參數做爲輸入,經過下面的過程,在接收valueForKey:
調用的類實例中操做。編碼
get<Key>
/<key>
/is<Key>
/_<key>
。若是找到,調用該方法而且帶着方法的調用結果調轉到第5步執行;不然,繼續下一步。countOf<Key>
,objectIn<Key>AtIndex:
(對應於NSArray
定義的基本方法),和<key>AtIndexs:
(對應於NSArray
的方法objectsAtIndexs:
) 一旦找到第一個和其餘兩個中的至少一個,則建立一個響應因此NSArray
方法並返回該方法的集合代理對象。不然,執行第3步。 代理對象隨後將任何NSArray
接收到的一些組合的消息。**實際上,與符合鍵值編碼對象一塊兒工做的代理對象容許底層屬性的行爲就像它是NSArray
同樣,即使它不是。countOf<Key>
/enumeratorOf<Key>
/memberOf<Key>:
,對應NSSet
類的基本方法。 若是三個方法全找到了,則建立一個集合代理對象來響應全部的NSSet方法並返回。不然,執行第4步。accessInstanceVariablesDirectly
返回YES
(默認YES),則按序搜索如下實例變量:_<key>
/_is<Key>
/<key>
/is<Key>
。若是找到其中之一,直接獲取實例變量的值並跳轉到第5步;不然執行第6步。NSNumber
支持的標量,則將其存儲在NSNumber
實例中並返回;若是結果是NSNumber
不支持的標量,則轉換成NSValue
對象並返回valueForUndefinedKey:
,這個方法默認拋出異常,NSObject
的子類能夠重寫來自定義行爲。setValue:forKey:
的默認實現是給定key
和value
做爲參數輸入,嘗試把value
設置給以key
命名的屬性。過程以下:atom
set<Key>:
或_set<Key>
,若是找到,則使用輸入參數調用並結束。accessInstanceVariablesDirectly
返回YES
(默認爲YES),則按序搜索如下實例變量: _<key>
/_is<Key>
/<key>
/is<Key>
,若是找到了則直接進行賦值並結束。setValue:forUndefinedKey:
,這個方法默認拋出異常,NSObject
的子類能夠自定義。Key-value observing提供了一種機制,容許對象把自身屬性的更改通知給其餘屬性。它對應用程序中model和controller層之間的通訊特別有用。一般,控制器對象觀察模型對象的屬性,視圖對象經過控制器觀察模型對象的屬性。另外,一個模型對象或許會觀察另外一個模型對象(一般用與確認從屬值什麼時候改變)或甚至自身(再次確認從屬值什麼時候改變)。 你能夠觀察屬性,包括簡單屬性,一對一關係和多對多關係。多對多關係的觀察者被告知所做出的改變的類型——以及改變中涉及哪些對象。spa
addObserver:forKeyPath:options:content:
方法來給observer註冊一個observed objectobserverValueForKeyPath:ofObject:change:context:
來接收更改的通知消息。removeObserver:forKeyPath:
方法來反註冊觀察者。起碼也要在observer被移除前調用這個方法。addObserver:forKeyPath:options:content:
複製代碼
options參數指定了一個按位OR
的常量選項,會影響通知中提供的更改字典的內容和生成通知的方式。 你能夠選擇使用NSKeyValueObservingOptionOld
選項,在被觀察的屬性修改前收到舊值;也可使用NSKeyValueObservingOptionNew
來獲取修改後的新值。經過NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
獲取二者。 使用NSKeyValueObservingOptionInitial
選項,讓被觀察的屬性在addObserver:forKeyPath:options:context
方法返回前發送即時通知。你可使用此附加的一次性通知來在觀察者中創建屬性的初始值。 經過包含NSKeyValueObservingOptionPrior
來指示被觀察對象在屬性更改以前發送通知(除了在更改以後發送通知)。在更改以前發送的通知中的change
字典始終包含NSKeyValueChangeNotificationIsPriorKey
,其值是包含布爾值YES的NSNumber對象,但不包含NSKeyValueChangeNewKey
的內容。若是指定此選項,則更改後發送的通知中的change
字典的內容和未指定此選項時包含的內容相同。當觀察者本身的鍵值觀察兼容性要求它爲本身的一個屬性調用-willChangexxx方法之一時,可使用此選項,而且該屬性的值取決於被觀察對象的屬性的值。
addObserver:forKeyPath:options:context:
消息中的上下文指針包含將在相應的更改通知中傳遞迴觀察者的任意數據。您可使用NULL來徹底指定並依賴於key path
字符串來肯定更改通知的來源,可是這種方法可能會致使其超類也因不一樣緣由觀察到相同密鑰路徑的對象出現問題。
一個更安全且具備擴展性的方法是使用content
來確保你收到的通知就是發給你的而不是超類的。
類中惟一命名的靜態(static)變量的地址是一個很好的content。在超類或子類中以相似的方式選擇的上下文不太可能重疊。您能夠爲整個類選擇同一個上下文,並根據通知消息中的key path字符串來肯定更改的內容;或者,您能夠爲每一個觀察到的密鑰路徑建立不一樣的上下文,從而徹底繞過字符串比較的須要,從而實現更有效的通知解析。
- (void)registerAsObserver {
BankAccount *myAccount = [[BankAccount alloc] init];
[myAccount addObserver:self forKeyPath:@"currBalance" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:PersonAccountBalanceContext];
myAccount.currBalance = @100;
}
複製代碼
注意,鍵值觀察addObserver:forKeyPath:options:context:
方法不對觀察者、被觀察的對象、上下文保持強引用。如須要,你應該對它們保持強引用。
當對象的被觀察屬性值改變的時候,觀察者對象會收到observeValueForKeyPath:ofObject:change:context:
消息。全部的觀察者必須實現這個方法。
觀察對象提供觸發通知的key path,自身做爲object
,change
字典包含改變的細節,而且context
指針就是觀察者被註冊時提供的。
NSKeyValueChangeKindKey
提供改變類型的信息。NSKeyValueChangeKindKey
表示觀察對象的值已更改。若是觀察的屬性是一個對多的關係,NSKeyValueChangeInsertion
/NSKeyValueChangeRemoval
/NSKeyValueChangeReplacement
分別表示集合的插入、刪除、替換操做。NSKeyValueChangeIndexesKey
表示集合中已更改內容的NSIndexSet
。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == PersonAccountBalanceContext) {
NSLog(@"PersonAccountBalanceContext 對應的屬性改變了");
} else if (context == PersonAccountTransactionContext) {
if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting) {
NSLog(@"集合內容賦值 索引爲:%@", change[NSKeyValueChangeIndexesKey]);
} else if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeInsertion) {
NSLog(@"集合內容插入 索引爲:%@", change[NSKeyValueChangeIndexesKey]);
} else if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeRemoval) {
NSLog(@"集合內容刪除 索引爲:%@", change[NSKeyValueChangeIndexesKey]);
} else if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeReplacement) {
NSLog(@"集合內容替換 索引爲:%@", change[NSKeyValueChangeIndexesKey]);
}
}
}
複製代碼
經過想觀察者發送removeObserver:forKeyPath:context
消息來移除觀察者對象。收到該消息後,觀察者對象將再也不接收任何observerValueForKeyPath:ofObject:change:context
中指定key path/object的消息。
刪除觀察者時,注意:
爲了讓特定屬性符合KVO標準,class必須知足一下內容:
有兩種技術可確保發出KVO通知。NSObject提供自動支持,默認狀況下可用於符合鍵值編碼的類的全部屬性。一般,若是你遵照Cocoa編碼和命名約定,則可使用自動通知,而沒必要編寫任何代碼。
手動方式爲通知觸發時提供了更多的控制權,而且須要額外編碼。你能夠經過實現automaticallyNotifiesObserversForKey:
來控制子類屬性的自動通知。
下列方法列舉了會觸發自動通知的一些場景:
//調用訪問器方法。
[account setName:@「Savings」];
//使用setValue:forKey:。
[account setValue:@「Savings」forKey:@「name」];
//使用密鑰路徑,其中'account'是'document'的kvc兼容屬性。
[document setValue:@「Savings」forKeyPath:@「account.name」];
//使用mutableArrayValueForKey:檢索關係代理對象。
Transaction * newTransaction = <#爲賬戶#>建立新交易;
NSMutableArray * transactions = [account mutableArrayValueForKey:@「transactions」];
[transactions addObject:newTransaction];
複製代碼
有些狀況下,你可能想要控制通知的過程,例如,最大限度減小因應用程序特定緣由而沒必要要的觸發通知,或把一組通知整合到一個。
手動通知和自動通知不是互斥的。手動和自動的通知能夠同時觸發。若是你只想要手動觸發,則須要經過重寫automaticallyNotifiesObserversForKey:
方法來禁止自動通知。
+ (BOOL)automaticNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@「balance」]) {
automatic = NO;
}
else {
automatic = [super automaticNotifiesObserversForKey: theKey];
}
return automatic;
}
複製代碼
**要實現手動觀察者通知,你要在值改變前調用willChangeValueForKey:
,並在值改變後調用didChangeValueForKey:
。有三組相似的方法:
willChangeValueForKey:
和didChangeValueForKey:
。用於單個對象willChange:valuesAtIndexes:forKey:
和didChange:valuesAtIndexes:forKey:
。用於有序集合willChangeValueForKey:withSetMutation:usingObjects:
和willChangeValueForKey:withSetMutation:usingObjects:
。用於無須集合下面在訪問器方法中手動觸發:
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
複製代碼
爲了減小沒必要要的通知,能夠先檢查值是否改變了,而後決定是否發通知:
- (void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
複製代碼
若是一個操做致使多個key發生改變,必須嵌套發送通知:
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
[self willChangeValueForKey:@"itemChanged"];
_balance = theBalance;
_itemChanged = _itemChanged+1;
[self didChangeValueForKey:@"itemChanged"];
[self didChangeValueForKey:@"balance"];
}
複製代碼
在有序的to-many關係中,除了指定更改的key,還不準指定更改的類型和所涉及對象的索引。
- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
[self willChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
// Remove the transaction objects at the specified indexes.
[self didChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
}
複製代碼
在許多狀況下,一個屬性的值取決於另外一個對象中的一個或多個其餘屬性的值。若是一個屬性的值發生更改,則還應標記派生屬性的值以進行更改。
要爲一對一關係自動觸發通知,應該重寫keyPathsForValuesAffectingValueForKey
或實現一個合適的方法,該方法遵循它爲註冊依賴鍵定義的模式。
例如,fullName
取決於firstName
和lastName
。返回fullName
的方法能夠寫成以下:
- (NSString *)fullName {
return [NSString stringWithFormat:@「%@%@」,firstName,lastName];
}
複製代碼
當firstName
或lastName
發生改變時,必須通知觀察fullName
屬性的程序,由於它們影響這個屬性的值。
一個解決方案是重寫keyPathsForValuesAffectingValueForKey
來指定fullName
屬性依賴於lastName
和firstName
。
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"]) {
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
複製代碼
重寫一般應該調用super並返回一個集合,以避免影響超類中對此方法的重寫。
經過重寫keyPathsForValuesAffecting<Key>
也能夠達到相同的效果。
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
複製代碼
keyPathsForValuesAffectingValueForKey:
方法不支持to-many關係的key paths。可使用下面兩種方案來處理to-many 關係:
observeValueForKeyPath:ofObject:change:context:
方法中,你能夠更新依賴值以相應更改,以下:[self addObserver:self forKeyPath:@"transactions" options:NSKeyValueObservingOptionNew context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"amount = %@", [self valueForKeyPath:@"transactions.@sum.amount"]);
[self setTotalConsumption:[self valueForKeyPath:@"transactions.@sum.amount"]];
}
複製代碼
自動key-value observing 是使用一種叫作isa-swizzling的技術實現的。
isa指針指向維護一個調度表(dispatch table)的對象的類。該調度表包含了指向該類實現的方法的指針,以及其餘數據。
當觀察者註冊對象的屬性時,觀察對象的isa指針被修改,指向中間類而不是真正的類。所以,isa指針的值不必定反映實例的實際類。
永遠不要依賴isa指針來肯定類成員。而應該使用class
方法來決定實例所屬的類。