美文网首页iOS学习日记
OC的KVO学习记录(2)

OC的KVO学习记录(2)

作者: 三生之二 | 来源:发表于2018-09-02 17:56 被阅读5次

仿写KVO的实现
KVO原理参考可前一篇OC的KVO学习记录
代码github地址:Sameny仿写KVO

注:关键代码的解释我放到了代码中,如有疑问,欢饮一起探讨

我们需要一些准备代码,以此来获取setter和getter方法,以及存储observer对象

NSString *const kSTKVOClassPrefix = @"STKVOClass_";
NSString *const kSTKVOObservers = @"STKVOObservers";

@interface STObservedPiece : NSObject

@property (nonatomic, weak)     NSObject            *observer;
@property (nonatomic, copy)     NSString            *key;
@property (nonatomic, copy)     STObservingBlock    block;

@end

@implementation STObservedPiece

- (instancetype)initWithObserver:(NSObject *)observer key:(NSString *)key block:(STObservingBlock)block
{
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end
// 构造setter的方法名
static NSString * getSetterSelector(NSString *key) {
    if (key.length <= 0) {
        return nil;
    }
    NSString *firstLetter = [[key substringToIndex:1] uppercaseString];
    NSString *restLetter = [key substringFromIndex:1];
    NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, restLetter];
    return setter;
}

static NSString * getGetterSelector(NSString *setter) {
    if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
        return nil;
    }
   
    NSRange range = NSMakeRange(3, setter.length - 4);
    NSString *key = [setter substringWithRange:range];
    
    NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                       withString:firstLetter];
    return key;
}

根据原理仿写KVO,核心代码如下:
首先构造我们所需的KVO子类

- (Class)createKVOClassWithObserveredClassName:(NSString *)observeredClassName {
    NSString *kvo_class_name = [kSTKVOClassPrefix stringByAppendingString:observeredClassName];
    Class kvo_class = NSClassFromString(kvo_class_name);
    if (kvo_class) {
        return kvo_class;
    }
    // 如果还没有构造过,就开始构造STKVOClass_XXX类
    // 我们使用构造observeredClass类的一个子类,该函数定义如下
    /*
     * Creates a new class and metaclass.
     * OBJC_EXPORT Class _Nullable
     * objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)
     * 该函数是比较直观的,直接返回创建好的类
     */
    Class observeredClass = object_getClass(self);
    kvo_class = objc_allocateClassPair(observeredClass, kvo_class_name.UTF8String, 0);
    
    // 重写class方法实现,使其返回父类的Class
    Method classMethod = class_getInstanceMethod(kvo_class, @selector(class));
    const char * types = method_getTypeEncoding(classMethod);
    class_addMethod(kvo_class, @selector(class), (IMP)kvo_class_imp, types);
    
    // 创建类之后需要register类
    objc_registerClassPair(kvo_class);
    return kvo_class;
}

kvo_class_imp函数实现如下:

static Class kvo_class_imp(id self, SEL _cmd)
{
    return class_getSuperclass(object_getClass(self));
}

其次构造我们的子类的setter方法


// 构造setter方法
static void st_kvo_setter(id self, SEL _cmd, id newValue) {
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getGetterSelector(setterName);
    
    if (!getterName) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }
    id oldValue = [self valueForKey:getterName];
    
    // ** 获取父类,并通过objc_msgSendSuper调用父类的setter方法更新值
    struct objc_super superclass = {
        .receiver = self, // 这个self就是父类的实例,消息的接受者
        .super_class = class_getSuperclass(object_getClass(self))
    };
    // OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    /*
     * These functions must be cast to an appropriate function pointer type
     * before being called.
     * 该函数需要映射到一个函数指针,而且其参数个数也是看需要而定,我们这里有3个参数,一个消息的接受者superclass,所调用的方法setter,参数是newValue
     */
    // 哈哈,这个像定义Block块一样,是不是?
    void(*st_objc_msgSendSuperCasted)(void*, SEL, id) = (void *)objc_msgSendSuper;
    // 调用执行父类setter方法,更新父类被监听对象的值
    st_objc_msgSendSuperCasted(&superclass, _cmd, newValue);
    
    // 执行完数值更新后,就该通知监听的观察者了
    NSMutableArray <STObservedPiece *>*observerPieces = objc_getAssociatedObject(self, (__bridge const void *)(kSTKVOObservers));
    [observerPieces enumerateObjectsUsingBlock:^(STObservedPiece * _Nonnull piece, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([piece.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                piece.block(self, getterName, oldValue, newValue);
            });
        }
    }];
}

