Runtime 探析

Runtime 简介

因为Objective-C是一门动态语言,这意味着它不仅需要一个编译器,也还需要一个运行时的系统来动态的创建类和对象、进行消息转递和转发。这就是Objective-C Runtime 系统存在的意义,它是整个Objc 运行框架的一块基石。


Runtime 基础数据结构

在我们调用方法是系统会自动为我们转换成 Objc_magSend:函数

id objc_msgSend ( id self,SEL op,...)

id

objc_msgSend 第一个参数的类型为 id ,它是一个指向类实例的指针:

typedef struct objc_object *id;

objc_object 是什么? 参考objc-private.h部分源码:

struct objc_object {
private:
isa_t isa;
public:

// ISA() assumes this is NOT a tagged pointer object
Class ISA();

// getIsa() allows this to be a tagged pointer object
Class getIsa();
... 此处省略其他方法声明
}

objc_object 结构体包含 一个 isa 指针类型,类型为isa_t的联合体。我们根据 isa 就可以找到对象所属的类。因为isa_t 使用 union 实现,所以可能表示多种形态,即可以当成多种形态 即可以当成指针 也可以存储标志位。
PS: isa 指针不总是指向实例对象的所属类,不能依靠它来确定类型 而是应该用 class 方法来确定实例对象的类。KVO的实现机制就是将被观察者对象的 isa 指针指向了一个中间类而不是真实的类,这种叫做 isa—swizzling 的技术。KVO

SEL

Objc_magSend 函数第二个参数类型为 SEL , 它是 selector 在objc 中表示类型。selector 是方法选择器,可以理解区分方法的ID,而ID的数据结构SEL:

typedef struct objc_selector *SEL

@selector( ) 基本可以等同于C语言的函数指针,只不过C语言中可以把函数名直接赋给一个函数指针,而Objc 的类不能直接应用函数指针。这样只能做一个@selector语法来取 它的结果是一个SEL类型。

Class

Class 是指向 objc_class 结构体的指针:

typedef struct objc_class *Class;

objc_class 包含很多方法,主要都为围绕它的几个成员:

struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;             // formerly cache pointer and vtable
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() { 
    return bits.data();
}
... 省略其他方法
}

objc_class 继承于 objc_object,也就是说一个Objc 类本身同时也是一个对象,为了处理类和对象的关系,runtime库创建了一种叫做元类(Meta Class),类对象所属的类型就叫做元类,它用来表述对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象。而每个类仅有一个与之相关的元类。当你发出一个类似 [NSObject alloc] 的消息时,实际上是把这个消息发送给一个类对象,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。 所有的元类最终指向根元类为其超类。所有的元类方法列表都能够响应消息的类方法。当 [NSObject alloc] 这条消息发送给对象的时候 objc_Send() 会去它的元类里面去查找能够响应的消息的方法。

class-diagram.jpg

上图实线是 superclass 的指针 ,虚线是isa 指针。
1.root class 其实就是NSObject NSObject 是没有超类的,所以 root class(class)和superrclass 指向 nil。
2.每个Class都有一个 isa 指针 指向唯一的Meta Class。
3. Root class(meta)的superclass指向Root class,也就是NSObject 形成一个回路。
4.每个Meta class的isa指针都指向Root class (meta)。

cache_t

struct cache_t {
struct bucket_t *_buckets; // 存储Method的链表
mask_t _mask;             //  缓存的bucket的总数
mask_t _occupied;         //  表明目前实际占用的缓存bucket的个数
... 省略其他方法
}

cache 为了方法的调用的性能进行了优化。每当实例对象接收到一个消息时,它不会直接在isa 指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了 而是在cache 中查找。Runtime 系统会把被调用方法存到 cache 中,下次查找的时候效率更高。

class_data_bits_t

objc_class 中复杂的是 bits class_data_bits_t 结构体所包含的信息太多了,主要包含 class_rw_t,retain/release/autorelease/retainCountalloc 等信息,很多存取方法也是围绕它展开。

struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() {
   return (class_rw_t *)(bits & FAST_DATA_MASK);
}
... 省略其他方法
}

Ivar

Ivar 是一种代表类种实例变量的类型。

typedef struct ivar_t *Ivar;

struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;

uint32_t alignment() const {
    if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
    return 1 << alignment_raw;
}
};

objc_property_t

objc_property_t 是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针,其定义如下:

 typedef struct objc_property *objc_property_t;

objc_property_attribute_t

objc_property_attribute_t 定义了属性的特性(attribute),它是一个结构体,定义如下:

typedef struct {
const char *name;           // 特性名
const char *value;          // 特性值
} 

Method

Method 是一种代表类中的某个方法的类型。

typedef struct method_t *Method;

struct method_t {
SEL name;
const char *types;
IMP imp;

struct SortBySELAddress :
    public std::binary_function<const method_t&,
                                const method_t&, bool>
{
    bool operator() (const method_t& lhs,
                     const method_t& rhs)
    { return lhs.name < rhs.name; }
};
};

方法名类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型 types 是个char指针,其实存储着方法的参数类型和返回值类型。
imp 指向了方法的实现,本质上是一个函数指针,后面会详细讲到。

IMP

IMP 定义:

typedef void (*IMP)(void /* id, SEL, ... */ );

它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。

你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id 和 SEL 参数就能确定唯一的方法实现地址;反之亦然。

消息实现

objc_msgSend 函数

在前面简单对 objc_msgSend 进行了一点介绍。看起来像是objc_msgSend 返回了数据,其实 objc_msgSend 从不返回数据而是你的方法被调用后返回的数据。消息发送过程:

1.检测这个 selector 是不是要忽略的。
2.检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
3.如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
4.如果 cache 找不到就找一下Class中的方法列表。
5.如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
6.如果还找不到就要开始进入消息转发。

方法中的隐藏参数

我们经常在方法中使用 self 关键字来引用实例本身,但没有想过为什么 self 就能取到调用当前方法的对象。其实self 的内容是在方法运行时被偷偷的动态转入的。

objc_msgSend 找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数转递给方法实现,同时还将两个隐藏的参数:
1.接收消息的对象 (self 指向的内容)
2.方法选择器 (_cmd 指向的内容)

在这两个参数中,self 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。
而当方法中的super关键字接收到消息时,编译器会创建一个objc_super结构体:

struct objc_super { id receiver; Class class; };

这个结构体指明了消息应该被传递给特定超类的定义。但receiver仍然是self本身,这点需要注意,因为当我们想通过[super class]获取超类时,编译器只是将指向selfid指针和class的SEL传递给了objc_msgSendSuper函数,因为只有在NSObject类才能找到class方法,然后class方法调用object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是指向self的id指针,与调用[self class]相同,所以我们得到的永远都是self的类型。

下面这道题考察的点就是上面所说的
下面代码输出什么?🤔

 @implementation Son : Father 
 - (id)init {
self = [super init];
if (self)
{
    NSLog(@"%@", NSStringFromClass([self class]));
    NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end

引用文献:
神经病院 Objective-C Runtime 入院第一天—— isa 和 Class
玉令天下的博客-Objective-C Runtime

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 参数自一个指针,指向类的要接收消息的实例。 OP在处理该信息的方法的选择。 ......可变参数列表包含参数的方法...
    reallychao阅读 785评论 0 0
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,129评论 0 9
  • runtime 运行时语言,实现Object-C的C语言库,将OC转换成C进行编译的过渡者。 作为一门动态编程语言...
    夜雨聲煩_阅读 541评论 0 0
  • swift Swift中文指南- 中文版Apple官方Swift教程《The Swift Programming ...
    EEEasonX阅读 355评论 0 0