从 ObjC Runtime 源码分析一个对象创建的过程

引言

最近闲来无事,研究研究 runtime。借助 runtime,ObjC 基本具备了动态语言的主要特性,下面这段代码便是动态创建一个类:


不知道大家发现问题了没有,这段代码在运行时 runtime 其实会触发一个 SIGKILL 的自杀信号来终止程序,我们来看看错误是什么:

额,据我们所知 doesNotRecognizeSelector: 是在一个无效 selector 被发出并一直没有被动态解析成功最后的一步,这个方法执行后你的程序也就挂掉了,但是我们这里得到的错误是,这个方法竟然也没有被实现!

这差点让我陷入困扰,知道我想起 Foo 类根本没有父类,所以 Foo 类除了我添加的 sayHello: 方法以外,别无其他任何方法。我们知道在 ObjC 中,任何类都要继承自 NSObject 这一基类,不然编译时就会出错,可以说 NSObject 是 ObjC 的灵魂,诸如 KVOKVC 等特性都是这个基类实现的。这么说 alloc 类方法也是 NSObject 实现的咯。那必然是,为了一探究竟,我下载了 ObjC Runtime 的全部源码,下面拿来分析一下。

Getting Started

首先,我们看一下工程目录:


P.S. 太长了,我的 15' RMBP 看起来也头疼

这里我们直奔主题,找到 NSObject.mm 这个文件,这是一个 C++ / OC 混写的源文件。这个文件代码特别长,我们通过导航菜单快速定位到 alloc 方法的位置:

按住 ⌘ 一路点击跟踪,直到这里:


这是一个纯 C 静态内联函数,可以看到在 ObjC 2.0 版本之后新增了一种自定义的快捷构造方式,我们不用管它,事实上它们最终都要调用 class_createInstance 这个方法,我们来看看:

这里有个分叉口,就是判断编译时是否使用 GC,至于这点其实我们不用过于纠结,iOS 上是不能使用 GC 的,这是苹果不知道什么时候为 Mac 设计的,应该没什么用。而且那个貌似经常出现的迷之 NSZone 类也是用来辅助 GC 来做内存管理的。这都是历史遗留问题了,我们直接走 _alloc 函数那个分支。这个 _alloc 指针指向了一个名为 _class_createInstance 的静态函数,但这个函数最终还是要调用 _class_createInstanceFromZone 函数,只不过这里 zone 参数传了 nil

继续跟踪,我们来到了 _class_createInstanceFromZone 函数:


在这里,runtime 主要做了有关内存对齐的一些计算,然后由于 zonenil,因此这里直接用 calloc 申请了一块内存。callocmalloc 的区别是,calloc 一次可以申请 n * size 字节大小的内存,并且申请后自动置零。紧接着,我们再看看最后一步 objc_constructInstance 函数:

这一步其实就做了一件事,那就是初始化对象的 isa 指针。我们在开发时用到的 objc/runtime.h 头文件中也有声明 id 就是 objc_object 这个结构体的指针,但是 objc_object 是一个我们称之为 Opaque Type 的东西,也就是说它对于开发者来说不需要理解其结构,只要拿来当一个“句柄”即可。但是现在我们有源码,所以我们就可以一探究竟!

先看看 objc_object 到底是什么:

这家伙其实就是一个 C++ 结构体,有权限控制,有成员函数。然后我们看看刚才提到的 objc_object::initIsa 函数:


恩,就是把对象的 isa 指针指向这个类的元信息 Class

What's Next?

知道全部过程之后我们其实就可以为我们的 Foo 类写一个 alloc 方法了。

且慢!我们似乎最后一步很难办到,objc_object 对于开发者而言并不能接触到,我们有必要通过直接修改内存的方式去修改其 isa 变量。那么,回到源码,我们看看 isa 那个 isa_t 类型究竟是什么:


原来是个联合体,鉴于我们从源码中看到的,它在初始化时直接被当做 uintptr_t 对待了,而这家伙又是 unsigned longtypedef,所以我们最终的代码可以顺理成章地写出来:

当然,这是最简化的代码了,但它和 runtime 的功能是一致的,我们没有考虑其他情况,仅仅对于 Foo 类是足够的了。

有一点需要注意的是,我们全程都不能使用 ARC,因为 ARC 模式下从 void * 转换到 id 是需要有一个 bridge 的过程的,而这个过程仍然依赖于 NSObject 来完成,所以我们又会陷入一个需要 NSObject 的死循环。

下面我们把上面实现的 alloc 方法添加到我们的类中,然后用平常的 [Foo alloc] 初始化一个实例对象,再执行。

仿佛又遇到困难了:


每个方法执行时 runtime 都会发出图中的警告,并且企图使用 abort() 函数杀死程序。我通过 step in 的方式使 abort() 函数被系统调用绕过,发现其实这整个流程都是可以 work 的。

原因出在哪了呢?可能是 runtime 在执行 objc_msgSend 的时候检查了这两个方法?

没办法,继续看 objc_msgSend 的实现,它的实现是由汇编语言写的,看样子像是宏汇编,反正我汇编很弱,将就看吧。我在一堆汇编代码里一顿通读以后发现问题可能出在 Cache 检查上。


可以发现,runtime 在检查 Cache 的时候也会执行 forwarding,然而我们没有实现相关方法,因此会触发 MESSENGER_END_FAST 子过程,我们的程序也就挂了。

既然这样,我只好把这两个函数简单做一个空实现了:


为了找出究竟哪个 selector 不能识别,我用 NSLog 打印出了这个 selector。运行结果让我恍然大悟:

我忘了实现 initialize 了。。。。。。。。。。。。。。。
这个方法在某个类第一次被初始化时调用,行了,加上 initialize 的空实现,没有父类的 Foo 类圆满了。一脸辛酸 ing。笔者写到这此时已是凌晨两点。。不说了,给大家看下最后的成果:

Wrap Up

本文结束了。最后想总结两句:

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 原文出处:南峰子的技术博客 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了...
    _烩面_阅读 1,215评论 1 5
  • 你想一夜暴富吗?你想一夜成名吗?你想开兰博基尼泡妞吗?你想拿钞票点烟吗?你想成为世界主宰吗?那还等什么,赶紧洗洗睡...
    科长2333阅读 250评论 0 1
  • 多年前 藏在口袋里的花 今晚被我拾了起来 想像着 这是在夏季的海岛 扬起帽子 浮现抑或消失 火焰般遗忘的嘴唇 人儿...
    雷玉玲Lyndi阅读 109评论 0 0
  • 过了几年没有在妈妈身边的日子,从今年开始又可以依偎在身边了。 加上最近待产,婆婆也过来,我又发现了很多从前在她身上...
    左左爱笑阅读 328评论 0 0