IOS运行时实现自己的KVO

来源:互联网 发布:pptv网络电视怎么看直播 编辑:程序博客网 时间:2024/06/05 16:14

参考文章 http://www.cocoachina.com/ios/20150313/11321.html

本文在此基础上为每段代码添加详细的注释

1.首先,我们创建 NSObject 的 Category,并在头文件中添加两个 API:

#import <Foundation/Foundation.h>

typedef void (^ZZXObservingBlock) (id observedObject,NSString *observedKey, id oldValue,id newValue);

@interface NSObject (ZZXKVO)

/*

 添加自己的 addobserver

 取代NSObject原有的 [self addObserver:(nonnull NSObject *) forKeyPath:(nonnull NSString *) options:(NSKeyValueObservingOptions) context:      (nullable void *)];

 

 PGObservingBlock block回调

 取代 NSObject原有的- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id>  *)change context:(void *)context

 */

- (void)ZZX_addObserver:(NSObject *)observer forkey:(NSString *)key withBlock:(ZZXObservingBlock)block;

- (void)ZZX_removeObserver:(NSObject *)observer forKey:(NSString *)key;

@end


2.然后是具体实现部分

注意点

1.  objc_msgSendSuperCasted(&superclazz, _cmd, newValue); 方法里面一定要给发消息给父类执行父类的setter

2.  object_setClass(self, clazz); 方法里面一定要给发消息给父类执行父类的setter


#import "NSObject+ZZXKVO.h"

#import <objc/runtime.h>

#import <objc/message.h>


/*

 新建类名的前缀

 */

NSString *const ZZXKVOClassPreFix =@"ZZXKVOClassPreFix_";

/*

 动态添加数组的标识

 */

NSString *const ZZXKVOAssociatedObservers =@"ZZXKVOAssociatedObservers";


#pragma mark - ZZXObservationInfo

/*

    添加的observer信息,存储在数组里

 */

@interface ZZXObservationInfo :NSObject

@property (nonatomic,weak)NSObject *observer;

@property (nonatomic,copy)NSString *key;

@property (nonatomic,copy)ZZXObservingBlock block;

@end


@implementation ZZXObservationInfo

- (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(ZZXObservingBlock)block

{

    self = [superinit];

    if (self) {

        _observer = observer;

        _key = key;

        _block = block;

    }

    returnself;

}

@end


@implementation NSObject (ZZXKVO)


#pragma mark - Helpers

/*

 通过setter的函数名,获取属性的名字,转换时要去掉“set”,":",并小写名字的第一个字母

 */

static NSString * getterForSetter(NSString *setter)

{

    if (setter.length <=0 || ![setterhasPrefix:@"set"] || ![setterhasSuffix:@":"]) {

        returnnil;

    }

    NSRange range =NSMakeRange(3, setter.length -4);

    NSString *key = [settersubstringWithRange:range];

    

    // lower case the first letter

    NSString *firstLetter = [[keysubstringToIndex:1]lowercaseString];

    key = [key stringByReplacingCharactersInRange:NSMakeRange(0,1)

                                       withString:firstLetter];

    return key;


}


/*

 通过属性名,获取setter方法名,setter方法第一个字母大写

 */

static NSString * setterForGetter(NSString *getter)

{

    if (getter.length <=0) {

        returnnil;

    }

    

    // upper case the first letter

    NSString *firstLetter = [[gettersubstringToIndex:1]uppercaseString];

    NSString *remainingLetters = [gettersubstringFromIndex:1];

    

    // add 'set' at the begining and ':' at the end

    NSString *setter = [NSStringstringWithFormat:@"set%@%@:", firstLetter, remainingLetters];

    

    return setter;


}


#pragma mark - Overridden Methods

/*

 新建类自己的setter方法

 */

static void kvo_setter(idself,SEL_cmd,id newValue)

{

    NSString *setterName =NSStringFromSelector(_cmd);

    NSString *getterName =getterForSetter(setterName);

    if (!getterName) {

        NSString *reason = [NSStringstringWithFormat:@"Object %@ does not have setter %@",self, setterName];

        @throw [NSExceptionexceptionWithName:NSInvalidArgumentException

                                       reason:reason

                                     userInfo:nil];

        return;

    }

    id oldValue = [selfvalueForKey:getterName];

    structobjc_super superclazz = {

        .receiver =self,

        .super_class =class_getSuperclass(object_getClass(self))

    };

    // cast our pointer so the compiler won't complain

    

    void (*objc_msgSendSuperCasted)(void *,SEL,id) = (void *)objc_msgSendSuper;

    

    // call super's setter, which is original class's setter method

    //给父类发送setter,通过断点可以看到执行新执行的是新建类的setter然后是父类的setter

    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);

    

    // look up observers and call the blocks

    //添加数组用于保存各个添加进来的观察者

    NSMutableArray *observers =objc_getAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers));

    for (ZZXObservationInfo *eachin observers) {

        if ([each.keyisEqualToString:getterName]) {

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

                each.block(self, getterName, oldValue, newValue);

            });

        }

    }


}


