手动撸一个带callback的KVO
2020-08-21 本文已影响0人
sea777777
大概思路:创建一个子类,然后把父类的 isa 指向子类 (object_setClass),在子类里新增一个setter方法,并在这个setter里调用父类的setter,调用完,执行block回调;
typedef void (^YCCallback)(id object, NSString *key, id newValue);
@interface NSObject (KVO)
-(void)addObserver:(NSObject *)target forKeyPath:(NSString *)keyPath callBack:(YCCallback)callback;
-(void)removeObserverForKeyPath:(NSString *)keyPath;
@end
#import "NSObject+KVO.h"
#import <objc/runtime.h>
#define kSetterPrefix @"set"
#define kGenObserverClsName(clsName) [NSString stringWithFormat:@"YCObserver_%@",clsName]
static NSString *const kMethodStorageKey = @"kMethodStorageKey";
static NSString *const kCallbackStorageKey = @"kCallbackStorageKey";
@implementation NSObject (KVO)
-(void)addObserver:(NSObject *)target forKeyPath:(NSString *)keyPath callBack:(YCCallback)callback{
if (target == nil || keyPath == nil || keyPath.length == 0) return;
Class origionCls = object_getClass(self);
NSString *origionClsName = NSStringFromClass(origionCls);
NSString *observerClsName = kGenObserverClsName(origionClsName);
SEL origionSetter = [self generateSetter:keyPath];
//Method 的应用场景:获取方法签名
Method origionSetterMethod = class_getInstanceMethod(origionCls, origionSetter);
Class allocatedNewClass = [self generateNewClass:self.class className:observerClsName];
//修改self的isa ,让他指向新的子类
object_setClass(self, allocatedNewClass);
//存储父类的 method,以后会用到
initMethodStorage(self);
storeMethod(self, NSStringFromSelector(origionSetter),origionSetterMethod);
initCallBackStorage(self);
storeCallBack(self, NSStringFromSelector(origionSetter), callback);
//给子类新增setter方法,覆盖父类的setter,子类方法名不变,只是IMP是自定义的
class_addMethod(allocatedNewClass, origionSetter, (IMP)yc_setter, method_getTypeEncoding(origionSetterMethod));
}
-(Class)generateNewClass:(Class)superClass className:(NSString *)className{
//objc_allocateClassPair 和 objc_registerClassPair 成对出现
Class allocatedNewClass = objc_allocateClassPair(superClass, [className UTF8String], 0);
objc_registerClassPair(allocatedNewClass);
return allocatedNewClass;
}
//生成setter方法:setWhat:
-(SEL)generateSetter:(NSString *)keyPath {
NSString *firstKeyPathChar = [keyPath substringToIndex:1];
NSString *otherKeyPathChar = [keyPath substringFromIndex:1];
NSMutableString *setterSELName = [kSetterPrefix mutableCopy];
[setterSELName appendString:[firstKeyPathChar uppercaseString]];
[setterSELName appendString:otherKeyPathChar];
[setterSELName appendString:@":"];
return NSSelectorFromString(setterSELName);
}
//setWhat: -> what
NSString* revertSetter(SEL sel){
NSString *selName = NSStringFromSelector(sel);
if ([selName hasPrefix:kSetterPrefix]) {
selName = [selName stringByReplacingOccurrencesOfString:kSetterPrefix withString:@""];
NSString *firstChar = [selName substringToIndex:1];
NSRange range = NSMakeRange(1, selName.length - 2);
NSString *lastChars = [selName substringWithRange:range];
selName = [[firstChar lowercaseString] stringByAppendingString:lastChars];
}
return selName;
}
void yc_setter(id target, SEL sel, id newValue){
//自定义setter 一定要调用父类的 setter
NSString *selName = NSStringFromSelector(sel);
//拿到父类的 IMP ,再调用
Method superMethod = loadMethod(target,selName);
IMP superImp = method_getImplementation(superMethod);
SEL superSEL = method_getName(superMethod);
((id(*)(id, SEL, id))superImp)(target, superSEL, newValue);
YCCallback callback = loadCallback(target, selName);
if (callback) {
callback([target superclass],revertSetter(sel),newValue);
}
}
void initMethodStorage(id target){
if (!objc_getAssociatedObject(target, &kMethodStorageKey)) {
objc_setAssociatedObject(target, &kMethodStorageKey, [NSMutableDictionary new], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
void initCallBackStorage(id target){
if (!objc_getAssociatedObject(target, &kCallbackStorageKey)) {
objc_setAssociatedObject(target, &kCallbackStorageKey, [NSMutableDictionary new], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
void storeMethod(id target,NSString *key,Method method){
if (!target || !key || !method) {
return;
}
NSMutableDictionary *storage = objc_getAssociatedObject(target, &kMethodStorageKey);
[storage setObject:(__bridge id _Nonnull)(method) forKey:key];
}
void storeCallBack(id target,NSString *key,YCCallback callback){
if (!target || !key) {
return;
}
NSMutableDictionary *storage = objc_getAssociatedObject(target, &kCallbackStorageKey);
if (callback == nil) {
[storage removeObjectForKey:key];
}else{
[storage setObject:callback forKey:key];
}
}
Method loadMethod(id target,NSString *key){
if (!target || !key) {
return nil;
}
NSMutableDictionary *storage = objc_getAssociatedObject(target, &kMethodStorageKey);
return (__bridge Method)([storage objectForKey:key]);
}
YCCallback loadCallback(id target,NSString *key){
if (!target || !key) {
return nil;
}
NSMutableDictionary *storage = objc_getAssociatedObject(target, &kCallbackStorageKey);
return (YCCallback)[storage objectForKey:key];
}
-(void)removeObserverForKeyPath:(NSString *)keyPath{
SEL deleteSel = [self generateSetter:keyPath];
storeCallBack(self, NSStringFromSelector(deleteSel), nil);//删除callback
}
@end
@interface ViewController ()
@property (nonatomic,strong) What *w;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.w = [What new];
[self.w addObserver:self forKeyPath:@"what" callBack:^(id _Nonnull object, NSString * _Nonnull key, id _Nonnull newValue) {
}];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.w.what = @"xxx";
[self.w removeObserverForKeyPath:@"what"];
}
@end
@interface What : NSObject
@property(strong,nonatomic) NSString *what;
@end