KVO is introduced in front of the system KVO implementation of the general logic, divided into the following steps
1. Create a subclass and point the object’s ISA to the newly created subclass
2. Override class to point to the parent (i.e. the current object’s class)
3. Rewrite the imp method of set specified key value, implement listening callback
4. When releasing, the object points to the current class (the parent of the released class) to avoid unnecessary trouble
The following is a custom KVO that mimics the system for self-destruction
General logic core code
Method of registering subclasses
Class newCls = objc_allocateClassPair(CLS, [NSString stringWithFormat:@"LSKVONotifying_%@", NSStringFromClass(cls)].UTF8String, 0); objc_registerClassPair(newCls);Copy the code
Subclasses override dealloc
// Subclass override dealloc to implement SEL deallocSel = NSSelectorFromString(@"dealloc"); Method deallocMethod = class_getInstanceMethod(cls, deallocSel); class_addMethod(newCls, deallocSel, (IMP)ls_dealloc, method_getTypeEncoding(deallocMethod));Copy the code
Subclass override class
// Subclass override SEL classSel = NSSelectorFromString(@"class"); Method classMethod = class_getInstanceMethod(cls, classSel); class_addMethod(newCls, classSel, (IMP)ls_class, method_getTypeEncoding(classMethod));Copy the code
Subclass overrides setter
SetterSel = NSSelectorFromString(setter); Method setterMethod = class_getInstanceMethod(cls, setterSel); class_addMethod(newCls, setterSel, (IMP)ls_setter, method_getTypeEncoding(setterMethod));Copy the code
Sets the class of the object to the new class
// Point the former object's owning class to its subclass object_setClass(Observed, newCls);Copy the code
So many basic methods, the implementation of a usable bar
Custom KVO
If you understand the previous observer correspondence of FBKVOController, you will soon understand the correspondence between the custom KVO, that is, one observed for multiple observers
_LSKVOInfo
It can be concluded from the above that each key value and corresponding block corresponds to a carrier, which is a basic data structure of the callback, and we define it as LSKVOInfo
Its data structure is as follows:
{ @public __weak id _observer; // Observer NSString *_keyPath; // listen for the key CallBack _block; // call block SEL _sel; // call sel}Copy the code
_LSClassInfo
This class is responsible for handling the core implementation of the observed logic
{ @public Class _cls; // Create a new Class Class _superCls; // NSMutableDictionary<NSString *, __LSClassKeyPathInfo *> *_keyPathMap; // Add a pair of setter and getter keys each time pointing to the __LSClassKeyPathInfo object NSHashTable<id> *_hashTable; // The dispatch_semaphore_t _semaphore class is set to dispatch_semaphore_t. }Copy the code
1. Create the class
Class only need to create one, so when we create an observer, if the observation class is not created, we will create the observation class, and mark, convenient for next judgment and use; If a class has been created, the system obtains the current class and does not create it.
2. Rewrite dealloc and class
Just like creating a class, it needs to be overridden once, so the rewriting process only needs to be overridden when the class is created
Create class, dealloc, class implementation as follows:
- (void)_observerClass:(Class) CLS info:(_LSKVOInfo *)info {newCls = objc_allocateClassPair(CLS, [NSString stringWithFormat:@"LSKVONotifying_%@", NSStringFromClass(cls)].UTF8String, 0); objc_registerClassPair(newCls); // Subclass override dealloc to implement SEL deallocSel = NSSelectorFromString(@"dealloc"); Method deallocMethod = class_getInstanceMethod(cls, deallocSel); class_addMethod(newCls, deallocSel, (IMP)ls_dealloc, method_getTypeEncoding(deallocMethod)); // Subclass override SEL classSel = NSSelectorFromString(@"class"); Method classMethod = class_getInstanceMethod(cls, classSel); class_addMethod(newCls, classSel, (IMP)ls_class, method_getTypeEncoding(classMethod)); _superCls = cls; _cls = newCls; } #pragma mark overrides the class method, notice what self stands for (called object, application object of this class is observed, Static Class ls_class(id self, SEL _cmd){return class_getSuperclass(object_getClass(self)); } #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 -- pragma mark -- pragma mark Object_setClass (self, class_getSuperclass(object_getClass(self))); }Copy the code
3. Override setter methods
As with class creation, when setter methods for current key values are not overridden, override and save records; If overridden, you do not re-implement setter methods, save the relevant ones later
// override setter and save - (void)addSetterAndSave:(_LSKVOInfo *)info {NSString *setter = [NSString stringWithFormat:@"set%@%@:",[[info->_keyPath substringToIndex:1] uppercaseString], [info->_keyPath substringFromIndex:1]]; __LSClassKeyPathInfo *keyPathInfo = [__LSClassKeyPathInfo alloc]; keyPathInfo->_setter = setter; keyPathInfo->_getter = info->_keyPath; // add key SEL SEL = NSSelectorFromString(setter); const char *encoding = method_getTypeEncoding(class_getInstanceMethod(_superCls, sel)); IMP imp = getTypeEncodingImp(encoding, &(keyPathInfo->_type)); if (! imp) return; class_addMethod(_cls, sel, imp, encoding); [_keyPathMap setObject:keyPathInfo forKey:info->_keyPath]; [_keyPathMap setObject:keyPathInfo forKey:setter]; }Copy the code
4. Set a new class for the current object and set the flag. If the object already has a class, set it no longer
- (void)updateInfo:(_LSKVOInfo *)info observer:(id)observed { dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); // Set the object's class to the new class if (! [_hashTable containsObject:observed]) { object_setClass(observed, _cls); [_hashTable addObject:observed]; __LSClassKeyPathInfo *keyPathInfo = [_keyPathMap objectForKey:info->_keyPath]; if (! KeyPathInfo) {// Subclasses override setter methods to implement addSetterAndSave(self, info); } // Set the basic information of classInfo // get the mapTable corresponding to the object, Corresponding mapTable object does not necessarily exist NSMapTable * mapTable = objc_getAssociatedObject (observed, & kKVOAssociatedMapTableKey); // Get the corresponding if (! MapTable = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality) valueOptions:(NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality) capacity:0]; NSMutableDictionary * infOS = [NSMutableDictionary dictionary]; [infos setObject:info forKey:info->_keyPath]; [mapTable setObject:infos forKey:info->_observer]; / / to join association objc_setAssociatedObject (observed, & kKVOAssociatedMapTableKey, mapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }else { NSMutableDictionary *infos = [mapTable objectForKey:info->_observer]; if (! infos) { infos = [NSMutableDictionary dictionary]; [mapTable setObject:infos forKey:info->_observer]; } [infos setObject:info forKey:info->_keyPath]; } // Add the class association to the object if (! objc_getAssociatedObject(observed, &kKVOAssociatedClassKey)) { objc_setAssociatedObject(observed, &kKVOAssociatedClassKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } dispatch_semaphore_signal(_semaphore); }Copy the code
Collection and association introduction:
In addition to the basic operations, you will find a lot of collection classes, the following describes its functions:
_hashTable: Stores the class as a collection of weak references to be observed, used to determine whether the class has been set. Because of the NSHashTable weak reference type, the object is automatically removed from the collection when it is released
_keyPathMap: Add a pair of setter and getter keys at a time, pointing to the __LSClassKeyPathInfo object
__LSClassKeyPathInfo: encodingTypes that hold setter, getter, setter method arguments (used to override setter methods)
MapTable: corresponding to the observed object, with each observer as the key value and the corresponding callback set as value(easy to obtain directly), and Associated to the observed object
_classInfo: this is the convenient information of the class (holding some metaclass-like processing), which is also Associated with each observed object
Setter method override
The main purpose of obtaining the encodingType of the setter method is to determine the type of parameters passed by the setter method. After all, there may be basic data types when calling the setter method, such as: Int long, which could be of type NSObject, which could be a structure, which could not be received with an ID, so the corresponding IMP is implemented with this type
void addSetterAndSave(_LSClassInfo *classInfo, _LSKVOInfo *info) { NSString *setter = [NSString stringWithFormat:@"set%@%@:",[[info->_keyPath substringToIndex:1] uppercaseString], [info->_keyPath substringFromIndex:1]]; __LSClassKeyPathInfo *keyPathInfo = [__LSClassKeyPathInfo alloc]; keyPathInfo->_setter = setter; keyPathInfo->_getter = info->_keyPath; // add key SEL SEL = NSSelectorFromString(setter); const char *encoding = method_getTypeEncoding(class_getInstanceMethod(classInfo->_superCls, sel)); IMP imp = getTypeEncodingImp(encoding, &(keyPathInfo->_type)); if (! imp) return; class_addMethod(classInfo->_cls, sel, imp, encoding); [classInfo->_keyPathMap setObject:keyPathInfo forKey:info->_keyPath]; [classInfo->_keyPathMap setObject:keyPathInfo forKey:setter]; }Copy the code
Get parameter type:
In the case of the object, the encodingTyes of the setter obtained looks like this:
v24@0:8@16 v--void 24 indicates the size, and so onCopy the code
All the other setter methods up to @ are the same as the previous ones, returning void, id self, SEL _cmd, and the last one is the method parameter we passed, so it can be obtained by this method. The type comparison table is as follows:
That’s how you get the type, and then you override the setter method
The process of rewriting setter methods, pay attention to the packaging of old and new values, such as: number to NSNumber, structure to NSValue, etc., block can not be converted, through KVC to obtain the type of the object
The callback
Simply iterate through the map collection
void _ls_responseSetter(id self, id value, id oldValue, NSMapTable *mapTable = objc_getAssociatedObject(self, &kKVOAssociatedMapTableKey); for (id observer in mapTable) { if (observer) { NSDictionary *infos = [mapTable objectForKey:observer]; if (infos) { _LSKVOInfo *info = [infos objectForKey:keyPath]; if (info) { if (info->_block) info->_block(observer, value, oldValue); else if (info->_sel) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [observer performSelector:info->_sel withObject:value withObject:oldValue]; #pragma clang diagnostic pop } } } } }Copy the code
_KVOControllerClassManager
As the name indicates, it is a managed object for all classes. It is a singleton that holds all classes under observation, performs update or create methods based on whether the class has been created, and avoids some creation security issues
{ @public NSMutableDictionary<NSString *, _LSClassInfo *> *_classInfoMap; // Class dispatch_semaphore_t _semaphore; } // Add a new observation, initialResponse set callback, - (void)observer:(id)observed info:(_LSKVOInfo *)info initialResponse:(BOOL)initialResponse {NSString *clsName = NSStringFromClass([observed class]); dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); _LSClassInfo *classInfo = [_classInfoMap objectForKey:clsName]; if (classInfo) { dispatch_semaphore_signal(_semaphore); [classInfo updateInfo:info observer: Observed]; }else { classInfo = [[_LSClassInfo alloc] init]; [_classInfoMap setObject:classInfo forKey:clsName]; // Add a new class [classInfo _observerClass:object_getClass(observed) info:info]; dispatch_semaphore_signal(_semaphore); [classInfo setInfo:info observer:observed]; } if (initialResponse) { [classInfo responseWithInfo:info observer:observed]; }}Copy the code
NSObject (LSKVOController)
For users to use the class, there are the following ways to use, please refer to the code comments or remind. Md test file
It can listen on a single or multiple keyPath
You can also listen for a specified subkey of a key value and for all subkey values
Supports an immediate callback after listening
/ * note: Only properties support listening, that is, there are setters and getters, and to get old values you must have listener types that are not supported by getters, including custom struct structures, The Union, */ /// Add block listener /// @param observer /// @param keyPath Properties to be observed, // @param callback block(id observer, id newValue, Id oldValue) observer, new value, oldValue /// @param initialResponse default callback once - (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(CallBack)callback initialResponse:(BOOL)initialResponse; // no callBack by default - (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull) callBack:(callBack) callBack; // add block listeners to multiple properties // @param Observer // @param keyPaths // @param callback block(id observer, id newValue, Id oldValue) observer, new value, oldValue /// @param initialResponse whether the default callback is once - (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths callBack:(CallBack)callback initialResponse:(BOOL)initialResponse; // no callback by default - (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths callBack:(CallBack)callback; // Add a subattribute listening block to the specified key attribute. If a subattribute of the specified key attribute changes, the current attribute will be called back. // @param observer // @param keyPath Set of properties to be observed, // @param callback block(id observer, id newValue, Id oldValue) observer, new value, oldValue /// @param initialResponse whether the default callback is once - (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(SubCallBack)callback initialResponse:(BOOL)initialResponse; // no callBack by default - (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(SubCallBack) callBack; // add a subattribute to the attribute to listen for block, specify that the subattribute of the key value changes, // @param Observer // @param keyPath Observation property keys // @Param Subkeypath Response sub-property whitelist set for the observation properties, // @param callback block(id observer, id newValue, Id oldValue) observer, new value, oldValue /// @param initialResponse whether the default callback is once - (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath subKeyPaths:(NSArray<NSString *> *)subkeyPaths callBack:(SubCallBack)callback initialResponse:(BOOL)initialResponse; // default no callback - (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath subkeypath :(NSArray<NSString *> *)subkeyPaths callBack:(SubCallBack)callback; // add SEL listener // @param observer // @param keyPath // @param initialResponse is called once by default -. // @param initialResponse is called once (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath selector:(nonnull SEL)sel initialResponse:(BOOL)initialResponse; // no callback by default - (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath selector:(nonnull SEL) SEL; /// add SEL listener /// @param Observer /// @param keyPaths // @param initialResponse is called once by default -. // @param initialResponse is called once (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths selector:(nonnull SEL)sel initialResponse:(BOOL)initialResponse; // no callback by default - (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull) selselts :(nonnull SEL) selts;Copy the code
The last
Welcome to discuss if you find any problems