iOS/Swift/Objc

KVO & KVC & Runtime

2018-07-27  本文已影响15人  emmet7life

一、语言类型了解

1. 动态语言
  1. 定义:动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

    主要动态语言:Object-CC#JavaScriptPHPPythonErlangObject-C语言的动态语言特性得益于它的Runtime机制,Runtime不是语言特性,而是运行时环境。

2. 静态语言
  1. 定义:与动态语言相对应的,运行时结构不可变的语言就是静态语言。如JavaCC++

    C语言调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。

3. 三个容易混淆的名词
  1. Dynamic Programming Language (动态语言或动态编程语言)

  2. Dynamically Typed Language (动态类型语言)

  3. Statically Typed Language (静态类型语言)

    • 动态类型语言:指在运行期间才去做数据类型检查的语言,说的是数据类型。
    • 静态类型语言:在编译期间(或运行之前)确定的,编写代码的时候要明确确定变量的数据类型。
    • 动态语言:说的是运行时改变结构,说的是代码结构。
4. 语言类型参考资料
  1. 语言类型
  2. Swift是什么类型的语言?
  3. 编译型语言、解释型语言、静态类型语言、动态类型语言概念与区别

二、Runtime 源码

1. 为什么需要了解runtime?
2. runtime是什么?
3. runtime能干些什么事?
4. 源码解析

OC类的实例对象最终被翻译成objc_object结构体:

//类
typedef struct objc_class *Class; 
//对象
typedef struct objc_object {
    Class isa; 
} *id;

每个Objective-C对象都有一个隐藏的数据结构,这个数据结构是Objective-C对象的第一个成员变量,它就是isa指针。
这个isa到底是什么呢?官方解释如下:

一个对象(Object)的isa指向了这个对象的类(Class),而这个对象的类(Class)的isa指向了metaclass。这样我们就可以找到静态方法和变量了。

Objective-C的运行时是动态的,它能让你在运行时为类添加方法或者去除方法以及使用反射。这在其它语言是不多见的。

objc_class 结构体:

struct objc_class {
    Class isa;
    Class super_class ;
    const char *name ;
    long version ;
    long info ;
    long instance_size ;
    struct objc_ivar_list *ivars ;
    struct objc_method_list **methodLists ;
    struct objc_cache *cache ;
    struct objc_protocol_list *protocols ;
} OBJC2_UNAVAILABLE;

struct objc_method_list {  
    struct objc_method_list *obsolete;
    int method_count;

#ifdef __LP64__
    int space;
#endif

    /* variable length structure */
    struct objc_method method_list[1];
};

struct objc_method {  
    SEL method_name;        /*函数名*/
    char *method_types;     /*表示函数类型的字符串*/
    IMP method_imp;         /*函数的实现IMP*/
};
简单介绍一下各个结构体的成员变量
  1. name: 一个 C 字符串,指示类的名称。

    我们可以在运行期,通过这个名称查找到该类(通过:id objc_getClass(const char *aClassName))或该类的 metaclass(id objc_getMetaClass(const char *aClassName));

  2. version: 类的版本信息,默认初始化为 0。

  3. info: 供运行期使用的一些位标识。

    • CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含实例方法和变量;
    • CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
    • CLS_INITIALIZED (0x4L) 表示该类已经被运行期初始化了,这个标识位只被 objc_addClass 所设置;
    • CLS_POSING (0x8L) 表示该类被 pose 成其他的类;(poseclass 在 ObjC 2.0 中被废弃了);
    • CLS_MAPPED (0x10L) 为 ObjC 运行期所使用
    • CLS_FLUSH_CACHE (0x20L) 为 ObjC 运行期所使用
    • CLS_GROW_CACHE (0x40L) 为 ObjC 运行期所使用
    • CLS_NEED_BIND (0x80L) 为 ObjC 运行期所使用
    • CLS_METHOD_ARRAY (0x100L) 该标志位指示 methodlists 是指向一个 objc_method_list 还是 一个包含 objc_method_list 指针的数组;
  4. instance_size:该类的实例变量大小(包括从父类继承下来的实例变量);

  5. ivars: 指向 objc_ivar_list 的指针,存储每个实例变量的内存地址,如果该类没有任何实例变量则为NULL;

  6. methodLists: 与 info 的一些标志位有关,CLS_METHOD_ARRAY 标识位决定其指向的东西(是指 向单个 objc_method_list 还是一个 objc_method_list 指针数组),如果 info 设置了 CLS_CLASS 则 objc_method_list 存储实例方法,如果设置的是 CLS_META 则存储类方法;

  7. cache: 指向 objc_cache 的指针,用来缓存最近使用的方法,以提高查找方法的效率,加快运行速度;

    举 objc_msgSend(obj, foo) 这个例子来说:

    1. 首先,通过 obj 的 isa 指针找到它的 class ;

    2. 在 class 的 method list 找 foo ;

    3. 如果 class 中没到 foo,继续往它的 superclass 中找 ;

    4. 一旦找到 foo 这个函数,就去执行它的实现IMP .

    但这种实现有个问题,效率低。但一个 class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次 objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是 objc_class 中另一个重要成员 objc_cache 做的事情 - 再找到 foo 之后,把 foo 的 method_name 作为 key ,method_imp 作为 value 给存起来。当再次收到 foo 消息的时候,可以直接在 cache 里找到,避免去遍历 objc_method_list.

  8. protocols: 指向 objc_protocol_list 的指针,存储该类声明要遵守的正式协议。

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。
图示1
图示2
class 与 metaclass的区别
  1. class 是 instance object 的类类型。
  2. 当我们向实例对象发送消息(实例方法)时,我们在该实例对象的 class 结构的 methodlists 中去查找响应的函数,如果没找到匹配的响应函数则在该 class 的父类中的 methodlists 去查找。
  3. metaclass 是 class object 的类类型。
  4. 当我们向类对象发送消息(类方法)时,我们在该类对象的 metaclass 结构的 methodlists 中去查找响应的函数,如果没有找到匹配的响应函数则在该metaclass 的父类中的 methodlists 去查找。
