This is the sixth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
review
In the previous two posts, we have introduced KVO operations, and the underlying logic of KVO is implemented by dynamically subclassing and overriding the parent class. So how do we customize a KVO?
KVO for iOS (1) — Introduction to KVO
Exploration of iOS Underlying KVO(ii) — Analysis of KVO principle
1. Preliminary analysis
KVO for the system extends some capabilities on top of NSObject, as shown below:
The trilogy of systems used by KVO is:
- Add to monitor
addObserver
- Listen to the callback
observeValueForKeyPath
- Remove the monitor
removeObserver
We also have a custom KVO modeled on the system API, as follows:
@interface NSObject (JP_KVO)
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
Copy the code
Our custom KVO is to observe the property, so there is no setter for the member variable, so the first step is to filter out the member variable. How to dynamically subclass, change the isa orientation, and save the observer is roughly as follows:
- Verify for existence
setter
Method: Keep member variables (instance variables) out - Dynamically generated subclass
- Change the isa pointing of a subclass
- Save our observer
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
// 1: verify that there is a setter method: do not let the instance in
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: dynamically generating subclasses
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa pointing
object_setClass(self, newClass);
// 4: Save the observer
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KJPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Copy the code
2. Verify that the setter method exists
- judgeSetterMethodFromKeyPath:keyPath
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
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:At sign, "Sorry, there's no setter for the current % at sign.",keyPath] userInfo:nil]; }}Copy the code
If there is a setter method, throw an exception
3. Dynamically subclass
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@ % @ % @ "",kjpKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// Prevent duplicate creation to generate new classes
if (newClass) return newClass;
/** * If memory does not exist, create a generation * parameter 1: the parent class * parameter 2: the name of the new class * parameter 3: the extra space opened by the new class */
// 2.1: Application
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2: registration class
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是JPPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)jp_class, classTypes);
// 2.3.2: adding setters
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)jp_setter, setterTypes);
return newClass;
}
Copy the code
- JP_class returns information about the parent class
Class JP_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
Copy the code
- Determine if subclasses have been created, if not
- Apply for class
- registered
newClass
If it does not existobjc_allocateClassPair
createkvo
Subclass, and rewrite- class
methods- add
setter
- return
newClass
4. Create a setter
There are two main parts to creating setter methods: calling superclass methods and sending notifications
static void jp_setter(id self,SEL _cmd,id newValue){
// 4: message forwarding: forwarding to the parent class
// Change the value of the parent class -- you can cast it
void (*jp_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)
jp_msgSendSuper(&superStruct,_cmd,newValue);
// Now that we have observed, the next step is to call back -- for our observer to call
// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
// 1: Get the observer
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJPKVOAssiociateKey));
// 2: the message is sent to the observer
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
Copy the code
-
Calling a superclass method can be done by objc_msgSendSuper, and calling a setter for the superclass (or by performing Selector)
-
Inform the observer that the keypath can be obtained through the _cmd transformation, that object is self, that change can also be obtained, and that context can be left alone. The core is the acquisition of an observer.
-
The observer is stored as an associated object
-
Get the observer, then send the message to the observer, information callback processing
-
getterForSetter
#pragmaMark - Gets the name set of the getter method from the set method<Key>:===> key
static NSString *getterForSetter(NSString *setter) {if (setter.length <= 0| |! [setter hasPrefix:@"set") | |! [setter hasSuffix:@ ","]) { return nil; }NSRange range = NSMakeRange(3.setter.length4 -);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0.1) withString:firstString];
}
Copy the code
More to come
🌹 just like it 👍🌹
🌹 feel have harvest, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹
🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