static Class kvo_class(idself,SEL_cmd)

{

    returnclass_getSuperclass(object_getClass(self));

}


- (void)ZZX_addObserver:(NSObject *)observer forkey:(NSString *)key withBlock:(ZZXObservingBlock)block

{

    // Step 1: Throw exception if its class or superclasses doesn't implement the setter

    SEL setterSelector =NSSelectorFromString(setterForGetter(key));

    Method setterMethod =class_getInstanceMethod([selfclass], setterSelector);

    //如果没有setter,比如属性是只读

    if (!setterMethod) {

        NSString *reason = [NSStringstringWithFormat:@"Object %@ does not have a setter for key %@",self, key];

        @throw [NSExceptionexceptionWithName:NSInvalidArgumentException

                                       reason:reason

                                     userInfo:nil];

        return;

    }

    Class clazz = object_getClass(self);

    NSString *clazzName =NSStringFromClass(clazz);

    

    // if not an KVO class yet

    //第一次添加观察者时,还没有创建该类,第二次添加观察者时就不用再创建了

    if (![clazzNamehasPrefix:ZZXKVOClassPreFix]) {

        clazz = [selfmakeKvoClassWithOriginalClassName:clazzName];

        //设置self类型为新建的子类,这样设置属性的值时就会调用新建的子类的setter

        object_setClass(self, clazz);

    }

    

    // add our kvo setter if this class (not superclasses) doesn't implement the setter?

    if (![selfhasSelector:setterSelector]) {

        constchar *types =method_getTypeEncoding(setterMethod);

        class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);

    }

    //开始回调value给各个观察者

    ZZXObservationInfo *info = [[ZZXObservationInfoalloc]initWithObserver:observerKey:keyblock:block];

    NSMutableArray *observers =objc_getAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers));

    if (!observers) {

        observers = [NSMutableArrayarray];

        objc_setAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers), observers,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    [observers addObject:info];

}

- (void)ZZX_removeObserver:(NSObject *)observer forKey:(NSString *)key

{

    NSMutableArray* observers =objc_getAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers));

    

    ZZXObservationInfo *infoToRemove;

    for (ZZXObservationInfo* infoin observers) {

        if (info.observer == observer && [info.keyisEqual:key]) {

            infoToRemove = info;

            break;

        }

    }

    

    [observers removeObject:infoToRemove];


}


- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName

{

    //originalClazzName父类名字   kvoClazzName子类名字

    NSString *kvoClazzName = [ZZXKVOClassPreFixstringByAppendingString:originalClazzName];

    Class clazz = NSClassFromString(kvoClazzName);

    if (clazz) {

        return clazz;

    }

    // class doesn't exist yet, make it

    Class originalClazz = object_getClass(self);

    //新建类

    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String,0);

    

    // grab class method's signature so we can borrow it

    Method clazzMethod =class_getInstanceMethod(originalClazz,@selector(class));

    constchar *types =method_getTypeEncoding(clazzMethod);

    class_addMethod(kvoClazz,@selector(class), (IMP)kvo_class, types);

    

    //这个一定要在新建方法之后再执行,执行之后,类不能再添加方法了

    objc_registerClassPair(kvoClazz);

    

    return kvoClazz;

}


- (BOOL)hasSelector:(SEL)selector

{

    Class clazz = object_getClass(self);

    unsignedint methodCount =0;

    Method* methodList =class_copyMethodList(clazz, &methodCount);

    for (unsignedint i =0; i < methodCount; i++) {

        SEL thisSelector =method_getName(methodList[i]);

        if (thisSelector == selector) {

            free(methodList);

            returnYES;

        }

    }

    

    free(methodList);

    returnNO;


}


@end



3.新建对象测试效果

#import <Foundation/Foundation.h>

#import "NSObject+ZZXKVO.h"


@interface zzxKvoTest : NSObject

@property (nonatomic,strong)NSString* name;

+ (id)getInstance;

@end


@implementation zzxKvoTest

+ (id)getInstance

{

    staticzzxKvoTest *sharedManager;

    staticdispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        sharedManager = [[zzxKvoTestalloc]init];

    });

    return sharedManager;

}


- (void)setName:(NSString *)name

{

    NSLog(@"hehe");

}

@end


测试部分

[[zzxKvoTestgetInstance]ZZX_addObserver:selfforkey:@"name"withBlock:^(id observedObject,NSString *observedKey,id oldValue, id newValue) {

        

 }];


完成,有不清楚的地方自己运行代码,断点跟踪,注意self的类型变化。





0 0
原创粉丝点击