用了这么多次KVO,你真的理解了吗?

作者: LZTuna | 来源:发表于2016-11-15 16:56 被阅读509次

    • KVO的实现

    • 窥探isa指针


    说在前面

    KVO作为观察者模式的一种实现,为Cocoa框架中实现Binding的一部分,在ReactiveCocoa框架未出现之前为MVVM模式的实现提供了基础。不过KVO饱受诟病,提供的API不易维护,严重依赖String,不过还好有ReactiveCocoa框架:)

    KVO的实现

    KVO的实现基于Runtime,在文档《Objective-C Runtime Programming Guide》中有这么一句:

    The runtime system acts as a kind of operating system for the Objective-C language

    Runtime为Objective-C扮演了一种操作系统的角色。OC中各种黑魔法均通过强大的Runtime来实现,Runtime让OC这一门上古语言迎来了第二春。KVO自然也得通过Runtime来实现。

    观察之前

    在添加观察者之前对象的isa指针指向了原始类。isa指针用于告诉Runtime该对象是属于哪个类。在这个阶段被观察对象仍然属于原始类

    观察之后

    在添加观察者以后:

    • 系统通过Runtime动态的创建一个中间类,继承自原始类

    • 实现中间类的四个方法

      • class 返回值为原始类

      • setter 用于通知观察者值已经发生改变

      • _isKVOA 私有方法_isKVOA 是用来标示该类是一个KVO 机制声称的类

      • delloc 处理一些收尾工作

    • 将被观察对象的isa指针指向中间类

    至此,被观察对象就神奇的变成了原始类的子类的实例

    下面我们用代码一一进行验证

    
    #import <objc/runtime.h>
    #import <objc/objc.h>
    
    @interface ViewController ()
    @property (nonatomic, strong)Person *person;
    
    @property (nonatomic, assign)IMP originalMethod;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.person = [Person person];
        //纪录原始方法地址
        self.originalMethod = method_getImplementation(class_getInstanceMethod(object_getClass(self.person),@selector(setName:)));
        [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        //打印对象信息
        [self logObjectInfo:(__bridge struct objc_object *)(self.person)];
        //延时改变观察值
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
            self.person.name = @"changeName";
        });
    }
    
    - (void)logObjectInfo:(struct objc_object * )object
    {
    
        Class object_class = [(__bridge id)object class];
    
        NSLog(@"--------------\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nclass:%s\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nisa:%s\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nisa_superClass:%@\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nisa_methods:%@\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nIMP_original_setName:%p\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nIMP_setName:%p\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n----------------",
              class_getName(object_class),
              class_getName(object->isa),
              class_getSuperclass(object->isa),
              [[self classMethodsList:object->isa] componentsJoinedByString:@"   |   "],
              self.originalMethod,
              method_getImplementation(class_getInstanceMethod(object->isa,@selector(setName:))));
    
    }
    
    - (NSArray *)classMethodsList:(Class )class
    {
        NSMutableArray *array = [NSMutableArray array];
        int methodCount = 0;
        Method *methodList = class_copyMethodList(class, &methodCount);
        int i;
        for(i = 0; i < methodCount; i++) {
            [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
        }
    
        free(methodList);
    
        return array;
    }
    
    

    struct objc_object * 其实是id的全称,打开 objc/object.h 头文件可以看到一行定义

    typedef struct objc_object *id;
    

    打印结果:

     2016-10-27 21:08:34.327 KVO-深入理解[4815:1764521]
    --------------------------------------------------------------------------
    class:                  Person                                            |
    --------------------------------------------------------------------------
    isa:                    NSKVONotifying_Person                             |
    --------------------------------------------------------------------------
    isa_superClass:         Person                                            |
    --------------------------------------------------------------------------
    isa_methods:            setName:   |   class   |   dealloc   |   _isKVOA  |
    --------------------------------------------------------------------------
    IMP_original_setName:   0x10b44f7a0                                       |
    --------------------------------------------------------------------------
    IMP_setName:            0x10b5524ed                                       |
    --------------------------------------------------------------------------
    

    苹果为了最大限度的还原被观察对象可谓是用心良苦啊,重写了class方法以后返回的是Person类,只有通过isa指针才能获取到真实类NSKVONotifying_Person.通过对比两个setName方法的地址,可以判断setName方法被重写了。

    窥探isa指针

    神奇的isa指针可以起到改变对象身份的作用,我们来扒扒isa指针的定义:

    • isa 是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身;

    在runtime头文件中可以找到对class 以及 object 的定义

    
    //苹果限制了这一部分的使用,如果是Objective-C 2.0编译将不通过
    
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    

    对象的本质其实就是一个结构体,类也是一个结构体,可以看到类和对象都有一枚isa指针。不过在arm64架构的设备中,object的isa已经不再是一个指针了!这是因为64位架构中,runtime为了节约空间,会将其余的空间用于储存析构状态,引用计数,被其他 weak 变量引用情况,
    下面列出了一些isa 的结构体定义

    (最低有效位)
    1 bit indexed //0代表原始isa,1代表非指针isa
    1 bit has_assoc //对象是否拥有关联对象,如果没有对象可以析构的更快
    1 bit has_cxx_dtor //对象是否拥有c++或者ARC析构函数,如果没有对象可以析构的更快
    30   bits shiftcls // 类指针
    9 bits magic //固定值为 0xd2,用于在调试时分辨真实对象是否未初始化
    1 bit weakly_referenced //对象是否有过 weak 对象,如果没有,则析构时更快
    1 bit deallocating //对象是否正在析构
    1 bit has_sidetable_rc //对象的引用计数值是否过大无法存储在 isa 中
    19   bits extra_rc //存储引用计数值减一后的结果
    (最高有效位)  
    
    

    相关文章

      网友评论

      本文标题:用了这么多次KVO,你真的理解了吗?

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