规则如下

规则一: 类的实例对象的 isa 指向该类;该类的 isa 指向该类的 metaclass;
规则二: 类的 super_class指向其父类,如果该类为根类则值为 NULL;
规则三: metaclass 的 isa 指向根 metaclass,如果该 metaclass是根 metaclass 则指向自身;
规则四: metaclass 的 super_class 指向父 metaclass,如果该metaclass 是根 metaclass 则指向 该 metaclass 对应的类;

一句话总结

ObjC 为每个类的定义生成两个 objc_class ,一个即普通的 class,另一个即 metaclass。我们可以在 运行期创建这两个 objc_class 数据结构,然后使用 objc_addClass将 class注册到运行时系统中,以此实现动态地创建一个新的类。

简单了解runtime相关函数
  1. 增加

    • 增加函数:class_addMethod
    • 增加实例变量:class_addIvar
    • 增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
    • 增加Protocol:class_addProtocol
  2. 获取

    • 获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ...
    • 获取属性列表及每个属性的信息:class_copyPropertyList property_getName
    • 获取类本身的信息,如类名等:class_getName class_getInstanceSize
    • 获取变量列表及变量信息:class_copyIvarList
  3. 替换

    • 将实例替换成另一个类:object_setClass
    • 替换类方法的定义:class_replaceMethod
  4. 其他常用方法

    • 交换两个方法的实现: method_exchangeImplementations
    • 设置一个方法的实现:method_setImplementation
5. Runtime 参考资料
  1. 对象内存结构中的 isa 指针是用来做什么的?
  2. 详解Objective-C的isa与meta-class
  3. iOS 开发 深入浅出Runtime运行时之类与对象的结构
  4. Objective-C Runtime之消息传递流程
  5. Method Swizzling 和 AOP 实践
  6. Objective-C内存布局

三、KVO & KVC

1. KVO (Key-Value Observing)
  1. 概念

KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。

有意思的是,你不需要给被观察的对象添加任何额外代码,就能使用 KVO 。这是怎么做到的?

  1. KVO 实现机制

KVO 的实现也依赖于 Objective-C 强大的 Runtime 。

简单概述下 KVO 的实现:

2. KVC 参考资料
  1. 如何自己动手实现 KVO
3. KVC (Key-Value Coding)
  1. KVC全称是Key Value Coding,定义在NSKeyValueCoding.h文件中,是一个非正式协议。
  2. KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。
协议图示
4. KVC 参考资料
  1. KVC原理剖析

💖 本期技术站点推荐 💖

  1. NSHipster.cn 中文 / NSHipster.com 英文

NSHipster 关注被忽略的 Objective-C、Swift 和 Cocoa 特性。每周更新。

  1. Glow技术团队工程师-顾 鹏
上一篇下一篇

猜你喜欢

热点阅读