KVC

Basic principles of key-value Coding

Accessing object properties

@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
Copy the code

CurrentBalance/owner/the transactions are now properties. The owner property is an object, which has a one-to-one relationship with BankAccount. The change of the property in the owner object will not affect the owner itself.

To preserve encapsulation, objects typically provide accessor methods for properties on their interfaces. When using accessor methods, attribute names must be written into the code before compilation. The name of the accessor method becomes a static part of the code that uses it. Example: [myAccount setCurrentBalance:@(100.0)]; With this lack of flexibility, KVC provides a more general mechanism for accessing object properties using string identifiers.

Use keys and key paths to identify attributes of objects

Key: A string that identifies a specific attribute. The key that typically represents an attribute is the name of the attribute itself displayed in the code. Keys must be ASCII encoded, may not contain Spaces, and usually start with a lowercase letter (except for urls). [myAccount setValue:@(100.0) forKey:@”currentBalance”];

Key path: A string used to specify the sequence of object attributes to traverse using “. Delimited keys. The property of the first key in the sequence is relative to the receiver, and each subsequent key is relative to the value of the previous property. The key path is especially useful when you need to use a method to get an object hierarchy step by step. For example, owner.address.street’s key path applied to a bank account instance is the value of the Street string stored in the bank account owner’s address.

Get the value of the property using key

- (void)getAttributeValuesUsingKeys {
    Account *myAccount = [[Account alloc] init];
    myAccount.currBalance = @100;
    
    Person *owner = [[Person alloc] init];
    Address *address = [[Address alloc] init];
    address.street = @" Third Avenue";
    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 = third avenue
    
    NSDictionary *values = [myAccount dictionaryWithValuesForKeys:@[@"currBalance".@"owner"]].NSLog(@"values = %@", values); // values = {currBalance = 100; owner = "
      
       "; }
      
    
    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"]];
}
Copy the code

Set the property value with key

- (void)settingAttributeValuesUsingKeys {
    Account *myAccount = [[Account alloc] init];
    [myAccount setValue:@100.0 forKey:@"currBalance"];
    NSLog(@"currBalance = %@", myAccount.currBalance); // currBalance = 100
    
    // operationTimes is a non-reference type, which is automatically converted to 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"]; // The owner is still 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:" @ "first avenue"});
}
Copy the code

Accessing collection properties

Objects that conform to the key-value encoding expose their many-to-many properties in the same way as any other property. You can use valueForKey: or setValue:forKey: to get or set the collection properties. However, when you want to manipulate the contents of these collections, using the protocol-defined variable proxy method is usually the most effective. The protocol defines three different proxy methods for collection object access, each with a key and key path variable:

  • mutableArrayValueForKey:andmutableArrayValueForKeyPath:Returns a behavior similar toNSMutableArrayProxy object of
  • mutableSetValueForKey:andmutableSetValueFOrKeyPath:Returns a behavior similar toNSMutableSetProxy object of
  • mutableOrderedSetValueForKey:andmutableOrderedSetValueForKeyPath:Returns a behavior similar toNSMutableOrderedSetWhen you operate on a proxy object, add elements to it, delete elements from it, or replace elements in it, the default implementation of the protocol modifies the underlying properties accordingly. This is more than usingvalueForKey:Get an immutable collection object, create a modifiable collection, and pass the modified collectionsetValue:forKey:More effective. In many cases, it is also more efficient than using mutable attributes directly. These methods provide the benefits of maintaining KVO features for objects that hold collection objects.
- (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")
}
Copy the code

Use the collection operator

You can embed the set operator in the key path when you send an object that matches the key-value encoding to the valueForKeyPath: message. The collection operator is one of a small list of keywords, preceded by an @ symbol, that specifies what the getter should do to manipulate the data in some way before returning it. NSObject provides a default implementation for valueForKeyPath:. When the key path contains the set operator, the part before the operator is called the left key path, indicating the set of operations relative to the message recipient. The left key path may be omitted when you send a message directly to a set (such as NSArray). The part after the operator, called the right-click path, specifies the properties in the collection that the operator should handle. All operators except @count require a right-click path.

  • Aggregate operatorSomehow merges the objects of a collection and returns a single object that normally matches the data type of the property specified in the right-click path.@countIs an exception that does not have the correct critical path and will always return oneNSNumberInstance. Include:@avg/@count/@max/@min/@sum.
  • Array operatorReturns aNSArrayInstance that contains some subset of the objects held in the named collection. Contains:@distinctUnionOfObjects/@unionOfObjects.
  • Nested operatorsProcesses collections containing other collections and returns one based on the operatorNSArrayorNSSetInstance that somehow combines objects of a nested collection. Contains:@distinctUnionOfArrays/@unionOfArrays/@distinctUnionOfSets.

