@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
方法来决定实例所属的类。