接下来就是我们的对外接口

- (void)st_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(STObservingBlock)block {
    SEL setterSelector = NSSelectorFromString(getSetterSelector(key));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        
        return;
    }
    
    Class class = object_getClass(self);
    NSString *className = NSStringFromClass(class);
    if (![className hasPrefix:kSTKVOClassPrefix]) {
        class = [self createKVOClassWithObserveredClassName:className];
        // 更改isa指针,将self的isa指针指向子类,这样当self的setter方法执行的时候,调用的是子类的setter,从而实现了在子类setter中进行通知的功能
        object_setClass(self, class);
    }
    // 判断子类(因为isa指针指向了子类)是否实现了setter方法
    if (![self hasSelector:setterSelector]) {
        const char * types = method_getTypeEncoding(setterMethod);
        class_addMethod(class, setterSelector, (IMP)st_kvo_setter, types);
    }
    
    // 存储通知的内容
    STObservedPiece *piece = [[STObservedPiece alloc] initWithObserver:observer key:key block:block];
    NSMutableArray <STObservedPiece *>*observerPieces = objc_getAssociatedObject(self, (__bridge const void *)(kSTKVOObservers));
    if (!observerPieces) {
        observerPieces = [[NSMutableArray alloc] init];
        objc_setAssociatedObject(self, (__bridge const void *)(kSTKVOObservers), observerPieces, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observerPieces addObject:piece];
}

KVO有addObserver方法,当然也要有removeObserver方法了:

- (void)st_removeObserver:(NSObject *)observer forKey:(NSString *)key {
    NSMutableArray <STObservedPiece *>*observerPieces = objc_getAssociatedObject(self, (__bridge const void *)(kSTKVOObservers));
    if (observerPieces) {
        if (!observer) {
            [observerPieces removeAllObjects]; // 移除所有监听
        }
        else {
            __block NSMutableArray <STObservedPiece *>*removePieces = [[NSMutableArray alloc] init];
            [observerPieces enumerateObjectsUsingBlock:^(STObservedPiece * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                if (obj.observer == observer) {
                    if (key.length > 0) {
                        if ([obj.key isEqualToString:key]) {
                            [removePieces addObject:obj];
                            *stop = YES;
                        }
                    }
                    else {
                        [removePieces addObject:obj];
                    }
                }
            }];
            [observerPieces removeObjectsInArray:removePieces];
        }
        
        // 销毁我们创建的STKVOClass_XXX类,并将self的isa指针重置为原来的类
        if (observerPieces.count == 0) {
            Class kvoClass = object_getClass(self);
            if ([NSStringFromClass(kvoClass) hasPrefix:kSTKVOClassPrefix]) { // 防止多次删除
                Class originalClass = class_getSuperclass(kvoClass);
                object_setClass(self, originalClass);
                objc_disposeClassPair(kvoClass);
            }
        }
    }
}

我们来测试一下结果:


@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMutableArray <NSString *>*nicks;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age {
    Person *person = [[Person alloc] init];
    person.name = name;
    person.age = age;
    return person;
}

- (NSMutableArray<NSString *> *)nicks {
    if (!_nicks) {
        _nicks = [[NSMutableArray alloc] init];
    }
    return _nicks;
}

@end
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.person st_addObserver:self forKey:@"name" withBlock:^(id observedObject, NSString *observedKey, id oldValue, id newValue) {
        NSLog(@"name = %@", newValue);
    }];
    [self.person st_addObserver:self forKey:@"nicks" withBlock:^(id observedObject, NSString *observedKey, id oldValue, id newValue) {
        NSLog(@"nicks = %@", newValue);
        NSLog(@"***********************");
    }];
    
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(personGrown)];
    [self.view addGestureRecognizer:tap];
}

