Runtime系列一:Runtime的前世今生

前言:

关于Runtime的资料网上一搜很多,但总是写的只言片语,不太全面。最近花了一个星期的时间重新学习Runtime,并整理了一个系列文章,并发表出来,同时也感谢开源贡献的开发者。这里共有三篇文章:

Runtime系列一:Runtime的前世今生

Runtime系列二:Runtime的原理

Runtime系列三:Runtime在项目中使用场景

一、  Smalltalk 与 C 的融合---Objective-C

三十几年前,Brad Cox 和 Tom Love 在主流且高效的 C 语言基础上,借鉴 Smalltalk 的面向对象与消息机制,想要搞出一个易用且轻量的 C 语言扩展,但 C 和 Smalltalk 的思想和语法格格不入,比如在 Smalltalk 中一切皆对象,一切调用都是发消息:

233 log

再比如用一个工厂方法来实例化一个对象:

p := Person name: 'sunnyxx' age: 26

在当时来看,一个具有面向对象功能的 C 语言真的是非常有吸引力,但必须得解决消息语法的转换,于是乎他们开发了一个 Preprocessor(预编译程序),去解析 Smalltalk 风格的语法,再转换成 C 语言的代码,进而和其他 C 代码一起编译。想法很美好,但 Smalltalk 语法里面又是空格、又是冒号的,万一遇到个什么复杂嵌套调用,语法解析多难写呀,于是乎他们想,诶呀别费劲了,把消息两边加个中括号吧,这样 Parser 写起来简单多了呢对吧:

[Person name:"sunnyxx"age:26];

这就造就了 Objective-C 奇怪的中括号、冒号四不像语法,这怎么看都是个临时的方案,但在当时可能是唯一方法,借用已有的 C 的编译器比重造一个成本低多了,而且完全兼容 C 语言。随着这几年 Apple 开发的火热,Objective-C 越来越成为 Apple 不爽的地方,先是恨透了在 GCC 上给 Objective-C 加支持,自己重建了个 Clang,后是干脆重新发明了个 Swift 来彻底代替,用 30 年的时间终于还完了这个技术债。

好的,虽然有了个 Preprocessor,但只能做到把 Smalltalk 风格的代码分析并转译成 C,还需要解决两个问题:

<1>: C 语言上实现一个 OOP 对象模型

<2>: 将 Smalltalk 风格的 Message 机制转换成 C 函数调用

对象模型的设计倒很省事,直接照搬 Smalltalk 的就好了:如 Class / Meta Class / Instance Method / Class Method 这些概念,还有一些关键字如 self / super / nil 等全都是 Smalltalk 的。这步转换在 Preprocessing 过程中就可以完成,因为重写后的 Class 就是原原本本的 C 语言的 Struct,只需要按 Smalltalk 中“类-元类”的模型设置好即可,无需额外的支持。

消息机制就不一样了,要实现向一个 target ( class / instance ) 发送消息名 ( selector ) 动态寻找到函数实现地址 ( IMP ) 并调用的过程,还要处理消息向父类传递、消息转发( Smalltalk 中叫 “Message-Not-Understood”)等,这些行为无法在 Preprocessing 或 Build Time 实现,需要提供若干运行时的 C 函数进行支持,所有这些函数打个包,便形成了最原始的Runtime

所以最初的  Objective-C = C + Preprocessor + Runtime

作为单纯的 C 语言扩展,Runtime 中只要实现几个最基础的函数(如 objc_msgSend)即可,但为了构建整套 Objective-C 面向对象的基础库(如 Foundation),Runtime 还需要提供像 NSObject 这样的 Root Class 作为面向对象的起点、提供运行时反射机制以及运行时对 Class 结构修改的 API 等。再后来,即便是 Objective-C 语言本身的不断发展,新语言特性的加入,也不外乎是扩展 Clang 和扩展 Runtime,比如:

ARC:编译器分析对象引用关系,在合适的位置插入内存管理的函数,并需要把这些函数打包加到 Runtime 中,如objc_storeStrong,objc_storeWeak等,同时还要处理 dealloc 函数,自动加入对 super 的调用等。

Lightweight Generics:叫做 “轻量泛型” 是因为只增加了编译器检查的支持,而泛型信息并未影响到运行时,所以 Runtime 库无需改动。

Syntax Sugars:比如 Boxed Expr(@123)、Array Literal(@[...])、Dictionary Literal(@{...})和轻量泛型一样,只是把如@123在编译期 rewrite 成[NSNumber numberWithInt:123]而已,无需改动 Runtime。

Non Fragile Ivars: 类实例变量的动态调整技术,用于实现 Objective-C Binary 的兼容性,随着 Objective-C 2.0 出现,需要编译器和 Runtime 的共同配合。

因此,Runtime 的精髓并非在于平日里很少接触的那些所谓“黑魔法” Runtime API、也并非各种 Swizzle 大法,而是在 Objective-C 语言层面如何处理 Type、处理 Value、如何设计 OOP 数据结构和消息机制、如何设计 ABI 等,去了解这么一个小而美的 C 语言运行时扩展是怎么设计出来的。

