iOSiOS技术专题首页投稿(暂停使用,暂停投稿)

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

2016-11-15  本文已影响509人  LZTuna


说在前面

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该对象是属于哪个类。在这个阶段被观察对象仍然属于原始类

观察之后

在添加观察者以后:

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

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


#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指针的定义:

在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 //存储引用计数值减一后的结果
(最高有效位)  

上一篇下一篇

猜你喜欢

热点阅读