- (void)dealloc {
    [self.person st_removeObserver:self forKey:nil];
}

- (void)personGrown {
    NSLog(@"***********************");
    self.person.age++;
    NSArray *names = @[@"shuzt", @"laoniu", @"atai", @"sameny"];
    self.person.name = names[self.person.age%4];
    [[self.person mutableArrayValueForKey:@"nicks"] addObject:self.person.name];
    if (self.person.age > 33) {
        NSLog(@"remove observers");
        [self.person st_removeObserver:self forKey:nil];
    }
}

- (Person *)person {
    if (!_person) {
        _person = [Person personWithName:@"sameny" age:27];
    }
    return _person;
}

@end

打印结果如下:

2018-09-02 17:53:02.519160+0800 STKVOImplement[19134:6128949] ***********************
2018-09-02 17:53:02.519529+0800 STKVOImplement[19134:6129061] name = shuzt
2018-09-02 17:53:02.520006+0800 STKVOImplement[19134:6129061] nicks = (
    shuzt
)
2018-09-02 17:53:02.520061+0800 STKVOImplement[19134:6129061] ***********************
2018-09-02 17:53:04.221709+0800 STKVOImplement[19134:6128949] ***********************
2018-09-02 17:53:04.222730+0800 STKVOImplement[19134:6129061] name = laoniu
2018-09-02 17:53:04.223088+0800 STKVOImplement[19134:6129061] nicks = (
    shuzt,
    laoniu
)
2018-09-02 17:53:04.223180+0800 STKVOImplement[19134:6129061] ***********************
2018-09-02 17:53:05.605007+0800 STKVOImplement[19134:6128949] ***********************
2018-09-02 17:53:05.605970+0800 STKVOImplement[19134:6129061] name = atai
2018-09-02 17:53:05.606307+0800 STKVOImplement[19134:6129060] nicks = (
    shuzt,
    laoniu,
    atai
)
2018-09-02 17:53:05.606412+0800 STKVOImplement[19134:6129060] ***********************

至此,完成一个阶段,基于对象的KVO监听,基本实现,但是还无法监听基础数据类型的变化,欢迎有想法的前辈和伙伴们的指教,一起探讨。

相关文章

  • OC的KVO学习记录(2)

    仿写KVO的实现KVO原理参考可前一篇OC的KVO学习记录代码github地址:Sameny仿写KVO 注:关键代...

  • OC的KVO学习记录(1)

    KVO :键值观察。当我们需要对某个对象的某个属性进行监听的时候,我们就利用KVO机制,通过属性关键字绑定,进行监...

  • iOS 10.17日记

    swift 学习 1 static和class的区别 2 学习手势的使用 3 swift 中kvo的使用(和oc...

  • Runtime面试题

    1.oc消息转发流程 2.KVO原理KVO是基于runtime实现的,KVO运用了isa-swizzling将两个...

  • OC基础-KVO(2)

    kvo总结问题:iOS用什么方式实现一个KVO?(KVO的本质是什么)答:利用RuntimeAPI动态生成一个子类...

  • swift中KVO和属性观察器

    开篇提醒:OC中的KVO及其KVO的基础知识可参见:深入runtime探究KVO Swift中,原本没有KVO模式...

  • iOS KVO的底层实现原理

    KVO 是 OC 观察者设计模式的一种KVO 的实现依赖于 OC 强大的 RuntimeKVO是Cocoa提供的...

  • OC底层学习笔记(3)-KVO的实现

    注:文章为自己学习李明杰老师的OC底层视频做的随手笔记 什么是KVO? KVO的全称是key-value obse...

  • KVO

    今天和大家讨论一下OC中KVO(KeyValueObserving)键值观察 KVO定义 KVO是iOS开发中的一...

  • iOS知识点小结

    1.KVO1.1> KVO 简介:KVO 是 OC 观察者设计模式的一种实现.KVO 指定一个被观察的对象的属性,...

网友评论

    本文标题:OC的KVO学习记录(2)

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