美文网首页
手动撸一个带callback的KVO

手动撸一个带callback的KVO

作者: sea777777 | 来源:发表于2020-08-21 15:16 被阅读0次

    大概思路:创建一个子类,然后把父类的 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
    

    相关文章

      网友评论

          本文标题:手动撸一个带callback的KVO

          本文链接:https://www.haomeiwen.com/subject/zniujktx.html