参考文章:http://blog.sunnyxx.com/2016/08/13/reunderstanding-runtime-0/

二、Runtime简介

作为一门动态编程语言,Objective-C 会尽可能的将编译和链接时要做的事情推迟到运行时。只要有可能,Objective-C 总是使用动态 的方式来解决问题。这意味着 Objective-C 语言不仅需要一个编译环境,同时也需要一个运行时系统来执行编译好的代码。运行时系统(runtime)扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。因此,runtime好比Objective-C的灵魂,很多东西都是在这个基础上出现的。所以它是值的你花功夫去理解的。

2.1、与静态语言编译后的区别

1、静态语言

一个静态语言程序,如下所示的C程序:

#include < stdio.h >

int  main(int  argc,const char**argv[])

{

printf("Hello World!");return0;

}

会经过编译器的语法分析,优化然后将你最佳化的代码翻译成汇编语言,然后完全按照你设计的逻辑和你的代码自上而下的执行。

2、Objective-C 动态语言

很常见的一个消息发送语句:

[receiver message]

会被编译器转化成

objc_msgSend(receiver, selector)

如果有参数则为

objc_msgSend(receiver, selector, arg1, arg2, …)

消息只有到运行时才会和函数实现绑定起来,而不是按照编译好的逻辑一成不变的执行。按照我的理解,编译阶段只是确定了要去向receiver对象发送message消息,但是却没有发送,真正发送是等到运行的时候进行。因此,编译阶段完全不知道message方法的具体实现,甚至,该方法到底有没有被实现也不知道。这就有可能导致运行时崩溃问题。

2.2、Objective-C Runtime的几点说明

1、runtime是开源的

是的,你没看错,runtime确实是开源的。目前苹果公司和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。其中苹果的版本可以在工程中引用

#import  <objc/runtime.h>  点击右键jump to definition,进去查看

2、runtime是由C语言实现的

runtime做为Objective-C最核心的部分,几乎全部由C语言实现。这里的“几乎”所指的例外就包含有的方法(比如下面要说道的objc_msgSend方法)甚至是用汇编实现的!!

3、runtime的两个版本

Objective-C运行时系统有两个已知版本:早期版本(Legacy)和现行版本(Modern)。

在现行版本中,最显著的新特性就是实例变量是"健壮“(non-fragile)的:

 在早期版本中,如果您改变类中实例变量的布局,您必须重新编译该类的所有子类。

 在现行版本中,如果您改变类中实例变量的布局,您无需重新编译该类的任何子类。

此外,现行版本支持声明property的synthesis属性器。

目前iPhone 程序和 Mac OS X v10.5 及以后的系统中的 64 位程序使用的都是 Objective-C 运行时系统的现行版 本。其它情况(Mac OS X 系统中的 32 位程序)使用的是早期版本。

2.3、和Runtime system交互的三种方式

1、通过Objective-C源代码

大部分情况下,运行时系统在后台自动运行,我们只需编写和编译 Objective-C 源代码。当编译Objective-C类和方法时,编译器为实现语言动态特性将自动创建一些数据结构和函数。这些数据 结构包含类定义和协议类定义中的信息,如在Objective-C 2.0 程序设计语言中定义类和协议类一节所讨论 的类的对象和协议类的对象,方法选标,实例变量模板,以及其它来自于源代码的信息。运行时系统的主要功能就是根据源代码中的表达式发送消息。

2、通过类NSObject的方法

Cocoa程序中绝大部分类都是NSObject类的子类,所以大部分都继承了NSObject类的方法,因而继承 了NSObject的行为(NSProxy类是个例外)。然而,某些情况下, NSObject类仅仅定义了完成某件事情的模板,而没有提供所有需要的代码。

例如,NSObject 类定义了description方法,返回该类内容的字符串表示。这主要是用来调试程序 ——GDB 中的 print-object 方法就是直接打印出该方法返回的字符串。NSObject 类中该方法的 实现并不知道子类中的内容,所以它只是返回类的名字和对象的地址。NSObject 的子类可以重新实现该方法以提供更多的信息。例如,NSArray 类改写了该方法来返回 NSArray 类包含的每个对象的内容。

某些 NSObject 的方法只是简单地从运行时系统中获得信息,从而允许对象进行一定程度的自我检查。

例如,class 返回对象的类;isKindOfClass:和 isMemberOfClass:则检查对象是否在指定的 类继承体系中;respondsToSelector:检查对象能否响应指定的消息;conformsToProtocol: 检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。

3、通过运行时系统的函数

运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明 头文件在/usr/include/objc中。这些函数支持用纯C的函数来实现和Objective-C同样的功能。还有一些函数构成了 NSObject 类方法的基础。这些函数使得访问运行时系统接口和提供开发工具成为可 能。尽管大部分情况下它们在 Objective-C 程序不是必须的,但是有时候对于 Objecitve-C 程序来说某些函 数是非常有用的。 这些函数的文档参见 Objective-C 2.0 运行时系统参考库。



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

推荐阅读更多精彩内容