iOS-自定义KVO

作者: linbj | 来源:发表于2017-12-22 11:56 被阅读199次
    image.png

    当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制

    如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

    每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
    键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
    补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类”

    #import <Foundation/Foundation.h>
    
    typedef void(^PGObservingBlock)(id observedObject, NSString *observedKey, id oldValue, id newValue);
    
    @interface NSObject (KVO)
    
    /**
     手动实现kvo并添加block
    
     @param observer 观察者
     @param key key
     @param block block
     */
    - (void)PG_addObserver:(NSObject *)observer
                    forKey:(NSString *)key
                 withBlock:(PGObservingBlock)block;
    
    - (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key;
    
    @end
    
    #import "NSObject+KVO.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
    
    NSString *const kPGKVOClassPrefix = @"PGKVOClassPrefix_";
    NSString *const kPGKVOAssociatedObservers = @"PGKVOAssociatedObservers";
    
    
    #pragma mark - PGObservationInfo
    @interface PGObservationInfo : NSObject
    
    @property (nonatomic, weak) NSObject *observer;
    @property (nonatomic, copy) NSString *key;
    @property (nonatomic, copy) PGObservingBlock block;
    
    @end
    
    @implementation PGObservationInfo
    
    - (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(PGObservingBlock)block
    {
        self = [super init];
        if (self) {
            _observer = observer;
            _key = key;
            _block = block;
        }
        return self;
    }
    
    @end
    
    
    #pragma mark - Debug Help Methods
    static NSArray *ClassMethodNames(Class c)
    {
        NSMutableArray *array = [NSMutableArray array];
        
        unsigned int methodCount = 0;
        Method *methodList = class_copyMethodList(c, &methodCount);
        unsigned int i;
        for(i = 0; i < methodCount; i++) {
            [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
        }
        free(methodList);
        
        return array;
    }
    
    
    static void PrintDescription(NSString *name, id obj)
    {
        NSString *str = [NSString stringWithFormat:
                         @"%@: %@\n\tNSObject class %s\n\tRuntime class %s\n\timplements methods <%@>\n\n",
                         name,
                         obj,
                         class_getName([obj class]),
                         class_getName(object_getClass(obj)),
                         [ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
        printf("%s\n", [str UTF8String]);
    }
    
    
    #pragma mark - Helpers
    static NSString * getterForSetter(NSString *setter)
    {
        if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
            return nil;
        }
        
        // remove 'set' at the begining and ':' at the end
        NSRange range = NSMakeRange(3, setter.length - 4);
        NSString *key = [setter substringWithRange:range];
        
        // lower case the first letter
        NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
        key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                           withString:firstLetter];
        
        return key;
    }
    
    
    /**
     给传入的getter 第一个字符变大写,前后分别拼接set 和 :变成一个setStr:形式
    
     @param getter 传入字符串
     @return 修改后的字符串
     */
    static NSString * setterForGetter(NSString *getter)
    {
        if (getter.length <= 0) {
            return nil;
        }
        
        // upper case the first letter
        NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
        NSString *remainingLetters = [getter substringFromIndex:1];
        
        // add 'set' at the begining and ':' at the end
        NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, remainingLetters];
        
        return setter;
    }
    
    
    #pragma mark - Overridden Methods
    // 重写kvo类的setter方法,当调用setter方法的时候触发block
    static void kvo_setter(id self, SEL _cmd, id newValue)
    {
        NSString *setterName = NSStringFromSelector(_cmd);
        NSString *getterName = getterForSetter(setterName);
        
        if (!getterName) {
            NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
            @throw [NSException exceptionWithName:NSInvalidArgumentException
                                           reason:reason
                                         userInfo:nil];
            return;
        }
        
        // 获取gettername对应的老的值
        id oldValue = [self valueForKey:getterName];
        
        struct objc_super superclazz = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self))
        };
        
        // cast our pointer so the compiler won't complain
    //    objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
        void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
        
        // call super's setter, which is original class's setter method
        // 派生类让类改变新值
        objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
        
        // look up observers and call the blocks
        // 从数组中找到对应的观察者调用block
        NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
        for (PGObservationInfo *each in observers) {
            if ([each.key isEqualToString:getterName]) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    each.block(self, getterName, oldValue, newValue);
                });
            }
        }
    }
    
    
    static Class kvo_class(id self, SEL _cmd)
    {
        return class_getSuperclass(object_getClass(self));
    }
    
    
    #pragma mark - KVO Category
    @implementation NSObject (KVO)
    
    - (void)PG_addObserver:(NSObject *)observer
                    forKey:(NSString *)key
                 withBlock:(PGObservingBlock)block
    {
        // 输入的key转换为 setKey: 变成SEL
        SEL setterSelector = NSSelectorFromString(setterForGetter(key));
        
        // 当前类是否实现了key 的setter方法
        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并且转为string类型
        Class clazz = object_getClass(self);
        NSString *clazzName = NSStringFromClass(clazz);
        
        // 如果class不是修改后的kvo类的话去创建一个kvo类(类名以PGKVOClassPrefix_为前缀)
        // if not an KVO class yet
        if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
            clazz = [self makeKvoClassWithOriginalClassName:clazzName];
            object_setClass(self, clazz);
        }
        
        // add our kvo setter if this class (not superclasses) doesn't implement the setter?
        // 判断类是否包含setkey: 这个方法
        if (![self hasSelector:setterSelector]) {
            // 不包含则手动添加一个method
            const char *types = method_getTypeEncoding(setterMethod);
            
            //  * Adds a new method to a class with a given name and implementation.
            // 给本类添加一个setter方法
            class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
        }
        
        // 添加一个观察者类并且将observer  key   block 给它。
        PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
        // 获取类的所有的观察者放入数组中
        NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
        if (!observers) {
            // 创建一个新的观察者数组
            observers = [NSMutableArray array];
            objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        // 添加观察者
        [observers addObject:info];
    }
    
    
    - (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key
    {
        NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
        
        PGObservationInfo *infoToRemove;
        for (PGObservationInfo* info in observers) {
            if (info.observer == observer && [info.key isEqual:key]) {
                infoToRemove = info;
                break;
            }
        }
        
        [observers removeObject:infoToRemove];
    }
    
    
    /**
     传入classname,生成一个以PGKVOClassPrefix_开头的派生类
    
     @param originalClazzName name
     @return 派生类的class
     */
    - (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
    {
        NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
        Class clazz = NSClassFromString(kvoClazzName);
        
        if (clazz) {
            return clazz;
        }
        
        // class doesn't exist yet, make it
        // 创建一个派生类
        Class originalClazz = object_getClass(self);
        
        // 创建一个派生与originalclass的新类
        Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
        
        // grab class method's signature so we can borrow it
        // 获取originalClazz 的method并复制给派生类
        Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
        const char *types = method_getTypeEncoding(clazzMethod);
        class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
        
        objc_registerClassPair(kvoClazz);
        
        return kvoClazz;
    }
    
    
    /**
     判断当前类是否包含selector
    
     @param selector selector
     @return bool
     */
    - (BOOL)hasSelector:(SEL)selector
    {
        Class clazz = object_getClass(self);
        unsigned int methodCount = 0;
        Method* methodList = class_copyMethodList(clazz, &methodCount);
        for (unsigned int i = 0; i < methodCount; i++) {
            SEL thisSelector = method_getName(methodList[i]);
            if (thisSelector == selector) {
                free(methodList);
                return YES;
            }
        }
        
        free(methodList);
        return NO;
    }
    
    
    @end
    

    参考github:自定义kvo

    用到的部分runtime方法如下

    //  根据传入的class返回它的instance 方法 
    // class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
        Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    
    // 通过传入的超类创建一个新的派生类
    // objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                           size_t extraBytes) 
        Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    
    // 获取originalClazz 的method并复制给派生类
        Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
        const char *types = method_getTypeEncoding(clazzMethod);
        class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    
    
    // 注册一个class
    // objc_registerClassPair(Class _Nonnull cls) 
        objc_registerClassPair(kvoClazz);
    
    // 返回一个字符串描述一个方法的参数和返回类型。
    // method_getTypeEncoding(Method _Nonnull m) 
       const char *types = method_getTypeEncoding(setterMethod);
    
    
    // 构建一个objc_super的结构体
        struct objc_super superclazz = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self))
        };
      
        
    // 函数签名对不上,声明一个函数指针指向函数实现的指针 强转一下就得了
    // objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
        
        void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;    
    // 派生类让类改变新值
        objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
        
    

    相关文章

      网友评论

        本文标题:iOS-自定义KVO

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