1.KVC
I have written about KVC and KVO in my previous summary articles, but they tend to be superficial, without exploring the real implementation principle and advanced usage inside. This summary just gives me a good learning opportunity, and I will summarize KVC and KVO in depth here.
KVC, which stands for NSKeyValueCoding, is an informal Protocol that provides a mechanism for indirectly accessing properties of objects. KVO is one of the key technologies for kVC-based implementations, along with Cocoa binding, Core Data, and AppleScript.
Api sample
The definition of KVC in Objective-C is an extension of NSObject. So for any type that inherits NSObject, you can use KVC, and here are the four most important methods of KVC
- (nullable id)valueForKey:(NSString *)key; // Use Key directly
- (void)setValue:(nullable id)value forKey:(NSString *)key; // Set the value by Key
- (nullable id)valueForKeyPath:(NSString *)keyPath; // The value can be KeyPath
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // set the value to KeyPath
Copy the code
In general, an obj-c object has some properties. This is shown in the code
#import <Foundation/Foundation.h>
@interface Person : NSObject
/** name */
@property ( nonatomic.copy ) NSString *name;
/** Address */
@property ( nonatomic.copy ) NSString *address;
/** Friends */
@property ( nonatomic.copy ) NSArray<Person *> *address;
/** Spouse */
@property ( nonatomic.copy ) Person *Spouse;
@end
Copy the code
The above Person object has multiple attributes. From the perspective of KVC, that is, the name, address and other attributes of the Person object have a Value corresponding to their Key Value.
- Key is a string type.
- Value can be of any type.
KVC provides two basic methods for storing values.
Person *man = [Person new];
/ / value
[man setValue:@"LiMing" forKey:@"name"];
/ / value
NSString *name = [man valueForKey:@"name"];
Copy the code
KVC provides two other methods for ease of use.
Let’s say the object we created earlier has a spouse, and the spouse is also a Person object, and we want to read the woman’s name property in man
You can do this
Person *woman = [Person new];
man.spouse = woman;
[man setValue:@"Lily" forKeyPath:@"spouse.name"];
NSLog(@ "% @",[man valueForKeyPath:@"spouse.name"]);
// Key is distinguished from KeyPath
// Key lets you get a value from an object
// KeyPath allows you to retrieve values from consecutive keys, using dots for keys. Cut and connect
Copy the code
Just a quick comparison
// Same result, but easier to use KeyPath
[man valueForKeyPath:@"spouse.name"]
[[man valueForKey:@"spouse"] valueForKey:@"name"];
// Point syntax is perfectly possible (why use it this way?)
NSLog(@ "% @",man.spouse.name);
Copy the code
KVC search for Key values
KVC provides an alternative to accessors to some extent, but whenever possible, KVC also works with the help of accessor methods. KVC looks for Key values in the following order.
1. The assignment
When the program calls
- (void)setValue:(nullable id)value forKey:(NSString *)key;
Copy the code
1. Find accessor methods first
The program calls the setKey property value method first, and the code sets it directly through the Setter method. The key value is the name of the member variable. The uppercase key must comply with the naming rules for Setter and Getter methods.
2. Look for _key
If no accessor method for setKey is found, the KVC mechanism checks
+ (BOOL)accessInstanceVariablesDirectly
Copy the code
Is the return value of “NO”, which returns YES by default. If the developer overrides the method to return NO, KVC will then call it directly
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
Copy the code
If you don’t do anything else, you’re going to raise an exception, so people don’t do that.
The KVC mechanism then searches the class for any member variable with _key in it. Regardless of whether you define it in a declaration file or an implementation file, and regardless of the attribute modifier used, KVC can assign a value to any member variable named _key.
3. Look for _isKey
If the class has neither a setKey: accessor method nor a _key member variable, the KVC mechanism searches for the _isKey member variable.
4. Search for keys and iskeys
As above, if the class has neither accessor methods for setKey: nor member variables _key and _isKey, the KVC mechanism will continue to search for key and isKey member variables and assign them values.
If none of the methods or member variables listed above exist, the setValue: forUNdefinedKey: method of the object is executed, and the default is to throw an exception.
If developers want to make this class disable KVC, then rewrite the + (BOOL) accessInstanceVariablesDirectly method to return NO, so if the KVC had not found the set < Key > : For attribute names, the setValue: forUNdefinedKey: method is used directly.
Value of 2.
When the program calls
- (nullable id)valueForKey:(NSString *)key;
Copy the code
1. Preferentially find accessor methods
First, search the getter methods in the order getKey, key, and isKey to find the direct call. If the value is bool, int, etc., then NSNumber is converted.
2. Search in ordered collections
The getter above was not found, look for countOfKey, objectInKeyAtIndex:, KeyAtIndexes methods. If countOfKey and one of the other two methods are found, a collection of proxies that can respond to all of NSArray’s methods is returned. The NSArray message method sent to the proxy collection is called as a combination of countOfKey, objectInKeyAtIndex:, and KeyAtIndexes. There is also an optional getKey:range: method.
3. Search in unordered collection
Find countOfKey, enumeratorOfKey, memberOfKey:. If all three methods are found, a collection of proxies that can respond to all methods of the NSSet is returned. The NSSet message methods sent to the proxy collection are called as countOfKey, enumeratorOfKey, memberOfKey: combination.
4. Search for member variable names
Still didn’t get, so if the class methods accessInstanceVariablesDirectly returns YES, then press _key, _isKey, key, direct search member name iskey order.
5. Report an exception
If no more is found, call ValueForUndefinedKey: and an exception is reported by default
KVC for collection types
The KVC we talked about above is one-to-one, such as the Name attribute in the Person class. But there are also one-to-many relationships. For example, there is a friend attribute in Person, which stores all of a Person’s friends. In this case, collections are needed to deal with it.
We have two options for handling collection classes
1. Take out the set class through KVC, and then process the set
2. Use the template method provided by KVC
An ordered set
The Key is the name of the property being listened on
-countOfKey
// Must be implemented, corresponding to NSArray's basic method count:
- objectInKeyAtIndex:
- keyAtIndexes:
NSArray objectAtIndex: objectsAtIndexes:
- getKey:range:
// It is not required, but can be implemented to improve performance, corresponding to the NSArray method
- getObjects:range:
- insertObject:inKeyAtIndex:
- insertKey:atIndexes:
NSMutableArray insertObject:atIndex: and insertObjects:atIndexes:
- removeObjectFromKeyAtIndex:
- removeKeyAtIndexes:
NSMutableArray removeObjectAtIndex: and removeObjectsAtIndexes:
- replaceObjectInKeyAtIndex:withObject:
- replaceKeyAtIndexes:withKey:
// Optionally, if there are performance issues with such operations, you need to consider implementing them
Copy the code
Unordered collection
- countOfKey
// Must be implemented, corresponding to NSArray's basic method count:
- objectInKeyAtIndex:
- keyAtIndexes:
NSArray objectAtIndex: objectsAtIndexes:
- getKey:range:
// It is not required, but can be implemented to improve performance, corresponding to the NSArray method
- getObjects:range:
- insertObject:inKeyAtIndex:
- insertKey:atIndexes:
NSMutableArray insertObject:atIndex: and insertObjects:atIndexes:
- removeObjectFromKeyAtIndex:
- removeKeyAtIndexes:
NSMutableArray removeObjectAtIndex: and removeObjectsAtIndexes:
- replaceObjectInKeyAtIndex:withObject:
- replaceKeyAtIndexes:withKey:
// Both of these are optional and should be considered if there are performance issues with such operations
Copy the code
KVC support for basic data types and structures
1. Basic data types are wrapped in NSNumber
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSNumber *)numberWithLongLong:(long long)value;
+ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithInteger:(NSInteger)value NS_AVAILABLE(10_5.2_0);
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value NS_AVAILABLE(10_5.2_0);
Copy the code
2. The structure is packaged with NSValue
+ (NSValue *)valueWithCGPoint:(CGPoint)point;
+ (NSValue *)valueWithCGSize:(CGSize)size;
+ (NSValue *)valueWithCGRect:(CGRect)rect;
+ (NSValue *)valueWithCGAffineTransform:(CGAffineTransform)transform;
+ (NSValue *)valueWithUIEdgeInsets:(UIEdgeInsets)insets;
+ (NSValue *)valueWithUIOffset:(UIOffset)insets NS_AVAILABLE_IOS(5_0);
Copy the code
All structures support encapsulation with NSValue
The set operator in KVC
The set operator is a special KeyPath that can be passed as an argument to the valueForKeyPath: method
1. Simple set operators
Simple set operators are @avg, @count, @max, @min, and @sum5
2. Object operators
The object operators are @distinctUnionofObjects and @unionofObjects, both of which return NSArray objects.
1.@distinctUnionOfObjects returns the collection after removing duplicate objects
2.@unionOfObjects returns all objects directly
NSKeyValueCoding other methods
+ (BOOL)accessInstanceVariablesDirectly;
// Return YES by default, if SetKey is not found, members will be searched in the order _key, _iskey, key, iskey. If SetKey is set to NO, exceptions will be thrown.
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC provides a property value validation API, which can be used to check if the value of a set is correct, make a replacement value for an incorrect value, or reject a new value and return the cause of the error.
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
// This is the set operation API, there are a series of such apis, if the property is an NSMutableArray, then you can use this method to return
- (nullable id)valueForUndefinedKey:(NSString *)key;
// If the Key does not exist and no KVC can find any fields or attributes related to the Key, this method is called. By default, an exception is thrown
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// Same as the previous method, but set the value.
- (void)setNilValueForKey:(NSString *)key;
// If you pass nil to Value during the SetValue method, this method is called
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
// Input a set of keys, return the values corresponding to the keys, and then return the dictionary, which is used to transfer the Model to the dictionary.
Copy the code
2.KVO
1. Know the KVO
KVO is similar to the Observer pattern, and we use simple code to understand what KVO is.
// Register a Person class
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic.copy) NSString *name;
@end
// Register a Dog class
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic.copy ) NSString *name;
@end
Copy the code
We introduce a header file in the ViewController and create two global properties. We want Person to be the observer of Dog, so that when the name property of Dog changes, Person can be the first to know. This is where we can apply KVO technology.
Person *p = [Person new];
self.p = p;
Dog *dog = [Dog new];
self.dog = dog;
// To become an observer of another object, register
// KeyPath represents the specific properties of the listener
// Observe
// Options can specify the old and new values to be observed
// Context can be any object that communicates information to observers, or can be used to distinguish observers with specified identifiers
[dog addObserver:p
forKeyPath:@"name"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:nil];
dog.name = @ "prosperous wealth";
Copy the code
Monitor option Options are defined by the enumeration NSKeyValueObservingOptions, he determines which values can be passed to the internal implementation of the observer method.
The definition is as follows:
enum {
// Provide a new value
NSKeyValueObservingOptionNew = 0x01.// Provide the old value
NSKeyValueObservingOptionOld = 0x02.// Immediately send a notification to the observer when adding an observer,
// Before the registered observer method returns
NSKeyValueObservingOptionInitial = 0x04.// If specified, a notification is sent to the observer in advance each time a property is modified before the notification is sent.
// This corresponds to the time when -willChangevalueForkey is triggered.
// In this case, two notifications are actually sent each time a property is modified.
NSKeyValueObservingOptionPrior = 0x08
};
typedef NSUInteger NSKeyValueObservingOptions;
// Option values can support multiple options
Copy the code
After registration, we implement the following methods within the observer
// When the observed property changes, the observer automatically calls the following method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context {
// keyPath Specifies the value of the property to be observed
NSLog(@"keyPath = %@",keyPath);
// object Indicates the object to be observed
NSLog(@"object = %@",object);
// The observed attribute is worth changing, more on that later
NSLog(@"change = %@",change);
// Context can also be arbitrary additional data
// The function of this Context is very important, which I will emphasize later
NSLog(@"context = %@",context);
}
// We can get some key information through this method
Copy the code
The Change option, which records changes to the monitored property. The value can be obtained by key:
// The type of the property change, which is an NSNumber object, contains the values associated with the NSKeyValueChange enumeration
NSString *const NSKeyValueChangeKindKey;
// The new value of the property. So when NSKeyValueChangeKindKey is NSKeyValueChangeSetting,
/ / and add the observing method to set the NSKeyValueObservingOptionNew, we can get the new value to the property.
/ / if NSKeyValueChangeKindKey NSKeyValueChangeInsertion or NSKeyValueChangeReplacement,
/ / and specifies the NSKeyValueObservingOptionNew, then we can get to an NSArray object contains objects or being inserted
// The object used to replace other objects.
NSString *const NSKeyValueChangeNewKey;
// The old value of the property. So when NSKeyValueChangeKindKey is NSKeyValueChangeSetting,
/ / and add the observing method to set the NSKeyValueObservingOptionOld, we can get to the attributes of the old value.
/ / if NSKeyValueChangeKindKey NSKeyValueChangeRemoval or NSKeyValueChangeReplacement,
/ / and specifies the NSKeyValueObservingOptionOld, then we can get to an NSArray object contains objects or has been removed
// The object being replaced.
NSString *const NSKeyValueChangeOldKey;
/ / if the value is NSKeyValueChangeInsertion, NSKeyValueChangeRemoval NSKeyValueChangeKindKey
/ / or NSKeyValueChangeReplacement, the corresponding value is a key NSIndexSet object,
// Contains the index of the object being inserted, removed, or replaced
NSString *const NSKeyValueChangeIndexesKey;
/ / when specified NSKeyValueObservingOptionPrior options, in front of the attribute is modified notification sent,
// A notification is sent to the observer first. We can use NSKeyValueChangeNotificationIsPriorKey
// to find out if the notification was sent in advance, if so, the value is always @(YES).
NSString *const NSKeyValueChangeNotificationIsPriorKey;
Copy the code
The value of NSKeyValueChangeKindKey is taken from NSKeyValueChange, which is an enumerated value and is defined as follows
enum {
// Set a new value. The monitored property can be an object, a one-to-one or one-to-many relationship.
NSKeyValueChangeSetting = 1.// A property that represents an object being inserted into a one-to-many relationship.
NSKeyValueChangeInsertion = 2.// Indicates that an object is removed from the property of a one-to-many relationship.
NSKeyValueChangeRemoval = 3.// Indicates that an object is replaced in a property of a one-to-many relationship
NSKeyValueChangeReplacement = 4
};
typedef NSUInteger NSKeyValueChange;
Copy the code
Note that the observer must be removed when no longer in use, otherwise it will crash
- (void)dealloc {
[self.dog removeObserver:self.p forKeyPath:@"name"];
}
Copy the code
From the brief code example above, we can see that fortune-watchers only need to implement a few simple steps.
- Registered observer
- The observer implements the corresponding method
- Remove observer
2. Implementation principle of KVC and KVO
KVC and KVO are implemented based on the powerful Runtime. And the technology that’s used is ISA-Swilling, and isa-Swilling is also a key technology that we’ll talk about in the Runtime. Please be patient if you don’t understand this.
There is a good article on the web about the implementation principle, linked here.
When an object of a class is observed for the first time, the system dynamically creates a derived class for that class during runtime. If the listening class is ClassA, the derived class name is NSKVONotifying_ClassA.
1. The isa pointer of the original object will point to the new derived Class. The derived Class overwrites the Class method in order to confuse and avoid others knowing that it is not the original Class.
2. At the same time, rewrite the Dealloc method for resource destruction processing.
3. Also overwrite _isKVOA, which is a flag to indicate that the class complies with the KVO mechanism.
4. The most important thing is to overwrite the Setter method of the listened property, which is the key to implementing KVO. And we’ll talk about why later.
Let me just draw a picture, maybe it’ll help.
The fact that we’ve overridden the Setter methods for the properties of the object being observed is critical, and that brings us to two other very important methods
// This method is called when the property value is about to be modified
- (void)willChangeValueForKey:(NSString *)key;
// This method is called when the property value has been modified
- (void)didChangeValueForKey:(NSString *)key;
// didChangeValueForKey: the method is explicitly called
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey.id> *)change
context:(void *)context {
}
Copy the code
Actually, my personal guess is, rewriting Setter methods inside should be like this
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
Copy the code
At this point, I believe you should fully understand the implementation mechanism of KVO.
// This is the key to KVO triggering
- (void)didChangeValueForKey:(NSString *)key;
Copy the code
3. Call the three methods of KVO
Based on the above implementation principle of KVO, we can draw the following conclusions:
1. KVC was used
Use the KVC, if you have accessor methods, the runtime calls in the accessor methods will/didChangeValueForKey: method; Use accessor methods, runtime in setValue: forKey method call will/didChangeValueForKey: method.
2. Accessor methods
Runtime rewrite accessor method call will/didChangeValueForKey: method. Therefore, KVO can also listen when the accessor method is called directly to change the value of the property.
3. Direct call
Explicitly call will/didChangeValueForKey: method.
4.KVO automatic notification and manual notification
General we use are automatically notified, after registered observers, when the trigger will/didChangeValueForKey: method, – (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object Change :(NSDictionary
*)change context:(void *)context {} will be called.
If we want to implement manual notification, we need to resort to an additional method
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
Copy the code
This method returns YES by default, indicating whether the property specified by Key supports KVO or not. If it returns NO, we need to manually update it.
Let’s go back to our top example and listen for the Person name property, but this time we’ll do it manually.
#import "Person.h"
@implementation Person
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
BOOL automaic = NO;
if ([key isEqualToString:@"name"])
{
automaic = NO;
}
else
{
// It is important to note here that other attributes that are not being processed call the parent class's old methods
automaic = [super automaticallyNotifiesObserversForKey:key];
}
return automaic;
}
@end
Copy the code
So we’ve marked for manual notification when the Person name property changes as follows:
@implementation Person
- (void)setName:(NSString *)name {
if(name ! = _name)// Add a judgment, if the values are the same, no need to send notification
{
// We need to call 'will... ` method
[self willChangeValueForKey:@"name"];
_name = name;
// We also need to call 'did... 'method, explicitly calling the observer's method
[self didChangeValueForKey:@"name"]; }}@end
Copy the code
You can manually send notifications one-to-one. In one-to-many cases, you can use the following method
- (void)willChange:(NSKeyValueChange)change valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key
- (void)didChange:(NSKeyValueChange)change valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key
- (void)willChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects
- (void)didChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects
Copy the code
5. Register dependent keys (similar to calculated properties in Vue)
In real development, you might encounter situations where the value of one variable depends on the value of another.
Let’s look at an example:
// Declare a Person class with three attributes
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic.copy) NSString *fullName;
@property (nonatomic.copy) NSString *firstName;
@property (nonatomic.copy) NSString *lastName;
@end
// fullName depends on firstName and lastName.
// If firstName and lastName change,fullName will also be affected.
#import "Person.h"
@implementation Person
// Registration of fullName depends on firstName and lastName
+ (NSSet<NSString *> *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@"firstName".@"lastName".nil];
}
- (NSString *)fullName {
NSString *tempName = _fullName;
if (_firstName || _lastName)
{
tempName = [NSString stringWithFormat:@ % @ - % @ "",_firstName,_lastName];
}
return tempName;
}
Copy the code
Back to the Controller:
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [Person new];
self.p = p;
[self.p addObserver:self
forKeyPath:NSStringFromSelector(@selector(name))
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:ContextMark];
self.p.fullName = @"lilei";
NSLog(@"fullName = %@".self.p.fullName);
self.p.firstName = @"lala";
NSLog(@"fullName = %@".self.p.fullName);
self.p.lastName = @"papa";
NSLog(@"fullName = %@".self.p.fullName);
}
// Print the following result
fullName = lilei
fullName = lala-(null)
fullName = lala-papa
Copy the code
6. “pits” in KVO use
When I was looking at this recently, I noticed that everyone was using tableView and ContentOffset as examples. Let’s use this most common control to illustrate.
1. Set keyPath to a string
As is known to all, the KeyPath in KVO is of NSString type. Combined with the characteristics of OBJ-C dynamic language, it is not checked at compile time. Only when it is executed, it will dynamically search the method list and instance variable list, so once we write the wrong KeyPath, it is hard to find when it is not run.
Based on this problem, we use the following methods to avoid
// So I can't write it wrong
NSStringFromSelector(@selector(contentSize))
Copy the code
2. Multiple layers of inheritance share the same callback method
If the parent class’s controller listens for the ContentOffset property of TableViews, it also listens for properties of other controls, but the same object or controller acts as an observer of multiple object properties, In fact the same callback method ends up being called – (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object Change :(NSDictionary
*)change context:(void *)context {}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"{[])selfDoSomethingWhenContentOffsetChanges]; }}Copy the code
However, this is not complete, because the current class is likely to have a parent class, and its parent class may be bound to some other KVO, the above code only has one condition, if not true, the KVO trigger operation will be broken. The KVO event that the current class cannot catch is probably in its parent class, or a parent of its parent class. The above action breaks the chain, so the correct method should be as follows:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"{[])selfDoSomethingWhenContentOffsetChanges]; }else{[superObserveValueForKeyPath: keyPath ofObject: object change: change the context: context]; }}Copy the code
In doing so, the chain remains intact.
3. Observer logout
After the above method is done, there are pitfalls. We know that KVO needs to be logged out when it is not in use. We know that when you log out of the same KVO twice, the system throws an exception by default.
When, you may wonder, do I log out of the same Observer more than once?
At this point we can consider when we unregister the Observer, is it mostly in the Dealloc method?
In OBJ-C, there are many system methods that need to be overridden to call methods such as super XXXXXXX, which is determined by the obJ-C inheritance.
Such as:
// To override the init method, we need to call the superclass init method
- (instancetype)init {
[super init];
}
// To layout a child control, call the parent's layoutSubviews method
- (void)layoutSubviews {
[super layoutSubviews];
}
Copy the code
There are also methods that don’t need to call the parent class’s methods, but will automatically call them for you, as we call Dealloc. In fact, only in ARC mode does not need to call the parent class, MRC Dealloc still has to manually call the super Dealloc.
So that’s what we do when we unregister the observer
- (void)dealloc {
[_tableView removeObserver:self forKeyPath:@"contentOffset"];
}
Copy the code
Suppose we have three classes ClassA (parent), ClassB (child), and ClassC (grandson). All three classes act as observers, observing the contentOffset property of tableViews.
If we release the observer in the Dealloc method of ClassC
- (void)dealloc {
[_tableView removeObserver:self forKeyPath:@"contentOffset"];
}
Copy the code
When the Dealloc of ClassC finishes executing, it automatically goes to the Dealloc method of ClassB, releasing the observer
- (void)dealloc {
[_tableView removeObserver:self forKeyPath:@"contentOffset"];
}
Copy the code
This is where the crash occurs, because as we mentioned earlier this causes the same removeObserver to be executed twice, causing a crash.
4. Write it correctly
For this type of Crash, we are going to talk about a key parameter Context, like registering an Observer. Before, I did not know what this Context is used for, and the use of KVO is only superficial, so I did not delve into the function of this mysterious Context. We will now use the Context to differentiate each Observer and avoid calling the same removeObserver multiple times.
Three key approaches to KVO
// Register the observer
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
// Observer response method
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey.id> *)change context:(nullable void *)context;
// Remove observer (there are two methods)
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
Copy the code
As those of you who are careful can already see, we can find the Context keyword in the three steps of registration, response, and removal. To maintain consistency between registration, response, and removal, the correct way to write it is as follows:
// First we should create a unique Context in the class that uses KVO to distinguish it from other classes
static Void *ContextMark = &ContextMark;
// use it when registering
[_tableView addObserver:self
forKeyPath:NSStringFromSelector(@selector(contentSize))
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:ContextMark];
// it is used when responding
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context {
if (context == ContextMark)
{
// do someThing
}
else{[superobserveValueForKeyPath:keyPath ofObject:object change:change context:context]; }}// Use it to log out
- (void)dealloc {
[_tableView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(contentSize))
context:ContextMark];
}
Copy the code
If you’re still worried, you can also use @try @catch to catch an exception
7. To summarize,
KVO this API really trouble ~~~~~