The sample

- (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];
    
    /* Aggregate operators * Aggregate operators can work with arrays or sets of attributes to generate a single value that reflects some aspect of the collection. * /
    // @avg average
    NSNumber *transactionAverage = [transactions valueForKeyPath:@"@avg.amount"];
    NSLog(@"transactionAverage = %@", transactionAverage); // transactionAverage = 278
    / / @ count number
    NSNumber *numberOfTransactions = [transactions valueForKeyPath:@"@count"];
    NSLog(@"numberOfTransactions = %@", numberOfTransactions); // numberOfTransactions = 5
    // @max Maximum value: compare
    NSDate *latestDate = [transactions valueForKeyPath:@"@max.date"];
    NSLog(@"latestDate = %@", latestDate); // latestDate = Thu Nov 1 15:05:59 2018
    // @min Min use compare: to compare
    NSDate *earliestDate = [transactions valueForKeyPath:@"@min.date"];
    NSLog(@"earliestDate = %@", earliestDate);// earliestDate = Thu Nov 1 14:57:39 2018
    / / @ sum total
    NSNumber *amountSum = [transactions valueForKeyPath:@"@sum.amount"];
    NSLog(@"amountSum = %@", amountSum); // amountSum = 1390
    
    The /* array operator * * The array operator causes valueForKeyPath: to return an array of objects corresponding to the specific set of objects indicated by the right-click path. * The valueForKeyPath: method throws an exception if any leaf object is nil when using the array operator. * * /
    // @distinctunionofObjects creates and returns an array of different objects corresponding to the collection of properties specified by the right-click path. Duplicate objects are deleted.
    NSArray *distinctPayees = [transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
    NSLog(@"distinctPayees = %@", distinctPayees); // distinctPayees = ("Green Power", "Animal Hospital", "Car Loan")
    
    // @unionofObjects creates and returns an array containing all the objects in the collection corresponding to the property specified in the right-click path. Duplicate objects are not deleted
    NSArray *payees = [transactions valueForKeyPath:@"@unionOfObjects.payee"];
    NSLog(@"payees = %@", payees); // payees = ("Green Power", "Green Power", "Car Loan", "Car Loan", "Animal Hospital")
    
    /** Nested operator ** The nested operator operates on nested collections, each item in which contains a collection. * The valueForKeyPath: method throws an exception if any leaf object is nil when using the array operator. * * /
    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 When specifying the @distinctUnionofArrays operator, valueForKeyPath: creates and returns an array of different objects containing combinations of all collections corresponding to the properties specified by the right-click path.
    NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
    NSLog(@"collectedDistinctPayees = %@", collectedDistinctPayees); // collectedDistinctPayees = ( "General Cable - Cottage", "Animal Hospital", "Hobby Shop", "Green Power", "Car Loan")
    // @unionofArrays differs from @distinctUnionofArrays in that it does not delete the same elements
    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 works the same as @distinctunionofArrays except for NSSet objects instead of NSArray
}
Copy the code

Visitor search pattern

The default implementation of the NSkeyValueCoding protocol provided by NSObject uses a well-defined set of rules to map key-based accessor calls to the underlying properties of the object. These protocol methods use “keys” to search for accessors, instance variables, and related methods that follow a naming convention in their own object instances. Although you rarely modify this default search, it helps to know how it works, both to track the behavior of key-encoded objects and to make your own kVC-compliant objects.

Getter search mode

The default implementation of valueForKey: is to operate on the class instance that receives the valueForKey: call, given the key argument as input, through the following procedure.

  1. Search the visitor methods in orderget<Key>/<key>/is<Key>/_<key>. If found, call the method and proceed to step 5 with the result of calling the method; Otherwise, proceed to the next step.
  2. If no simple access method is found, search for instances of methods whose names match some pattern. Where the matching pattern containscountOf<Key>.objectIn<Key>AtIndex:(corresponding to theNSArrayBasic methods of definition), and<key>AtIndexs:(corresponding to theNSArrayThe method ofobjectsAtIndexs:Once the first and at least one of the other two are found, a response is createdNSArrayMethod and returns the collection proxy object for that method. Otherwise, perform Step 3. The proxy object will then have anyNSArraySome combination of messages received. ** In fact, proxy objects that work with key-value conforming encoding objects allow the underlying property to behave as if it wereNSArraySame, even if it’s not.
  3. If no simple accessor method or array access method group is found, look for three methodscountOf<Key>/enumeratorOf<Key>/memberOf<Key>:, corresponding toNSSetClass basic methods. If all three methods are found, a collection proxy object is created to respond to all NSSet methods and return. Otherwise, perform Step 4.
  4. If none of the above methods are found, and the recipient’s class methodaccessInstanceVariablesDirectlyreturnYES(default YES), the following instance variables are searched in order:_<key>/_is<Key>/<key>/is<Key>. If one of these is found, simply get the value of the instance variable and jump to step 5; Otherwise, go to Step 6.
  5. If the retrieved property value is an object pointer, only the result is returned; If the value is PINSNumberThe supported scalar is stored inNSNumberInstance and return; If the result isNSNumberUnsupported scalars are converted toNSValueObject and return
  6. Is called if all previous attempts failvalueForUndefinedKey:, this method throws an exception by default,NSObjectSubclasses can override custom behavior.

Setter’s search mode

The default implementation of setValue:forKey: attempts to set the value to a property named after the key given a key and value as parameters. The process is as follows:

  1. Sequential searchset<Key>:or_set<Key>If found, it is called with input arguments and ends.
  2. If no simple accessor method is found, and if the class methodaccessInstanceVariablesDirectlyreturnYES(the default is YES), the following instance variables are searched in order:_<key>/_is<Key>/<key>/is<Key>If found, the assignment is performed and the end is complete.
  3. Call if all the above methods failsetValue:forUndefinedKey:, this method throws an exception by default,NSObjectSubclasses of “can be customized.

KVO

Key-value observing provides a mechanism that allows an object to notify other properties of changes to its own properties. It is particularly useful for communication between the Model and Controller layers in an application. In general, the controller object views the properties of the model object, and the view object views the properties of the model object through the controller. In addition, a model object may observe another model object (usually by validating when a dependent value changes) or even itself (revalidating when a dependent value changes). You can look at properties, including simple properties, one-to-one relationships and many-to-many relationships. Observers of a many-to-many relationship are informed of the type of change being made — and which objects are involved in the change.

Registered KVO

  • useaddObserver:forKeyPath:options:content:Method to register a Observed object to the Observer
  • Implemented within the ObserverobserverValueForKeyPath:ofObject:change:context:To receive notification messages for changes.
  • Used when the message should no longer be receivedremoveObserver:forKeyPath:Method to unregister the observer. This method should at least be called before the observer is removed.

Registration of the Observer

addObserver:forKeyPath:options:content:
Copy the code

options

The options parameter specifies a bitwise OR constant option that affects the contents of the dictionary provided in the notification and how the notification is generated. You can choose to use the NSKeyValueObservingOptionOld option and received before observed attribute to modify the old value; You can also use NSKeyValueObservingOptionNew to obtain new value of the modified. Through NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew for both. Use NSKeyValueObservingOptionInitial option, observed attributes in the addObserver: forKeyPath: options: context method returns before sending instant notification. You can use this additional one-time notification to set up the initial value of the property in the observer. By including NSKeyValueObservingOptionPrior to indicate that the observed object before the property change notifications (in addition to send notification after changes). Send notice the change in the dictionary before change always contain NSKeyValueChangeNotificationIsPriorKey, its value is a Boolean value YES NSNumber object, but does not contain NSKeyValueChangeNewKey content. If this option is specified, the contents of the change dictionary in the notification sent after the change are the same as if this option was not specified. This option can be used when the observer’s own key-value observation compatibility requires it to call one of the -WillChangexxx methods for one of its properties, and the value of the property depends on the value of the property of the object being observed.

context

AddObserver: forKeyPath: options: context: pointer within the context of the message contained in the corresponding change notification of any data passed back to the observer. You can use NULL to fully specify and rely on the key path string to determine the source of change notifications, but this approach can cause problems with objects whose superclasses also observe the same key path for different reasons.

A more secure and extensible approach is to use Content to ensure that the notifications you receive are addressed to you and not the superclass.

The address of a uniquely named static variable in a class is a good content. Contexts selected in a similar manner in a superclass or subclass are unlikely to overlap. You can select the same context for the entire class and determine what changes are made based on the key Path string in the notification message; Alternatively, you can create a different context for each observed key path, bypassing the need for string comparisons altogether and enabling more efficient notification resolution.

- (void)registerAsObserver {
    BankAccount *myAccount = [[BankAccount alloc] init];
    [myAccount addObserver:self forKeyPath:@"currBalance" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:PersonAccountBalanceContext];
    myAccount.currBalance = @100;
}
Copy the code

Note that the key value observing addObserver: forKeyPath: options: context: right way observer and the observed object, context, keep strong reference. You should keep strong references to them if necessary.

Accept notice of change

When the object is observed attribute values change, the observer object will receive observeValueForKeyPath: ofObject: change: context: message. All observers must implement this method.

The observer provides the key path that triggers the notification, itself as an object, the change dictionary contains the details of the change, and the context pointer is provided when the observer is registered.

NSKeyValueChangeKindKey provides information to change the type. NSKeyValueChangeKindKey indicates that the value of the observed object has changed. If observations of property is a, on the relationship between the more NSKeyValueChangeInsertion NSKeyValueChangeRemoval/NSKeyValueChangeReplacement respectively set insert, delete, replace operation. NSIndexSet NSKeyValueChangeIndexesKey said the collection changed content.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context {
    if (context == PersonAccountBalanceContext) {
        NSLog(@ PersonAccountBalanceContext corresponding properties changed "");
    } else if (context == PersonAccountTransactionContext) {
        if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting) {
            NSLog(@" Set content assignment index: %@", change[NSKeyValueChangeIndexesKey]);
        } else if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeInsertion) {
            NSLog(@" Set content insert index: %@", change[NSKeyValueChangeIndexesKey]);
        } else if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeRemoval) {
            NSLog(@" Set content delete index: %@", change[NSKeyValueChangeIndexesKey]);
        } else if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeReplacement) {
            NSLog(@" Set content replacement index: %@", change[NSKeyValueChangeIndexesKey]); }}}Copy the code

Remove observer object

Through to send observers removeObserver: forKeyPath: context information object to remove the observer. After receiving the message, will no longer receive any observerValueForKeyPath observer object: ofObject: change: in the context of the specified key path/object.

When deleting an observer, note:

  • Remove and add methods should be symmetrical, otherwise exceptions will be thrown. If symmetry cannot be maintained, the removed method is placed in a try/catch block.
  • When the object is released, it does not automatically remove itself from the observer, and the observer continues to send notifications. But just like any other message, notification of a change sent to the freed object triggers a memory access exception. Therefore, it is important to remove the observer before it disappears from memory
  • The protocol does not provide a way to query whether an object is observed or observed. You must avoid errors in your code. The typical scenario is to register as an observer during observer initialization (init or Dealloc) and unregister at release (dealloc).

Compatible with KVO

In order for a particular attribute to comply with the KVO standard, a class must satisfy the following:

  • The class must be a KVC conforming to this attribute
  • This class triggers a KVO notification for this property
  • The relevant key has been successfully registered

There are two techniques to ensure that KVO notifications are issued. NSObject provides automatic support for all properties of classes that conform to key-value encoding by default. In general, if you follow Cocoa encoding and naming conventions, you can use automatic notifications without having to write any code.

The manual approach provides more control over notification triggering and requires additional coding. You can by implementing automaticallyNotifiesObserversForKey: to control the automatic notification of subclasses properties.

Automatic notification

The following methods list some scenarios in which automatic notification can be triggered:

// Call the accessor method.[account elegantly-named setName: @ "Savings"];// use setValue: forKey:[account setValue: @ "Savings" forKey: @ "name"];// Use the key path, where 'account' is the KVC-compatible property of 'document'.[Document setValue: @ "Savings" forKeyPath: @ "account.name"];// Use mutableArrayValueForKey to retrieve the relational proxy object.Transaction * newTransaction = < # create a newTransaction for the account # >;NSMutableArray* Transactions = [Account mutableArrayValueForKey: @ "transactions"]; [the transactions addObject: newTransaction];Copy the code

Manual to inform

In some cases, you may want to control the notification process, for example, to minimize unnecessary triggering of notifications for application-specific reasons, or to consolidate a group of notifications into one.

Manual and automatic notifications are not mutually exclusive. Manual and automatic notifications can be triggered simultaneously. If you just want to manually trigger, you may need to rewrite the automaticallyNotifiesObserversForKey: method to disable automatic notification.

+ (BOOL)automaticNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if([theKey isEqualToString: @ "balance"]) {automatic =NO;
    }
    else {
        automatic = [super automaticNotifiesObserversForKey: theKey];
    }
    return automatic;
}
Copy the code

** To implement manual observer notification, you call willChangeValueForKey: before the value changes and didChangeValueForKey: after the value changes. There are three groups of similar methods:

  • willChangeValueForKey:anddidChangeValueForKey:. For a single object
  • willChange:valuesAtIndexes:forKey:anddidChange:valuesAtIndexes:forKey:. Used of ordered sets.
  • willChangeValueForKey:withSetMutation:usingObjects:andwillChangeValueForKey:withSetMutation:usingObjects:. Used of unnecessary sets

The following is triggered manually in accessor methods:

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    _balance = theBalance;
    [self didChangeValueForKey:@"balance"];
}
Copy the code

To reduce unnecessary notifications, check to see if the value has changed and then decide whether to send notifications:

- (void)setBalance:(double)theBalance {
    if(theBalance ! = _balance) { [self willChangeValueForKey:@"balance"];
        _balance = theBalance;
        [self didChangeValueForKey:@"balance"]; }}Copy the code

If an operation causes multiple keys to change, you must send nested notifications:

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    [self willChangeValueForKey:@"itemChanged"];
    _balance = theBalance;
    _itemChanged = _itemChanged+1;
    [self didChangeValueForKey:@"itemChanged"];
    [self didChangeValueForKey:@"balance"];
}
Copy the code

In an ordered to-many relationship, in addition to specifying the key of the change, you are not allowed to specify the type of change and the index of the object involved.

- (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"];
}
Copy the code

Register affiliate keys

In many cases, the value of one attribute depends on the value of one or more other attributes in another object. If the value of an attribute changes, the value of a derived attribute should also be marked for change.

The To – One relationship

For one-to-one relationship automatically trigger notifications, it should be rewritten keyPathsForValuesAffectingValueForKey or achieve a suitable method, the method follows the pattern of it as a registered rely on key definitions.

For example, fullName depends on firstName and lastName. The fullName method can be written as follows:

- (NSString*) fullName {return [NSStringStringWithFormat: @ "% @ % @", firstName, lastName]; }Copy the code

Programs observing the fullName attribute must be notified when firstName or lastName changes because they affect the value of the attribute.

One solution is to rewrite the keyPathsForValuesAffectingValueForKey to specify fullName attribute is dependent on the lastName and firstName.

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
 
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
 
    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName".@"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
Copy the code

Overrides should normally call super and return a collection so as not to affect overrides of this method in the superclass.

Through rewriting keyPathsForValuesAffecting < Key > can also achieve the same effect.

+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName".@"firstName".nil];
}
Copy the code

The To – many relationships

KeyPathsForValuesAffectingValueForKey: method does not support the to – many relationships of key paths. You can use the following two schemes to handle to-many relationships:

  1. Use key-value observing to register the parent item as the relevant attribute observer of the child item. When a child object is added to or removed from a relationship, you must add or remove the parent object. inobserveValueForKeyPath:ofObject:change:context:Method, you can update the dependency values to change accordingly, as follows:
[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:@"[email protected]"]);
    [self setTotalConsumption:[self valueForKeyPath:@"[email protected]"]];
}
Copy the code
  1. In Core Data, you can register a parent item with your application’s notification center as an observer of its managed object context. The parent item should respond to relevant change notifications from children in a manner similar to key-value observations.

Key-value Observing implementation details

Automatic key-value observing is implemented using a technique called ISa-Swizzling.

The ISA pointer points to the class of objects that maintain a Dispatch table. The schedule table contains Pointers to the methods implemented by the class, as well as other data.

When an observer registers an object’s properties, the isa pointer to the observed object is modified to point to the intermediate class instead of the real class. Therefore, the value of the ISA pointer does not necessarily reflect the actual class of the instance.

Never rely on isa Pointers to determine class members. Instead, use the class method to determine which class the instance belongs to.

Refer to the connection

The sample code

KVC

KVO