iOS KVO 自定义

137 阅读2分钟

接着上一篇文章 KVO原理,模仿官方自定义实现KVO

1.在addObserver代码执行后,系统动态生成了一个NSKVONotifying_Person

2.Person的isa指针改变指向,指向了NSKVONotifying_Person

3.NSKVONotifying_Person重写下面的

set方法(无法监听成员变量,只能监听set方法)、 也修改原来的成员变量的值,同时做回调

class方法 返回原来的Person类

4.removeObserver方法之后重新将根据残余KeyPath的数量调整isa指向

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy) NSString * nickName;


@end


#import "Person.h"
@implementation Person{
 
}
@end
#import <Foundation/Foundation.h>

typedef NS_OPTIONS(NSUInteger, KLKeyValueObservingOptions) {

    KLKeyValueObservingOptionNew = 0x01,
    KLKeyValueObservingOptionOld = 0x02
};
 

@interface KLKVOInfo : NSObject

@property(nonatomic,copy)   NSString * keyPath;
@property(nonatomic,weak)   NSObject * observer;
@property(nonatomic,assign) KLKeyValueObservingOptions options;

- (instancetype)initWithObsever:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(KLKeyValueObservingOptions)options;

@end


#import "KLKVOInfo.h"

@implementation KLKVOInfo

- (instancetype)initWithObsever:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(KLKeyValueObservingOptions)options{
    if(self = [super init]){
        _observer = observer;
        _keyPath = keyPath;
        _options = options;
    }
    return self;
}

@end

#import "KLKVOInfo.h"

 

@interface NSObject(KLKVO)

- (void)KL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(KLKeyValueObservingOptions)options context:(nullable void *)context;
- (void)KL_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)kl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
@end



#import "NSObject+KLKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>

static const NSString * KLKVOClassPrefifix = @"KLKVONotifying_";
static const NSString * KLKVOAssciocateKey = @"KLKVOAssciocateKey";

@implementation NSObject(KLKVO)
- (void)KL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(KLKeyValueObservingOptions)options context:(nullable void *)context{
    
    //1.排除成员变量和属性\
    [self setterMethodJudgeFromKeyPath:keyPath];
    
    //2.kvo - isa_swiziling
    Class newClass = [self createChildClassWithKeyPath:keyPath];
      
    //3. isa指向
    object_setClass(self, newClass);
    
    //4.setter业务逻辑
        //保存下观察者
    
    //因为可能会有多个keypath需要观察 所以这里需要收集多个数据
    KLKVOInfo * info = [[KLKVOInfo alloc]initWithObsever:observer forKeyPath:keyPath options:options];
    
    NSMutableArray * infos = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey));
    if(!infos){
        infos = [NSMutableArray arrayWithCapacity:1];
    }
    [infos addObject:info];
    
    
    //注意循环引用问题  vc -> self-> infos -> info -> observer(vc)
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey), infos, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)KL_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context{
    
    NSMutableArray * infos = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey));
    
    //判断是否还有观察的keypath,如果为0则改变isa指向恢复原来
    
    
    for(KLKVOInfo * info in infos){
        if([info.keyPath isEqualToString:keyPath]){
            [infos removeObject:info];
            
            objc_setAssociatedObject(self,  (__bridge const void * _Nonnull)(KLKVOAssciocateKey), infos, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            
            break;
        }
         
    }
     
    //指向还原\
    if(infos.count <= 0){
        Class superClass = [self class];
        object_setClass(self,superClass);
    }
    
}


//创建子类
- (Class)createChildClassWithKeyPath:(NSString*)keyPath{
    
    Class superClass = [self class];
    NSString * oldClassName = NSStringFromClass([self class]);
    NSString * newClassName = [NSString stringWithFormat:@"%@%@",KLKVOClassPrefix,oldClassName];
    
    Class newClass = NSClassFromString(newClassName);
    
    //2.1申请类
    if(newClass){
        return newClass;
    }
    
    newClass = objc_allocateClassPair(superClass, newClassName.UTF8String0);
 
    
    //2.2添加方法
    
    //set方法
    SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSel);
    const char * setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSel, (IMP)kl_setter, setterType);
    
    //class方法
    SEL classSel = NSSelectorFromString(@"class");
    Method classMethod = class_getClassMethod(superClass, classSel);
    const char * classType = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSel, (IMP)kl_class, classType);
    
    
    //dealloc方法
    

    //2.3注册类
    objc_registerClassPair(newClass);
    
    return newClass;
}

//子类重写的IMP
static void kl_setter(id self,SEL _cmd,id newValue,id oldValue){
    //判断自动开关
    
    //核心  Person  setter  _cmd 父类发送消息
    struct objc_super kl_objc_super;
    kl_objc_super.receiver = self;
    kl_objc_super.super_class = class_getSuperclass(object_getClass(self));
    objc_msgSendSuper(&kl_objc_super,_cmd,newValue);

    //向观察者发送 通知
   
    
    NSString * keyPath = getterFromSetter(NSStringFromSelector(_cmd));
    
    
    NSMutableArray * infos = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)KLKVOAssciocateKey);
    
    for (KLKVOInfo * info in infos) {
        
        if(![info.keyPath isEqualToString:keyPath])
        {
            break;
        }
        
        //observer不是self 而是add的时候添加的,但是此处正常方法无法取到,所以使用关联对象获取\
        id observer = info.observer;
        
        NSMutableDictionary<NSKeyValueChangeKey,id> * change = [NSMutableDictionary dictionaryWithCapacity:1];
        
        dispatch_async(dispatch_get_global_queue(00), ^{
            
            //观察新值 旧值 判断
            
            if(info.options & KLKeyValueObservingOptionNew){\
                [change setObject:newValue forKey:NSKeyValueChangeNewKey];
            }
            
            if(info.options & KLKeyValueObservingOptionOld){\
                [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                if(oldValue){
                    [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                }
            }
           
            //发送观察回调消息
            SEL observeSEL = @selector(kl_observeValueForKeyPath:ofObject:change:context:);
            
            objc_msgSend(observer, observeSEL,keyPath,self,change,NULL);
        });
        
    }
     
}

Class kl_class(id self,SEL _cmd){\
    return class_getSuperclass(object_getClass(self));
}


//判断setter方法是否存在
- (void)setterMethodJudgeFromKeyPath:(NSString*)keyPath{
    Class superClass = object_getClass(self);
    
    SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(superClass, setterSelector);
    if(!setterMethod){
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"当前%@缺少setter方法",keyPath] userInfo:nil];
    }
}

//根据getter获取setter方法名
static NSStringsetterForGetter(NSString* getter){
    
    if(getter.length <= 0){return nil;}
    
    NSString * firstString = [[getter substringToIndex:1] uppercaseString];
    NSString * leaveString = [getter substringFromIndex:1];
    
    NSString * setterStr = [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
    
    return setterStr;
}

//根据setter获取getter方法名
static NSStringgetterFromSetter(NSString* setter){
    
    if(setter.length <= 0){return nil;}
    
    
    NSString * leaveString = [setter substringWithRange:NSMakeRange(4, setter.length-5)];
    NSString * firstString = [setter substringWithRange:NSMakeRange(31)].lowercaseString;
    
    NSString * getterStr = [NSString stringWithFormat:@"%@%@",firstString,leaveString];
    
    return getterStr;
}
@end