天天看點

用Runtime實作KVO

一.建立一個繼承自NSObject的類目GXJKVO,在.h檔案中添加兩個方法

//添加觀察者

- (void)addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(void(^)(id observed, NSString *key, id oldValue, id newValue))block;

//删除觀察者

  • (void)removeObserver:(NSObject *)observer forKey:(NSString *)key;

二.在.m中實作這兩個方法

1.首先聲明兩個唯一的key

NSString *const kClassPrefix = @"GXJKVOClassPrefix";

NSString *const kGXJKVOAssociateKey = @“GXJKVOObserverArrayKey";

2.添加一個新類的延展

@interface ObserveInfo : NSObject

@property (strong, nonatomic) id observer;

@property (copy , nonatomic) NSString * key;

@property (copy , nonatomic) void(^observerBlock) (id observerObject, NSString * key, id oldValue, id newValue);

+(id)instanceWithObserver:(id)observer forKey:(NSString *)key block:(void (^)(id, NSString *, id, id))block;

@end

@implementation ObserveInfo

+(id)instanceWithObserver:(id)observer forKey:(NSString *)key block:(void (^)(id, NSString *, id, id))block

{

    ObserveInfo *observerInfo = [[ObserveInfo alloc]init];

    if (observerInfo) {

        observerInfo.observer = observer;

        observerInfo.key = key;

        observerInfo.observerBlock = block;

    }

    return observer;

}

@end

3.實作添加觀察者、删除觀察者方法

//函數 通過key獲得setter方法名

NSString *getSetterName(NSString *key) {

    NSString *firstCharacter = [key substringFromIndex:1];

    return [NSString stringWithFormat:@"set%@%@",[firstCharacter uppercaseString],[key substringFromIndex:1]];

}

//函數 實作通過setter方法名取出key

NSString *getKey(NSString *setName) {

    //去掉冒号

    NSString *preName = [setName substringToIndex:setName.length - 1];

    //去掉set

    NSString * name = [preName substringFromIndex:3];

    //獲得首字母

    NSString * firstCharacter = [name substringToIndex:1];

    //傳回字元串

    return [NSString stringWithFormat:@"%@%@",[firstCharacter lowercaseString],[name substringFromIndex:1]];

}

//生成衍生類方法

- (Class)makeKVOClass:(Class)originClass

{

    NSString *className = NSStringFromClass(originClass);

    NSString *kvoClassName = [NSString stringWithFormat:@"%@%@",kClassPrefix,className];

    Class kvoClass = objc_allocateClassPair(object_getClass(self), kvoClassName.UTF8String, 0);

    objc_registerClassPair(kvoClass);

    return kvoClass;

}

//判斷衍生類是否已經實作目前key所對應的setter方法

- (BOOL)hasSelector:(SEL)aSelector

{

    //擷取方法連結清單并周遊

    unsigned int methodCount = 0;

    //擷取方法連結清單

    Method *methodArray = class_copyMethodList(object_getClass(self), &methodCount);

    //周遊每個方法名

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

        Method m = methodArray[i];

        if (method_getName(m) == aSelector) {

            free(methodArray);

            return YES;

        }

    }

    //否則傳回NO

    free(methodArray);

    return NO;

}

//gxj_kvoSetter完成兩個功能,1通過向父類發消息完成set本來的指派功能,2并行調用觀察者方法

void gxj_kvoSetter(id objc_self, SEL objc_cmd, id newValue) {

    //擷取目前的setter方法名

    NSString *setterName = NSStringFromSelector(objc_cmd);

    //獲得key

    NSString *key = getKey(setterName);

    //獲得oldValue

    id oldValue = [objc_self valueForKey:key];

    //objc_msgSendSuper()函數的參數是父類結構體,需要手動建立

    struct objc_super selfSuper = {

        .receiver = objc_self,

        .super_class = class_getSuperclass(object_getClass(objc_self))

    };

    objc_msgSendSuper(&selfSuper, objc_cmd,newValue);

    //并行回調觀察者block

    NSMutableArray *observeArray = objc_getAssociatedObject(objc_self, (__bridge const void *)kGXJKVOAssociateKey);

    //周遊數組進行回調

    for (ObserveInfo *info in observeArray) {

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            info.observerBlock(objc_self, key, oldValue, newValue);

        });

    }

}

//添加觀察者

- (void)addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(void(^)(id observed, NSString *key, id oldValue, id newValue))block

{

    //判斷能否進行KVO,是否存在此key對應的屬性setter方法

    NSString *setterName = getSetterName(key);

    Method setMethod = class_getInstanceMethod(object_getClass(self), NSSelectorFromString(setterName));

    if (!setMethod) {

        NSLog(@"無法KVO");

        return;

    }

    //是否已經生成衍生類

    NSString *className = NSStringFromClass(object_getClass(self));

    Class kvoClass = object_getClass(self);

    if (![className hasPrefix:kClassPrefix]) {

        //生成目前類的衍生類

        kvoClass = [self makeKVOClass:object_getClass(self)];

        //生成衍生類後,改變目前的類的類辨別,使self變成kvoClass的執行個體

        object_setClass(self, kvoClass);

    }

    //判斷衍生類是否已經實作目前key所對應的setter方法

    if (![self hasSelector:NSSelectorFromString(setterName)]) {

        //若未實作setter方法,則添加我們實作的kvoSetter

    }

    //完成以上,就可以将觀察者添加到關聯數組中

    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void *)kGXJKVOAssociateKey);

    ObserveInfo *observerInfo = [ObserveInfo instanceWithObserver:observer forKey:key block:block];

    if (!observerArray) {

        observerArray = [NSMutableArray array];

        objc_setAssociatedObject(self, (__bridge const void *)(kGXJKVOAssociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    [observerArray addObject:observerInfo];

}

//删除觀察者

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

{

    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void *)kGXJKVOAssociateKey);

    for (ObserveInfo * observerInfo in observerArray) {

        if (observerInfo.observer == observer && [observerInfo.key isEqualToString:key]) {

            [observerArray removeObject:observerInfo];

        }

    }

}

三.在ViewController中調用添加觀察者方法

//建立一個model,添加一個name屬性

Company *p1 = [Company new];

    [p1 addObserver:self forKey:@"name" withBlock:^(id observed, NSString *key, id oldValue, id newValue) {

        NSLog(@"object = %@, key = %@, oldValue = %@, newValue = %@",observed,key,oldValue,newValue);

    }];

    p1.name = @"Baidu";

    p1.name = @"Tencent";

    p1.name = @“Google";

當p1的屬性name被修改時,會提示被修改。

繼續閱讀