Objective-C Runtime

来源:互联网 发布:室内分布设计软件 编辑:程序博客网 时间:2024/06/16 21:16

由消息发送开始:
[receiver message]在编译的时候会被转化为objc_msgSend(receiver, selector)objc_msgSend的签名是:objc_msgSend(id self, SEL op, …)

对象模型

id是什么?

typedef struct objc_object *id;struct objc_object{ Class isa };typedef struct objc_class *Class;

即id是一个objc_object的指针,这个objc_object结构中有一个objc_class指针。

id -> objc_object { -> objc_class}

objc_class又是啥?

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

objc_class是描述类的类,叫做类对象。其中isa字段是一个递归定义,指向一个描述这个类对象的类,叫做元类对象。

  • 元类对象的isa指向root class的元类对象,root class可以认为是NSObject。
  • root class的元类对象的isa指向自己。
  • isa的根节点就是root class的元类对象。

super_class还是一个Class,这里有几种情况:

  • 如果这个类对象描述的是一个自定义的类,那么这个类对象的super_class指针指向的是这 个自定义类的基类的类对象;
  • 如果这个类对象描述的是root class,那么super_class指向nil;
  • 如果这是一个元类对象,super_class指针指向的是它描述的类对象对所描述的类的基类的元类对象。
  • 如果这是一个root class的元类对象,super_class指向的root class的类对象。
  • super_class的根节点是nil。

isa和super_class的关系可以用下图表示:
这里写图片描述

name字段就是描述的类的名字。
version是类的版本号,暂时不知道有啥用,应该是运行时自己用。
info类的信息,暂时不知道有啥用。
instance_size 类的大小。
ivars用于保存类对象所描述的类的实例变量的描述。objc_ivar_list这个结构不展开了,里边保存了变量的个数和一个数组,数组中的每一个元素就是一个实例变量的描述。
描述实例变量的结构叫做objc_ivartypedef struct objc_ivar *Ivar:

struct objc_method {    SEL method_name    char *method_types    IMP method_imp}

第一个字段是SEL,是方法选择器,这就解释了如何从一个对象中通过方法选择器找到方法。
第二个字段保存了方法的参数类型和返回值,
第三个字段是方法的函数指针,指向真正的代码地址

cache主要的作用是将找到的方法地址缓存起来,提高效率。

objc_protocol_list保存了所描述对象遵从的协议的类对象。

消息机制

对象模型了解到这里,接下来可以表述下消息发送的路由:

  • 检查接受者是不是nil,如果是,返回nil。
  • 从接受者的类对象的cache中查找selector,找到就执行。
  • 找接受者的类对象的methodLists.应该是比较SEL。
  • 找接受者的基类的类对象的methodLists,直到NSObject的类对象。
  • 进入动态方法解析。

可以使用NSObject的methodForSelector方法可以找到Selector的函数指针,直接调用。

动态方法解析

首先如何构造动态方法解析?如果向一个实例发送它没有实现的方法,编译器就不会通过,是不能运行的,也就无法触发动态方法解析。
一种方法是将实例赋值给id,然后在id指向的对象上调用。
另外一种是只声明实例方法而不提供实现。动态属性@dynamic就是这种类型。

NSObject的类方法resolveInstanceMethodresolveClassMethod会在上文中的第5步被调用,这是一个提供动态方法的时机。如果在这里给类动态的添加了方法来响应这个selector,返回YES,否则返回NO。
responseToSelector也会触发resolveInstanceMethodresolveClassMethod

如果resolveInstanceMethod或者resolveClassMethod返回了NO。则进入消息转发的环节。

消息转发环节有两个钩子,第一个是forwardingTargetForSelector,可以将这个selector发给其他对象。第二个是forwardInvokation,可以调用其他对象的其他方法,而不仅仅限于这个selector

最后都没有机会处理消息,程序Crash。

Category

先说一个category和extension的区别,因为extension看起来就是一个没有名字的category,实际上完全不是那么回事。extentsion是编译期决议的,因此必须有源码,所以可以添加实例变量。category是运行时决议的,不需要有源码。所以也不能添加实例变量。
category是一个category_t的结构体。

typedef struct category_t {    const char *name;    classref_t *cls;    struct method_list_t *instanceMethods;    struct method_list_t *classMethods;    struct protocol_list _t *protocols;    struct property_list_t *instanceProperties;}category_t;

category的cls字段建立起了和对应的Class的联系。编译期间并不对原来的类做任何改变。在运行时,runtime根据cls找到对应的类对象,将category的实例方法加载到类对象的实例方法列表中,把category的类方法加载到元类对象上。

这里有两个问题:
1. category的方法是在什么时候加载到类中去的?
2. 如果不引入category头文件,category的方法还会加载么

这两个问题用同一个答案能解释,category是在类加载的时候就加载了,所以无论是否引入了Category头文件,Category中相应的方法都会被添加进主类。可以通过-performSelector等方式对Category中的方法进行调用.

Category中的方法被整合到类对象中以后,和主类中的方法没有区别。如果有重复的方法,category中的方法会在前边。这样很容易导致难以调试的bug,可以通过在写category的时候,给所有方法加上自己的前缀来解决这个问题。

那么这里有一个偏门的问题,我们常常在category中写一个+load方法,用于在类的初始化期间做一些事情,如果method swizzling,既然category中的方法在主类的方法前边,那么在category中的+load方法和主类中的+load为什么会分别执行?

答案是runtime对+load的方法调用是直接使用函数指针,而不是通过方法列表查找。runtime会使用指针分别调用主类和category的+load方法。

+load & +initialize

+load是类被加载的时候就会被调用,+initialize是类或者子类第一次收到消息的时候被调用。可以将一些可以延迟加载的代码放到+initialize中优化启动速度。

+load只会被调用一次,并且,基类的+load会在子类的+load方法之前调用,子类的+load会在category的+load方法之前调用。+load方法不走方法列表,直接调用函数指针,所以子类的实现也不能覆盖父类的实现。

+initialize不能保证只被加载一次,子类如果实现+initialize,就会调用父类的,这是因为+initialize就是一个普通的消息发送,会走方法列表。所以一个类的+initialize函数应该是有dispatch_once来保证代码只执行一次。

动态创建类

  1. 首先要查一下这个类名是不是已经存在了:objc_getClass(className);
  2. 如果不存在才继续创建:objc_allocteClassPair(superClass, className, 0);
  3. 添加成员变量:class_addIvar(kClass, “expression”, alignment,“”),最后一个“”是typeEncoding表示方式。
  4. 添加方法:class_addMethod(kClass, @selector(setExpression:),(IMP)setExpression, “v@:@“)
  5. 注册类到运行时:objc_registerClassPair(kClass)
  6. 实例化:id instance = [[kClass alloc] init];
  7. 给变量赋值:object_setInstanceVariable(instance, “expression”, “1+1”);
  8. 调用函数:[instance performSelector:@selector(getExpression)];

给变量赋值和调用函的形式和在编译时定义的类是不同的,是为了绕过编译器检查,如果使用[instance getExpression]是不能通过编译的。

注意类方法要添加到kClass的元类中。kClass = [kClass class];

objc_registerClassPair之后不能再添加变量。

原创粉丝点击