KVO is used very frequently in iOS development, either indirectly or directly. Today we will explore the following points.
KVO que
1. Firstly, by simply using KVO, analyze the meaning of each parameter of API in KVO provided by Apple and how to use it to achieve optimal use of API. Here is the first code of the day: kgPerson.h:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface KGPerson : NSObject @property(nonatomic,copy) NSString *name; @end NS_ASSUME_NONNULL_ENDCopy the code
The kgPerson.m code looks like this:
#import "KGPerson.h"
@implementation KGPerson
@end
Copy the code
Viewcontroller.m:
#import "ViewController.h"
#import "KGPerson.h"
@interface ViewController ()
@property (nonatomic,strong) KGPerson *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_person = [[KGPerson alloc] init];
[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionPrior context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@==%@==%@",keyPath,object,change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
_person.name = @"KG";
}
@end
Copy the code
So that’s the usual way we write KVO, (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath Options: (NSKeyValueObservingOptions) options context: (nullable void *) the context the meaning of parameters in this method, the observer is the observer object, namely the message recipient; KeyPath is the path, which is the property or member variable that we need to look at; So let’s look at these properties in code. First look at options, which is an enumeration, as follows:
Typedef NS_OPTIONS (NSUInteger NSKeyValueObservingOptions) {NSKeyValueObservingOptionNew = 0 x01, / / value is 1, Indicates that the change dictionary should provide new property values, if applicable. NSKeyValueObservingOptionOld = 0 x02, / / value of 2, indicating change dictionary should contain the old attribute value (if applicable). NSKeyValueObservingOptionInitial API_AVAILABLE (macos (10.5), the ios (2.0), watchos (2.0), tvos (9.0)) = 0 x04, / / value of 4, if specified, A notification should be sent to the observer immediately before the observer registration method returns. So the user basically signs up for listening, Not change in the value before they can send a message NSKeyValueObservingOptionPrior API_AVAILABLE (macos (10.5), the ios (2.0), watchos (2.0), tvos (9.0)) = 0 x08, a value of 8, Should observers be sent separate notifications before and after each change, rather than a single notification after the change? };Copy the code
Add: 1, enumeration value if such as 1<
2, the above enumerated value options has the following situations:
(1), the options = 1: NSKeyValueObservingOptionNew selected by the user
(2), the options = 2: NSKeyValueObservingOptionOld selected by the user
(3), the options = 3: the user selected NSKeyValueObservingOptionNew and NSKeyValueObservingOptionOld
(4), the options = 4: NSKeyValueObservingOptionInitial selected by the user
(5), the options = 5: the user selected NSKeyValueObservingOptionInitial and NSKeyValueObservingOptionNew
(6), the options = 6: the user selected NSKeyValueObservingOptionInitial and NSKeyValueObservingOptionOld
(7), the options = 7: the user selected NSKeyValueObservingOptionInitial, NSKeyValueObservingOptionOld, NSKeyValueObservingOptionNew
(8), the options = 8: NSKeyValueObservingOptionPrior selected by the user
(9), the options = 9: the user selected NSKeyValueObservingOptionPrior and NSKeyValueObservingOptionNew
(10), the options = 10: the user selected NSKeyValueObservingOptionPrior and NSKeyValueObservingOptionOld
(11), the options = 11: the user selected NSKeyValueObservingOptionPrior, NSKeyValueObservingOptionOld, NSKeyValueObservingOptionNew
(12), the options = 12: the user selected NSKeyValueObservingOptionInitial and NSKeyValueObservingOptionPrior
(13), the options = 13: the user selected NSKeyValueObservingOptionInitial, NSKeyValueObservingOptionPrior, NSKeyValueObservingOptionNew
(14), the options = 14: The user selected NSKeyValueObservingOptionInitial, NSKeyValueObservingOptionPrior, NSKeyValueObservingOptionNew, NSKeyValueObservingOpti onOld
Then we write code to see what happens with the scheme in the supplement above:
Option 1 :(click on the screen to trigger a ‘touchesBegan’)
Option 2:(click on the screen to trigger a ‘touchesBegan’)
Option 3:(need to touch the screen to trigger a ‘touchesBegan’)
Option 4:(no need to click on the screen)
Option 5:(no need to tap the screen)
Option 6:(no need to tap the screen)
Option 7:(no need to tap the screen)
Option 8:(need to touch the screen to trigger a ‘touchesBegan’)
Option 9:(touching the screen triggers a ‘touchesBegan’)
Option 10:(touching the screen triggers a ‘touchesBegan’)
Option 11:(need to touch the screen to trigger a ‘touchesBegan’)
Option 12:(without clicking on the screen, print the first line, and click on the screen to trigger a ‘touchesBegan’ to print the last two lines)
Option 13:(without clicking the screen, print the first line, and click the screen to trigger a ‘touchesBegan’ to print the last two lines.)
Option 14:(without clicking on the screen, print the first line, and click on the screen to trigger a ‘touchesBegan’ to print the last two lines)
The above are the scheme results of all options. You can choose the corresponding scheme according to the scenario of your own project.
The context parameter is explained very thoroughly in KVO. It is mainly used to distinguish between different classes that have the same property listener, i.e. the same keyPath, to simplify the judgment condition, and to make the judgment safer. Here’s an example of our general judgment and using context judgment:
Common observation of the same property of different objects code writing :(we often write context pass nil, which works fine, but is wrong from the point of view of code rigor, because apple explicitly states in the documentation that NULL is passed if this object is not used)
Use context to observe the same properties of different objects:
Supplement:
Nil: In OC to indicate that the value of a pointer is null, often used to create an empty object to indicate that the pointer does not point to any memory space.
NULL: in C, the value of a pointer is NULL, indicating that the pointer does not point to any memory space.
Here is basically for the use of KVO, I think it is already familiar, so let’s further understand KVO active call and passive call. Dealloc = dealloc; dealloc = dealloc
2. In some cases during development, such as KVO listener of member variable, we can’t listen by modifying the value directly, then we usually trigger KVO listener by manually calling willChangeValueForKey and didChangeValueForKey. The code is as follows:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[_person willChangeValueForKey:@"_nikeName"];
[_person setValue:@"KG" forKey:@"_nikeName"];
[_person didChangeValueForKey:@"_nikeName"];
}
Copy the code
In addition to the attributes of listening, if we need to manually to trigger the KVO callback, then you should rewrite the first + (BOOL) automaticallyNotifiesObserversForKey (nsstrings *) key method. Details are as follows:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
if (key isEqualToString:@"name") {
return NO;
}
return YES;
}
Copy the code
In addition to overwriting the above method, we also need to actively call willChangeValueForKey and didChangeValueForKey as follows:
- (void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
Copy the code
Now that we are done with the basic use of KVO, let’s take a look at how Apple implements KVO. What is the principle? Listen to our next analysis.
KVO principle exploration
1, no more words, first look at the following GIF, we see the picture to speak.
As we can see from the above animation, when we add the KVO property to an object, the system will change the isa pointer of our object to a dynamically created class NSKVONotifying_KGPerson at runtime. I’m not telling you apple told me, apple said,
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.Copy the code
This is what my translator said:
Automatic key observation is implemented using a technique called ISA-Swizzling. The ISA pointer, as the name implies, points to the class of objects that hold a schedule. The schedule table mainly contains Pointers to 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 an intermediate class instead of the real class. Therefore, the value of the ISA pointer does not necessarily reflect the actual class of the instance. You should never rely on isa Pointers to determine class membership. Instead, you should use the class method to determine the class of an object instance.Copy the code
2. In fact, this is analyzed in the official Apple document KVO. Isa-swizzling is used to create a class dynamically at runtime, and then change the isa pointer to the object to point to the dynamically created class. Which class does this dynamically created class inherit from? Let’s modify the code together, and then run the print. The specific code and the result are shown in the figure below:
We print the KGPerson class and all its subclasses before and after adding the property listener. We can see from the printout that when adding the listener, the system will dynamically create a subclass NSKVONotifying_KGPerson that inherits from the KGPerson class. Then we modify the KGPerson class code as follows:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface KGPerson : NSObject{
@public
NSString *_nikeName;
}
@property (nonatomic,copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
Copy the code
After making the changes, we make the changes as shown in the figure and the property listener where we use the LGPerson class, and look at the results:
As can be seen from the printed results, the monitoring of member variables has no effect, and the monitoring of attributes can be monitored. Then I modify the above code and run it again to see what it looks like:
The listener for the attributes of the member variable is gone, so we can draw the following conclusions:
The principles of KVO include the following two points:
Dynamically generate subclass :NSKVONotifying_XXX
You’re looking at setter methods
NSKVONotifying_KGPerson = NSKVONotifying_KGPerson Let’s take a look at the following code and then analyze it:
Based on the above code results, we can analyze that the dynamically created subclass does the following operations:
Override setter methods for observed properties
Override the class method, which returns the KGPerson class again.
Rewrite the Dealloc method to point isa to KGPerson after the destruction method is executed, and dynamically created classes are cached.
The _isKVOA method is implemented
4. For the third conclusion above, we verify it, modify the code and run it, and the result is as follows:
The isa of the Po object_getClassName(self.person) object will go to KGPerson. The isa of the Po object_getClassName(self.person) object will go to KGPerson. Then we print all subclasses of KGPerson on the previous screen, with the code and result as follows:
5. As shown in the figure above, subclasses are cached by default when created dynamically. So far, we have learned a basic principle of system KVO operation, and the process is as follows:
Now that we know how KVO works, can we customize our implementation of KVO? Let’s look at custom KVO.
Custom simulation KVO
1. First of all, we analyze the system’s KVO method. By looking at the API of KVO, we can see that the system extends the NSObject class, that is, through classification. So we’re also going to do it by classification, by comparison, if you want to be able to listen for properties of all classes globally, it’s best to extend the class of NSObject, because all classes inherit from NSObject, so let’s first create a class of NSObject, and we’ll call it KGKVO. The specific code is as follows, with detailed comments in the code:
NSObject+KGKVO.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (KGKVO)
- (void)kg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)kg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
NS_ASSUME_NONNULL_END
Copy the code
NSObject+KGKVO.m
#import "NSObject+ kgkvo. h" #import <objc/message.h> // static NSString *const kKGKVOPrefix = @"KGKVONotifying_"; Static NSString *const kKGKVOAssiociateKey = @"kKGKVO_AssiociateKey"; @implementation NSObject (KGKVO) - (void)kg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath Options: (NSKeyValueObservingOptions) options context: (nullable void *) context {/ / 1, the setter method exists [the self judgeSetterMethodFormKeyPath:keyPath]; / / 2, dynamically generated subclasses Class newClass = [self createChildClassWithKeyPath: keyPath]; Isa points to a newly created subclass, isa_swizzling object_setClass(self, newClass); Objc_setAssociatedObject (self, (__bridge const void *_Nonnull)(kKGKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)kg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{// obtain the parent Class of the current subclass, Class superClass = [self class]; object_setClass(self, superClass); } # pragma mark, verify whether there is a setter method - (void) judgeSetterMethodFormKeyPath keyPath: (nsstrings *) {/ / for parent Class supperClass = object_getClass(self); // generate setsel setterSEL = NSSelectorFromString(setterSelector(keyPath)); SetterMethod = class_getInstanceMethod(supperClass, setterSEL); // Check if there is a method if (! SetterMethod) {// If the method does not exist, An exception is thrown @ throw [NSException exceptionWithName: NSInvalidArgumentException reason: [nsstrings stringWithFormat: @ "sorry, brother, Currently %@ has no setter method ",keyPath] userInfo:nil]; }} # pragma mark - dynamic subclassing - (Class) createChildClassWithKeyPath keyPath: (nsstrings *) {/ / get the Class name of the current Class nsstrings * oldClassName = NSStringFromClass([self class]); / / generated current class a subclass of the class name nsstrings * newClassName = [nsstrings stringWithFormat: @ "% @ % @", kKGKVOPrefix, oldClassName]; Class newClass = NSClassFromString(newClassName); // Determine if subclasses if (! NewClass) {// If the class does not exist, the first argument is to pass in the parent class, the second argument is to pass in the class name, NewClass = objc_allocateClassPair([self class], newClassName.utf8String, 0); // Register class objc_registerClassPair(newClass); // add class method SEL classSEL = NSSelectorFromString(@"class"); ClassMethod = class_getInstanceMethod([self class], @selector(class)); Const char *classType = method_getTypeEncoding(classMethod); // Add class method class_addMethod(newClass, classSEL, (IMP)kg_class, classType); SetterSEL = NSSelectorFromString(setterSelector(keyPath)); SetterMethod = class_getInstanceMethod([self class], setterSEL); Const char *type = method_getTypeEncoding(setterMethod); // Get method parameters and return type const char *type = method_getTypeEncoding(setterMethod); // add method class_addMethod(newClass, setterSEL, (IMP)kg_setter, type); // return subclass newClass; } #pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark -- pragma mark To forward the message, call msgSendSuper(), Struct objc_super superStruct = {.receiver = self,.super_class = class_getSuperclass([self class])}; Void (*kg_objc_msgSendSuper)(void *,SEL,id) = (void *)objc_msgSendSuper; Kg_objc_msgSendSuper (&superStruct,_cmd,newValue); Observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKGKVOAssiociateKey)); / / and then sends a message to observer SEL observerSEL = @ the selector (observeValueForKeyPath: ofObject: change: context:); // Get the getter method NSString *keyPath = getterFormSetter(NSStringFromSelector(_cmd)); // message forwarding ((void (*)(id, SEL, NSString, id, NSDictionary, void *))(void *)objc_msgSend)(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL); } #pragma mark -- get the superclass kg_class(id self,SEL _cmd){return class_getSuperclass(object_getClass(self)); } #pragma mark -- get setter method keyPath->setKeyPath static NSString *setterSelector(NSString *getter){// If (getter.length <= 0) {return nil; } // take the first character and convert to uppercase NSString *firstString = [getter substringToIndex:1] uppercaseString]; NSString *leaveString = [getter substringFromIndex:1]; Return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString]; } #pragma mark -- get getter from setter method static NSString *getterFormSetter(NSString *setter) (setter.length <= 0 || ! [setter hasPrefix:@"set"] || ! [setter hasSuffix:@":"]) { return nil; } // obtain keyPath NSRange range = NSMakeRange(3, setter.length-4); NSString *getter = [setter substringWithRange:range]; NSString *getter = [setter substringWithRange:range]; NSString *firstString = [[getter substringToIndex:1] lowercaseString]; NSString *firstString = [getter substringToIndex:1] lowercaseString]; / / return getter methods return [setter stringByReplacingCharactersInRange: NSMakeRange (0, 1) withString: firstString]; } @endCopy the code
2. Then use it at the place where it is called as shown below:
3. So far, we have customized KVO by imitating the implementation principle of KVO of the system. However, we have only imitated the observation of the properties of the system’s implementation and failed to achieve the same perfection as system KVO, so we continue to improve the customized KVO. Current problems:
If multiple objects are observing property values, then the observer that we hold through objc_setAssociatedObject will be messed up.
If an object needs to observe more than one property at a time, which means calling objc_setAssociatedObject multiple times, it’s easy to repeatedly associate different values with the same key value and association strategy
Memory leaks.
4. Then we will improve the customized KVO for the above problems. Please see the next analysis.
Custom simulation KVO+ perfect
1. First of all, we observe multiple attributes. The first thing we can think of is to save some information through the array, and then evaluate it from the array and decide to do a series of operations. In iOS, categorizing information is nothing more than structs, objects, dictionaries, etc., but here we use objects, defining objects as follows:
@interface KGInfo: NSObject @property (nonatomic,strong) NSObject *observer; /// property @property (nonatomic,copy) NSString *keyPath; / / / observation keys conditions @ property (nonatomic, assign) NSKeyValueObservingOptions options; @property (nonatomic,assign) void * context; /// @param observer Observer object /// @param keyPath property // @param options view key value conditions /// @param context identify identifier - (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; @end@implementation KGInfo /// initializing method // @param observer observer object // @param keyPath attribute // @param options observe key value conditions /// @param context identification identifier - (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{ self = [super init]; if (self) { self.observer = observer; self.keyPath = keyPath; self.options = options; self.context = context; } return self; } @endCopy the code
3. Then make corresponding modifications on the previous basis:
- (void)kg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options Context: (nullable void *) context {/ / 1, the setter method exists [self judgeSetterMethodFormKeyPath: keyPath]; / / 2, dynamically generated subclasses Class newClass = [self createChildClassWithKeyPath: keyPath]; Isa points to a newly created subclass, isa_swizzling object_setClass(self, newClass); NSMutableArray *arr = objc_getAssociatedObject(self, (__bridge const void *_Nonnull)(kKGKVOAssiociateKey)); If (! Arr) {// if (! Arr) { arr) { arr = [NSMutableArray array]; } / / 6, create information object KGInfo * info = [[KGInfo alloc] initWithObserver: the observer forKeyPath: keyPath options: options context:context]; // 7, addObject information to array [arr addObject:info]; Objc_setAssociatedObject (self, (__bridge const void *_Nonnull)(kKGKVOAssiociateKey), arr, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }Copy the code
- (void)kg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ NSMutableArray *arr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKGKVOAssiociateKey)); if (arr.count <= 0) { return; } for (KGInfo *info in arr) { if ([info.keyPath isEqualToString:keyPath]) { [arr removeObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kKGKVOAssiociateKey), arr, OBJC_ASSOCIATION_RETAIN_NONATOMIC); break; }} if (observer.copy <= 0) {superClass = [self Class]; object_setClass(self, superClass); }}Copy the code
- (Class) createChildClassWithKeyPath keyPath: (nsstrings *) {/ / get the Class name of the current Class nsstrings * oldClassName = NSStringFromClass ([the self class]); / / generated current class a subclass of the class name nsstrings * newClassName = [nsstrings stringWithFormat: @ "% @ % @", kKGKVOPrefix, oldClassName]; Class newClass = NSClassFromString(newClassName); if (! NewClass) {// If the class does not exist, the first argument is to pass in the parent class, the second argument is to pass in the class name, NewClass = objc_allocateClassPair([self class], newClassName.utf8String, 0); // Register class objc_registerClassPair(newClass); // add class method SEL classSEL = NSSelectorFromString(@"class"); ClassMethod = class_getInstanceMethod([self class], @selector(class)); Const char *classType = method_getTypeEncoding(classMethod); // Add class method class_addMethod(newClass, classSEL, (IMP)kg_class, classType); SetterSEL = NSSelectorFromString(setterSelector(keyPath)); SetterMethod = class_getInstanceMethod([self class], setterSEL); Const char *setterTypes = method_getTypeEncoding(setterMethod); // add method class_addMethod(newClass, setterSEL, (IMP)kg_setter, setterTypes); // return subclass newClass; }Copy the code
Static void kg_setter(id self,SEL _cmd,id newValue){// Get the getter NSString *keyPath = getterFormSetter(NSStringFromSelector(_cmd)); Id oldValue = [self valueForKey:keyPath]; // To forward the message, call msgSendSuper(), Struct objc_super superStruct = {.receiver = self,.super_class = class_getSuperclass([self class])}; Void (*)(struct objc_super *,SEL,id))(void *)objc_msgSendSuper)(&superStruct,_cmd,newValue); NSMutableArray *arr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKGKVOAssiociateKey)); If (arr.count > 0) {// Loop through the KGInfo object in the array for (KGInfo *info in arr) {// check whether the required info if ([info.keypath isEqualToString:keyPath]) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionary]; / / to deal with the old and new values if (info. The options & NSKeyValueObservingOptionNew) {[change setObject: newValue forKey:NSKeyValueChangeNewKey]; } if (info.options & NSKeyValueObservingOptionOld) { if (oldValue) { [change setObject:oldValue forKey:NSKeyValueChangeOldKey]; }else{ [change setObject:@"" forKey:NSKeyValueChangeOldKey]; }} / / and then sends a message to observer SEL observerSEL = @ the selector (kg_observeValueForKeyPath: ofObject: change: context:); // Message forwarding ((void (*)(id, SEL, NSString *, id, id, void *))(void *)objc_msgSend)(info.observer,observerSEL,keyPath,self,change,NULL); }); }}}}Copy the code
4. At this point, a basic KVO attribute observation is completed, but there are still some defects, we need to add attribute observation. Then run to – (void)kg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change Context :(void *)context to determine which property the listener is returning and so on and so forth, so can we optimize at this point? Of course it can. We naturally thought of blocks, and functional programming makes our code much cleaner, so see next time.
Custom analog KVO+ functional programming ideas
1. In view of the tedious code before, we optimize it again by using the idea of functional programming to break the tedious steps of system KVO. After optimization, the code is as follows:
2, then the use of time is more simple, as follows:
3. In writing, we can see where the observation is added and the callback is implemented there, which is logically clearer, and the tedious observer and keyPath judgments are removed to make the code more concise.
4. Since we have simplified the custom KVO to this extent, can we simplify it further and implement auto-destruct directly? You don’t have to destroy it manually? Because you often forget to manually remove listening, the answer is yes, so look at the next section analysis.
Supplement:
- There’s a little detail here, that’s right
observer
Why notstrong
It USESweak
The reason is to prevent circular references, because if notweak
To break the closed loop, then you are inVC holds ->person ->arr ->info ->VC
In the case.
Custom simulated KVO+ self-destruct
1, we found that every time you need to add attributes to observe all need to manually removed, and a lot of time because of carelessness, it’s easy to forget, then the program runs immediately error collapses, so in order to prevent this, we need to consider whether we can make it automatically destroyed, when the object is released automatically remove attributes to observe? At this point we thought of doing method_swizzled to the system method dealloc, but when should we do it? So far we usually use in the load method, and when we’re currently adding listeners, which one is addObserver, but which one? First of all, if we Hook the method in addObserver, how to judge whether the Hook has been done? So we decided to hook it in the load method, but because NSObject is the base class for all of our classes, it would hook every load of every class, and it would be confusing, and we wouldn’t know if it was successful, so after the whole app started, We Hook once in the load method and never Hook again, so we thought of using simple interest with dispatch_once, but only once.
NSObject+KGKVO. H: NSObject+KGKVO. H: NSObject+KGKVO.
Directly remove a series of listener removal processing, directly in the Hook myDealloc method to remove, simple and rigorous.
3. When we use it, we only need to add attribute listening, no extra processing is needed, and no need to check whether the listening is removed. Only a simple code call is needed to complete the whole complex KVO listening, as follows:
One line of code to listen for changes in property values through KVO, and there are no other configuration options, automatically return old values, new values and so on.
conclusion
So far, the exploration of KVO is basically completed, but there are still many details that need to be optimized, such as:
Listening for different types of properties, current code in which kG_setter methods return values and arguments are inconsistent can cause crashes
Listener implementation of member variables and so on
If you are interested in this, you can discuss it together or analyze FBKVOController