preface
“This is the 16th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge.”
The customKVO
The preparatory work
In the last article, we looked at the basics of KVO in detail, so this article will follow the implementation of KVO and customize it.
Since KVO is displayed as an NSObject+NSKeyValueObserving category at the bottom, we can customize it by creating the NSObject+LGKVO category first.
In the NSObject + LGKVO. H:
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
Copy the code
In the NSObject + LGKVO. M:
First define two global static constants:
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
Copy the code
Then import the #import
header.
Then define the getter and setter methods separately, so that you can run debugging directly afterwards:
setter
methods
#pragma mark - obtain the name of the set method from get ===>>> setKey: static NSString *setterForGetter(NSString *getter) { if (getter.length <= 0) { return nil; } NSString *firstString = [[getter substringToIndex:1] uppercaseString]; NSString *leaveString = [getter substringFromIndex:1]; return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString]; }Copy the code
getter
methods
#pragma mark - getgetterforsetter (NSString *setter){if (NSString *setter) (setter.length <= 0 || ! [setter hasPrefix:@"set"] || ! [setter hasSuffix:@":"]) { return nil; } NSRange range = NSMakeRange(3, setter.length-4); NSString *getter = [setter substringWithRange:range]; NSString *firstString = [[getter substringToIndex:1] lowercaseString]; return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString]; }Copy the code
Registered observer
Then I implement the register observer method in NSObject+ lgKvo.m,
Idea is:
- 1: verifies whether the interface exists
setter
Method: keep the instance out - 2: dynamically generating subclasses
- 3:
isa
To:LGKVONotifying_LGPerson
- 4: Save the observer
The code is as follows:
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options Context: (nullable void *) context {/ / 1: to verify whether there is a setter method: don't let the instance in [the self judgeSetterMethodFromKeyPath: keyPath]; / / 2: the dynamically generated Class subclass newClass = [self createChildClassWithKeyPath: keyPath]; // 3: isa point: LGKVONotifying_LGPerson object_setClass(self, newClass); // 4: save observer LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options]; NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey)); if (! observerArr) { observerArr = [NSMutableArray arrayWithCapacity:1]; [observerArr addObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }}Copy the code
Verify for existencesetter
methods
# pragma mark - verify whether there is a setter method - (void) judgeSetterMethodFromKeyPath keyPath: (nsstrings *) {Class superClass = object_getClass(self); SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getInstanceMethod(superClass, setterSeletor); if (! setterMethod) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString StringWithFormat :@" old iron has no setter for the current %@ ",keyPath] userInfo:nil]; }}Copy the code
Dynamically generated subclass
Ideas:
- Verify that a dynamic subclass has been created
- If it is already created, return it directly
- If it is not created, go to ②
- (2) Create subclasses
- The first application class, the need for the parent class, the name of the new class, open up new space
- Registered again
- Finally add the method, required
class
、setter
methods
# pragma mark - dynamic subclassing - (Class) createChildClassWithKeyPath keyPath: (nsstrings *) {nsstrings * oldClassName = NSStringFromClass([self class]); NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName]; Class newClass = NSClassFromString(newClassName); If (newClass) return newClass; /** * If memory does not exist, create a new class. */ // 2.1: NewClass = objc_allocateClassPair([self class], newClassName.utf8String, 0); // 2.2: register class objc_registerClassPair(newClass); // 2.3.1: add class: class refers to LGPerson SEL classSEL = NSSelectorFromString(@"class"); Method classMethod = class_getInstanceMethod([self class], classSEL); const char *classTypes = method_getTypeEncoding(classMethod); class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes); // 2.3.2: Add setsel setterSEL = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getInstanceMethod([self class], setterSEL); const char *setterTypes = method_getTypeEncoding(setterMethod); class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes); return newClass; }Copy the code
Save observer
To do this, create a new LGKVOInfo that inherits from NSObject to hold the observer information, add multiple instances of LGKVOInfo to the array, and then do the related operations by storing the associated objects in self. The detailed code is as follows:
In LGKVOInfo. H:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
LGKeyValueObservingOptionNew = 0x01,
LGKeyValueObservingOptionOld = 0x02,
};
@interface LGKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
@end
NS_ASSUME_NONNULL_END
Copy the code
M: in LGKVOInfo.
#import "LGKVOInfo.h"
@implementation LGKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options{
self = [super init];
if (self) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
@end
Copy the code
Implement subclass methods
When subclassing dynamically, class and setter methods are overridden, and their IMPs point to lg_class and LG_setter function addresses, respectively. By storing the associated object in self, this is the same as when we analyzed KVO in the last part, and using the class method overwritten by the intermediate class, we still get the original class object, as if nothing KVO did existed.
lg_class
Class lg_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
Copy the code
lg_setter
:
Ideas:
- ① Message forwarding: forward to the parent class, change the value of the parent class
- (2) Get the observer
- ③, the new and old values for processing
- (4) Send messages to observers
Static void lg_setter(id self,SEL _cmd,id newValue){NSLog(@" here :%@",newValue); NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd)); id oldValue = [self valueForKey:keyPath]; void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper; // void /* struct objc_super *super, SEL op, ... */ struct objc_super superStruct = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)), }; //objc_msgSendSuper(&superStruct,_cmd,newValue) lg_msgSendSuper(&superStruct,_cmd,newValue); / / 2: Get the observer NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey)); for (LGKVOInfo *info in observerArr) { if ([info.keyPath isEqualToString:keyPath]) { dispatch_async(dispatch_get_global_queue(0, 0), ^{NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1]; / / 3 to deal with the old and new values if (info. The options & LGKeyValueObservingOptionNew) {[change setObject: newValue forKey:NSKeyValueChangeNewKey]; } if (info.options & LGKeyValueObservingOptionOld) { [change setObject:@"" forKey:NSKeyValueChangeOldKey]; if (oldValue) { [change setObject:oldValue forKey:NSKeyValueChangeOldKey]; }} / / 4: a message sent to the observer SEL observerSEL = @ the selector (lg_observeValueForKeyPath: ofObject: change: context:); objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL); }); }}}Copy the code
Remove observer
Ideas:
- Remove observers;
isa
Refers back to the original class object
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey)); if (observerArr.count<=0) { return; } for (LGKVOInfo *info in observerArr) { if ([info.keyPath isEqualToString:keyPath]) { [observerArr removeObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC); break; }} if (observerarr. count<=0) {Class superClass = [self Class]; object_setClass(self, superClass); }}Copy the code
At this point, the customization of KVO is complete, so let’s use:
- (void)viewDidLoad { [super viewDidLoad]; self.person = [[LGPerson alloc] init]; NSLog(@" before registering observer: %s",object_getClassName(self.person)); [self.person lg_addObserver:self forKeyPath:@"nickName" options:(LGKeyValueObservingOptionNew|LGKeyValueObservingOptionOld) context:NULL]; NSLog(@" after registering observer: %s",object_getClassName(self.person)); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.nickName = @"KC"; } #pragma mark -kvo callback - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@",change); } - (void)dealloc{NSLog(@" remove observer before: %s",object_getClassName(self.person)); [self.person lg_removeObserver:self forKeyPath:@"nickName"]; NSLog(@"end"); NSLog(@" after removing observer: %s",object_getClassName(self.person)); }Copy the code
Take a look at the print:
The customKVO
– functional
Functional programming is written as a series of nested functions and then called. Functional programming supports functions as first-class objects, which can also be called closures or functor objects.
Advantages of functional programming:
-
(1) The code is concise and the development speed is fast: functional programming uses a lot of functions to reduce the repetition of the code, so the program is short and the development speed is fast;
-
(2) Close to natural language programming, easy to understand: functional programming has a high degree of freedom, can write very close to natural language code;
-
③, more convenient code management: functional programming does not depend on the state of the outside world, nor will it change the state of the outside world, as long as given the specified parameters, then the return result must be the same. Therefore, each function can be treated as an independent unit, which is very good for unit testing and bug debugging, and is also very easy to modular combination.
-
④ Easy concurrent programming: functional programming does not require deadlock, because it does not modify variables, so there is no problem with locking threads. You don’t have to worry about one thread’s data being modified by another, so you can easily split your work across multiple threads and deploy concurrent programming.
So custom KVO, why use functional programming?
Through above lg_observeValueForKeyPath: ofObject: change: context: this call is not very convenient. So, let’s introduce the functional idea of using blocks to implement listening callbacks. Next, let’s optimize.
In lgkvoinfo. h, add LGKVOBlock:
@interface LGLKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) LGKVOBlock handleBlock;
@end
@implementation LGLKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block {
if (self=[super init]) {
_observer = observer;
_keyPath = keyPath;
_handleBlock = block;
}
return self;
}
@end
Copy the code
In NSObject+ lgkvo. h, the lg_addObserver method also adds blocks:
typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
Copy the code
In NSObject+ lgkvo. m, the lg_addObserver method adjusts:
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{ // 1: Verify whether there is a setter method: don't let the instance in [the self judgeSetterMethodFromKeyPath: keyPath]; / / 2: the dynamically generated Class subclass newClass = [self createChildClassWithKeyPath: keyPath]; // 3: isa point: LGKVONotifying_LGPerson object_setClass(self, newClass); // 4: save information LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block]; NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey)); if (! mArray) { mArray = [NSMutableArray arrayWithCapacity:1]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } [mArray addObject:info]; }Copy the code
In NSObject+ sslkvo. m, the lg_removeObserver method adjusts:
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey)); if (observerArr.count<=0) { return; } for (LGKVOInfo *info in observerArr) { if ([info.keyPath isEqualToString:keyPath]) { [observerArr removeObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC); break; }} if (observerarr. count<=0) {Class superClass = [self Class]; object_setClass(self, superClass); }}Copy the code
In NSObject+ lgKvo. m, the lg_setter method is adjusted, instead of calling the KVO callback method, instead of calling the block saved in info:
Static void lg_setter(id self,SEL _cmd,id newValue){NSLog(@" here :%@",newValue); NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd)); id oldValue = [self valueForKey:keyPath]; Void (*lg_msgSendSuper)(void *,SEL, id) = (void *)objc_msgSendSuper; // void /* struct objc_super *super, SEL op, ... */ struct objc_super superStruct = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)), }; //objc_msgSendSuper(&superStruct,_cmd,newValue) lg_msgSendSuper(&superStruct,_cmd,newValue); NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey)); for (LGKVOInfo *info in mArray) { if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) { info.handleBlock(info.observer, keyPath, oldValue, newValue); }}}Copy the code
After optimization, and then in the controller inside the call, will be much simpler, the code is as follows:
- (void)viewDidLoad { [super viewDidLoad]; self.person = [[LGPerson alloc] init]; NSLog(@" before registering observer: %s",object_getClassName(self.person)); [self.person lg_addObserver:self forKeyPath:@"nickName" block:^(id _Nonnull observer, NSString * _Nonnull keyPath, Id _Nonnull oldValue, id _Nonnull newValue) {NSLog(@" callback method: %@ - %@",oldValue,newValue);}]; NSLog(@" after registering observer: %s",object_getClassName(self.person)); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.nickName = @"KC"; } - (void)dealloc {NSLog(@" remove observer before: %s",object_getClassName(self.person)); [self.person lg_removeObserver:self forKeyPath:@"nickName"]; NSLog(@" after removing observer: %s",object_getClassName(self.person)); }Copy the code
Print results after operation:
Self-destruct mechanism
In the previous article, we looked at whether we could override the subclass dealloc method to automatically remove the observer if it needed to be destroyed, since we had to manually remove the observer after using KVO.
Dynamically generated subclass
: first of all increateChildClassWithKeyPath
Method to adddealloc
:
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{ NSString *oldClassName = NSStringFromClass([self class]); NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName]; Class newClass = NSClassFromString(newClassName); If (newClass) return newClass; /** * If memory does not exist, create a new class. */ // 2.1: NewClass = objc_allocateClassPair([self class], newClassName.utf8String, 0); // 2.2: register class objc_registerClassPair(newClass); // 2.3.1: add class: class refers to LGPerson SEL classSEL = NSSelectorFromString(@"class"); Method classMethod = class_getInstanceMethod([self class], classSEL); const char *classTypes = method_getTypeEncoding(classMethod); class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes); // 2.3.2: Add setsel setterSEL = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getInstanceMethod([self class], setterSEL); const char *setterTypes = method_getTypeEncoding(setterMethod); class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes); // 2.3.3: add deallocSEL deallocSEL = NSSelectorFromString(@"dealloc"); Method deallocMethod = class_getInstanceMethod([self class], deallocSEL); const char *deallocTypes = method_getTypeEncoding(deallocMethod); class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes); return newClass; }Copy the code
- Added when subclassing dynamically
dealloc
Method,IMP
Point to thelg_dealloc
Function address, inlg_dealloc
Inside the method:
Static void lg_dealloc(id self,SEL _cmd) {NSLog(@" before autodestruct: %s",object_getClassName(self)); Class superClass = [self class]; object_setClass(self, superClass); NSLog(@" after autodestruct: %s",object_getClassName(self)); }Copy the code
- The use of the autodestruct mechanism is called directly from within the controller
dealloc
Methods:
- (void)dealloc{NSLog(@" destroyed "); }Copy the code
Run the print result:
FBKVOController
explore
FBKVOController
Basic use of
FBKVOController is a Facebook open source framework based on system KVO implementation. Supports Objective-C and Swift languages. Download it on gitHub
Advantages of FBKVOController:
- No need to manually remove observers; The frame automatically removes observers for us
- Functional programming, can be a line of code to achieve the system
KVO
Three steps. - implementation
KVO
Same code context as where the event occurs, no need to pass parameters across methods; - increased
block
andSEL
Custom operation pairNSKeyValueObserving
Callback processing support for improved usageKVO
The experience; - each
keyPath
Would correspond to ablock
orSEL
You don’t have toblock
The use ofif
judgekeyPath
In other words, there is no need for unifiedobserveValueForKeyPath
Methods in writingif
Judgment; - You can listen on multiple attributes of an object at the same time.
- Thread safe.
Let’s first look at the use of FBKVOController. The code looks like this:
#import "ViewController.h" #import <FBKVOController.h> #import "LGPerson.h" @interface ViewController () @property (nonatomic, strong) FBKVOController *kvoCtrl; @property (nonatomic, strong) LGPerson *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [[LGPerson alloc] init]; [self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) { NSLog(@"****%@****",change); }]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.name = @"cooci"; } - (FBKVOController *)kvoCtrl{ if (! _kvoCtrl) { _kvoCtrl = [FBKVOController controllerWithObserver:self]; } return _kvoCtrl; } @endCopy the code
- Here is the one used
block
The callback:
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
Copy the code
In addition to the block call, there are other ways to implement it:
- use
action
The callback:
[self.kvoCtrl observe:self.person keyPath:@"age" options:NSKeyValueObservingOptionNew action:@selector(lg_observerAge)];
Copy the code
- Listening on mutable arrays
[self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
Copy the code
FBKVOController
The source code parsing
Initialize the
Registered observer
- in
_FBKVOInfo
Class, which stores observer information, including:FBKVOController
,keyPath
,options
,block
And so on; - will
observe
andinfo
Correlate;
observe
andinfo
Correlation details of
- in
FBKVOController
When it initializes, it holdsNSMapTable
, the use ofNSMapTable
Replace associated objects; - Then put the
object
As akey
To obtainNSMutableSet
The type ofvalue
And at the same time,NSMutableSet
Stored in the_FBKVOInfo
; - if
infos
Empty, create, and add toNSMapTable
; - when
infos
Not null after willinfo
Object toNSMutableSet
The type ofinfos
; - middle-borns
_FBKVOSharedController
Is the singleton pattern responsible for the realKVO
Operation;
middle-borns_FBKVOSharedController
Analysis of the
- Registration system
KVO
- The incoming
object
Call the system for the instance objectKVO
To start listening for properties; - The incoming
addObserver
methodsself
Is the mediator of the current singleton; - Intermediate holding
NSHashTable
Is stored as a weak reference_FBKVOInfo
; - through
_FBKVOInfo
the_state
Member variable, according to the enumerated value_FBKVOInfoStateInitial
,_FBKVOInfoStateObserving
,_FBKVOInfoStateNotObserving
Decide to add or deleteKVO
;
- The incoming
- To implement the callback
- through
info
To obtainFBKVOController
; - from
FBKVOController
, getobserver
Which is the real thingVC
; - through
info
To obtainblock
And calls, passing in the necessary arguments;
- through
- The destruction
Copy its own NSMapTable to the temporary table, clean it up, traverse the temporary table again, and remove the association between the instance object and infOS from the singleton mediator
- Remove the system
KVO
Listening in- Call system
KVO
theremoveObserver
Method to remove the observer - Destruction process:
VC
Destruction — — >FBKVOController
Destroy –> intermediateunobserve
Method –> Remove observer
- Call system