Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code
The directory is as follows:
- Exploring the underlying principles of iOS alloc
- Exploration of the underlying principles of iOS structure in vivo alignment
- The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
- The underlying principles of the ISA-class (part 1)
- The underlying principles of the ISA class (middle)
- The underlying principles of isA-Class (part 2)
- Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
- Objc_msgSend explores the underlying principles of iOS
- Runtime Runtime slow lookup process
- Dynamic method resolution for exploring the underlying principles of iOS
- IOS underlying principles to explore the message forwarding process
- IOS Application loading principle
- Application loading principle (2)
- IOS underlying principle exploration class load
- IOS Underlying principles to explore the classification of loading
- Associated object for iOS underlying principle exploration
- Exploration of iOS underlying principles of sorcerer KVC
Summary column for the above
- Summary of the phase of iOS underlying principle exploration
What is the KVO
Key-Value Observing Programming Guide
-
Key-value observation is a mechanism that allows an object to be notified when a specified property of another object changes.
-
Key-value observation provides a mechanism to allow objects to be notified when certain properties of other objects change. It is especially useful for communication between the model layer and the controller layer in an application. (In OS X, controller layer binding technology relies heavily on key-value observation.) Controller objects typically observe the properties of model objects, and view objects observe the properties of model objects through controllers. In addition, however, model objects may observe other model objects (often to determine when the dependency values change) or even themselves (again to determine when the dependency values change).
-
You can look at properties, including simple properties, one-to-one relationships, and many-to-many relationships. Observers of the many-to-many relationship are told what type of change was made — and which objects participated in the change.
A simple example shows how KVO can work in your application. Suppose a Person object interacts with an Account object that represents that Person’s savings Account at a bank. Instance Person might need to know when some aspect of the instance’s Account has changed, such as the balance or the interest rate.
If these properties were the public property Account for, Person could periodically poll the Account for changes, but of course this would be inefficient and often impractical. A better approach is to use KVO, which is similar to Person receiving interrupts when changes are made.
To use KVO, you must first ensure that the object being observed, Account in this case, is KVO compatible. In general, if your object inherits from NSObject and creates properties in the usual way, your object and its properties will automatically conform to KVO. Compliance can also be implemented manually. KVO compliance describes the difference between automatic and manual key-value observation and how to implement both.
Next, you must register your observer instance Person, using the observed instance Account. For each observed the critical path, the Person to send a addObserver: forKeyPath: options: context: news Account, named their observers.
To receive change notification Account, the Person to realize observeValueForKeyPath: ofObject: change: context: all the observer method required. Account sends this message to Person every time one of the registered key paths changes. Person can then take appropriate action based on the change notification.
In the end, when it no longer needs to notice, at least before it was released from distribution, the Person instance must pass the message to send removeObserver: forKeyPath: to Account.
Registration key observation describes the entire life cycle of registration, receive, and unregistration key observation notifications.
The main benefit of KVO is that you don’t have to implement your own scheme to send notifications every time a property changes. Its well-defined infrastructure has frame-level support, making it easy to adopt — typically you don’t need to add any code to the project. In addition, the infrastructure is fully functional and can easily support multiple observers of a single attribute as well as dependent values.
Registering dependent keys explains how to specify that the value of one key depends on the value of another key.
Unlike the notifications used, NSNotificationCenter, there is no central object that provides change notifications for all observers. Instead, when a change is made, notification is sent directly to the observed object. NSObject provides a basic implementation of this key-value observation, and you should rarely need to override these methods.
Key-value Observing Implementation Details describes how to implement key-value Observing.
Register key value observation
The following steps must be performed in order for an object to receive key-value observation notifications for KVO compatible properties:
- Use methods to register observers with the observed object
addObserver:forKeyPath:options:context:
. observeValueForKeyPath:ofObject:change:context:
Implemented inside the observer to accept change notification messages.- When the observer
removeObserver:forKeyPath:
Use this method to unregister an observer when it should no longer be receiving messages. At the very least, this method is called before the observer is freed from memory.
Register as an observer
Observation object first by sending addObserver: forKeyPath: options: context: the message to be registered himself, looking at an object itself as an observer and to observe the properties of the critical path. The observer also specifies an option parameter and a context pointer to manage aspects of the notification.
options
The options argument specifies bitwise for the OR option constant, affecting the contents of the change dictionary provided in the notification and how the notification is generated.
You can specify the option to choose from the observed change before receiving the value of the attribute NSKeyValueObservingOptionOld. You use the new value of the option to request attribute NSKeyValueObservingOptionNew. You can OR receive old and new values by bitwise for these options.
Fired you indicate the addObserver: forKeyPath: options: context: use the option to send immediately change notification NSKeyValueObservingOptionInitial (before return). You can use this additional one-time notification to establish the initial value of the property in the observer.
You can include option to indicate that the observed object before the property change notifications (in addition to change usually notice) NSKeyValueObservingOptionPrior. Change the dictionary by including NSKeyValueChangeNotificationIsPriorKey represented with NSNumberwrapping value key change notification in advance YES. The key does not exist. When the observer’s own KVO compliance requires it to be willChange… You can use pre-change notification when one of the properties – methods is called that depends on the property being observed. The usual change after notification is too late to call willChange… .
context
AddObserver: forKeyPath: options: context: in the message context pointer containing arbitrary data, the data will be back to the observer in the corresponding change notification. You can specify NULL and rely entirely on the key path string to determine the source of the change notification, but this approach can cause problems for objects where the superclass is also observing the same key path for different reasons.
A more secure and extensible approach is to use context to ensure that the notifications you receive are addressed to your observers and not to the superclass.
The address of the uniquely named static variable in the class is a good context. Contexts selected in a similar manner in a superclass or subclass are unlikely to overlap. You can select a context for the entire class and rely on the critical path string in the notification message to determine what has changed. Alternatively, you can create a different context for each observed key path, bypassing the need for string comparisons altogether and improving the efficiency of notification parsing. Listing 1 shows the sample context for the Balance and interestRate properties selected in this manner.
Listing 1 creates the context pointer
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
Copy the code
The following example demonstrates how a Person instance can use a given context pointer to register itself as an observer of the Balance and interestRate properties of the Account instance.
Listing 2 registers the inspector as an observer to the Balance and interestRate properties
- (void)registerAsObserverForAccount:(Account*)account {
[account addObserver:self
forKeyPath:@"balance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountBalanceContext];
[account addObserver:self
forKeyPath:@"interestRate"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountInterestRateContext];
}
Copy the code
Note: the key value observing addObserver: forKeyPath: options: context: method do not maintain the observation object, the observed object or context of strong reference. You should ensure that strong references to observed and observed objects and context are maintained as needed.
Receiving change notifications
When the object is to observe the value of the attribute changes, observers will receive a observeValueForKeyPath: ofObject: change: context: message. All observers must implement this method.
The observation object provides the critical path that triggers the notification, itself acts as a related object, contains a dictionary of detailed information about the change, and a context pointer that is provided when the observer registers for this critical path.
The change dictionary entry NSKeyValueChangeKindKey provides information about the type of change that occurred. If the observed object’s value has changed, the NSKeyValueChangeKindKey entry returns NSKeyValueChangeSetting. The NSKeyValueChangeOldKey and NSKeyValueChangeNewKey entries in the change dictionary contain the property values before and after the change, depending on the options specified when the observer is registered. If the property is an object, the value is supplied directly. If the attribute is a scalar or C construct, the value is wrapped in an NSValue object (just like key-value encoding).
Observed if the property is a, on the relationship between the more described NSKeyValueChangeKindKey entries also said in the relationship between object is inserted, deleted or replaced by returning NSKeyValueChangeInsertion, NSKeyValueChangeRemoval or NSKeyValueChangeReplacement, respectively.
Change the dictionary entry NSKeyValueChangeIndexesKey is a NSIndexSet object, specify the relationship between the change in the index. If the registered observers will NSKeyValueObservingOptionNew or NSKeyValueObservingOptionOld specified as options, The NSKeyValueChangeOldKey and NSKeyValueChangeNewKey entries in the change dictionary are arrays of related object values before and after the change.
The example in listing 3 shows the observer observeValueForKeyPath: ofObject: change: context: implementation of the Person it recorded the old values and new values of the attribute balanceand interestRate, registered in listing 2.
Listing 3 observevalueforkeypath: ofObject: change: the context of the implementation:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == PersonAccountBalanceContext) {
// Do something with the balance…
} else if (context == PersonAccountInterestRateContext) {
// Do something with the interest rate…
} else {
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
Copy the code
If your NULL specifies the context when registering an observer, the notified critical path is compared to the critical path you are observing to determine what changes have occurred. If you use a single context for all observed critical paths, first test it against the context of the notification, then find a match, and use a critical path string comparison to determine exactly what has changed. If you provide a unique context for each critical path, as shown here, a series of simple pointer comparisons will also tell you whether the notification is for this observer, and if so, which critical path has changed.
In any case, the observer should observeValueForKeyPath: ofObject: change: context: in unable to identify the context (or in the simplest case, any critical path) call the superclass implementation, because it means that the superclass is registered notification.
Removes an object as an observer
You can send observed object removeObserver: forKeyPath: context: news, designated observation object, key path and context to remove key viewer. The example in Listing 4 shows Person deleting itself interestRate as an observer of Balanceand.
Listing 4 removes the inspector as the balance and interest rate observer
- (void)unregisterAsObserverForAccount:(Account*)account {
[account removeObserver:self
forKeyPath:@"balance"
context:PersonAccountBalanceContext];
[account removeObserver:self
forKeyPath:@"interestRate"
context:PersonAccountInterestRateContext];
}
Copy the code
Received removeObserver: forKeyPath: context: news, people will no longer receive observeValueForKeyPath: ofObject: change: context: the specified key path and the object of any message.
When removing an observer, keep the following in mind:
- If you are not already registered as an observer, asking to be removed as an observer causes
NSRangeException
. You canremoveObserver:forKeyPath:context:
Call only once for the corresponding calladdObserver:forKeyPath:options:context:
Or if this is not feasible in your applicationremoveObserver:forKeyPath:context:
Calls are placed in try/catch blocks to handle potential exceptions. - Observers do not automatically delete themselves when unallocated. The observed object continues to send notifications, ignoring the state of the observer. However, a change notification sent to a released object triggers a memory access exception just like any other message. Therefore, you want to make sure that the observer removes itself before it disappears from memory.
- The protocol does not provide a way to ask whether the object is the observer or the observed. Build your code to avoid release-related errors. A typical pattern is to register as an observer during observer initialization (for example, in
init
orviewDidLoad
) and unregister during release (usually indealloc
) to ensure that messages are correctly paired and added and deleted in order, and that observers are unregistered before being released from memory.
Some details of the KVO
Manual switch
For key values we can look at some special cases where we can manually switch them on and off,
- Class implements the following method: return NO
/ * Return YES if the key value observing mechanism should automatically calls - willChangeValueForKey: - didChangeValueForKey: - willChange: valuesAtIndexes: forKey: -didChange:valuesAtIndexes:forKey:, or -willChangeValueForKey:withSetMutation:usingObjects: - didChangeValueForKey: withSetMutation: usingObjects: whenever the instance of the class to receive the keys of the key code message, or call the key variable key coding compliance method. Starting with Mac OS 10.5, the default implementation of this method automatically searches the receiving class for a method whose name matches Pattern + and returns the result of calling the method (if found). Therefore, any such method must also return a BOOL. If no such method is found, then YES is returned. */ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;Copy the code
- In the attribute set method that needs to be observed manually, the implementation is as follows:
[self willChangeValueForKey:property];
property = nick;
[self didChangeValueForKey:property];
Copy the code
One-to-many processing
For one-to-many cases such as download progress
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double writtenData;
@property (nonatomic, assign) double totalData;
Copy the code
keyPathsForValuesAffectingValueForKey
Returns a set of key paths for properties whose values affect the values of the keyed properties.
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"downloadProgress"]) { NSArray *affectingKeys = @[@"totalData", @"writtenData"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; } - (NSString *)downloadProgress{ if (self.writtenData == 0) { self.writtenData = 10; } if (self.totalData == 0) { self.totalData = 100; } return [[nsstrings alloc] initWithFormat: @ "% f", 1.0 f * self writtenData/self totalData]; }Copy the code
Observation of collection properties
Observations about collection properties
To operate on collection properties, use the following methods:
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
They return a proxy object that behaves like an NSMutableArray object.
mutableSetValueForKey:
和mutableSetValueForKeyPath:
They return a proxy object that behaves like an NSMutableSet object.
mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
They return a proxy object that behaves like an NSMutableOrderedSet object.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.nick = [NSString stringWithFormat:@"%@+",self.person.nick]; Array [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"]; [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"]; [[self.person mutableArrayValueForKey:@"dateArray"] removeObject:@"1"]; [[self.person mutableArrayValueForKey:@"dateArray"] replaceObjectAtIndex:0 withObject:@"99"]; }Copy the code
So, once you look at the KVO of an array, you need to take advantage of the mechanism of KVC in order to see the changes, because the underlying KVO is also implemented by KVC.
extension
- In the log of the callback method, you can see the following:
indexes
You can see that is the subscript of the set property of the current operation.
What is kind? Browse through the source code and find the following:
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4,
};
Copy the code
conclusion
- At this point in KVO’s exploration, we would think that its underlying implementation is the handling of the observed setter methods (which are covered in the manual mode above), but is that actually true?
- And in the specification of KVO programming, there is a process of removing observers at the end, why do you need to remove them?
- In the use of KVO, are there any intermediate layer products (i.e. the system does something silently)?
With these questions in mind, we set up the arrangement of KVO concepts and the exploration of details, and we officially entered the exploration of the underlying principles of KVO.
Exploration of the principles of KVO
Before we begin our formal exploration, let’s make a little guess about how KVO works, based on the progress made so far:
guess
- When addObserver, change the ISA of the observed instance from the SMPerson class to a class that the system dynamically creates for us
- There’s code in the setter method (willchange, didchange) that you can get in the callback when you change it
- The removal of an observation silently reprocesses the class created by the system
Question:
- Does the system dynamically create a class for us when we add observations (does ISA change)?
- When we remove an observation, will we destroy the class that the system created for us?
- Which class is the setter method that we’re calling in our code?
- What is the relationship between the class we define, SMPerson, and the class the system helps us create?
With the above questions in mind, we began to test one by one and answer one by one.
Isa does change direction after adding observations
- The system will create it dynamically for usNSKVONotifying_Person
And points the ISA of the observed instance to this dynamically created class
- The system dynamically creates the time when we add the observation, because there was no NSKVONotifying_Person class before we added it
- What is the relationship between NSKVONotifying_Person and Person?
We define a method that prints all subclasses of the class, and then prints the subclass of Person before and after adding the observation, and the subclass of NSKVONotifying_Person after adding the observation.
Judging from the printoutPerson is the parent class of NSKVONotifying_Person
NSKVONotifying_Person
- What does the NSKVONotifying_Person class contain (objc_method_list, objc_ivar_list, objc_protocol_list, objc_cache)?
All methods are obtained by printing all methods in the class
- setName:-0x7fff207bf03f
Overrides the setter method for name, but still changes the property value in the instance of the Person class
- class-0x7fff207bdb49
A type
- dealloc-0x7fff207bd8f7
Method of release – silently point isa back at the time of destruction
- _isKVOA-0x7fff207bd8ef
An identity of a system derived class
Isa will point back after removing the observer
Will NSKVONotifying_Person be removed after that? We print all subclasses of Person on the first page, add observers on the second page, remove observers when the second page is destroyed, and print all subclasses of Person on the first page. NSKVONotifying_Person has not been removed.
Although isa moved during the observation period, it printedself.person.class“, will still printPerson
, NSKVONotifying_Person
He’s just in the back helping us out.
conclusion
What did NSKVONotifying_Person do in the setter
Because NSKVONotifying_Person is a dynamically generated class that overrides setter methods, the instance member variables cannot be observed because there are no setter methods (which can distinguish between properties and member variables). Here is a further verification:
- Add an observation line and hit a breakpoint
- watchpoint set variable self->_person->_name
- Breakpoint release
- Modify the name value
In the process of changing the name value, you can see from the stack information that the system has processed a lot of content
frame #2: Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 646
frame #3: Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 68
frame #4: Foundation`_NSSetObjectValueAndNotify + 269
Copy the code
_NSSetObjectValueAndNotify
NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:
Depending on the stack information, it also goes to [Person setName:] so the property value of the Person class changes
observeValueForKeyPath
ObserveValueForKeyPath and NSObject (NSKeyValueObservingPrivate) _changeValueForKey: key: key: usingBlock: twowillchange
和 didchange
It’s a perfect closed loop
After we add the observation, the system changes isa to point to NSKVONotifying_Person; Then, after you change the value of the property, you actually call the setter that NSKVONotifying_Person overrides; It calls the setter method for Person. This is then called to the KVO callback function observeValueForKeyPath. Finally, when the observation is removed, ISA is pointed back to Person.
KVO process
Finally, let’s sort out the KVO process:
conclusion
After our entire exploration process of the KVO principle, in fact, there is no special difficulty. I believe that you have done so much exploration on the bottom layer of Apple together with me. In fact, the whole exploration process and debugging content, you are more and more skilled and familiar with. In the next article, we will hand-write KVO and combine the characteristics of functional, together with the manual implementation. Everybody, come on!!