前段时间有人问我,如何考虑编译优化。我想了想,但是因为自己没有学过这方面的课程,也没有总结出成套的体系(毕竟自己一共就只吃了几年程序员的🍚),所以,只能告诉他,“把自己当做编译器的作者。当编译器的作者面对自己的代码时,他能做些什么”
扯淡结束,下面通过一个小例子讲讲“为什么 Objective-C 难以进行优化”。
Objective-C 是 C 的超集,语言创作者通过给 struct 添加了一些 function,把 struct 升级为了 class。当然,实际情况不会像我说的这样简单,肯定会更加复杂,但我这里只是讲和下文相关的内容。
struct 升级为了 class,function 也“一人得道鸡犬升天”地升级为了 method。
很多 iOS 开发者都无法区分“函数”和“方法”。实际上,把它们翻译为 function 和 method 可能区分度更高一些。
可以粗糙的用以下等式区别它们:
类 = 含有特殊字段的结构体 + 其它
method = function + 类的引用 + 其它
首先,稍微解释一下第一个等式。类经过编译后,会被打散到 mach-o 的多个地方。其中,一份信息会存储到 mach-o 的 __DATA,__objc_data
字段。
如下图。
Objective-C 中的 method 被编译后,会被当做函数处理,与此同此,编译后的 mach-o 文件也会保留一些特殊信息。
如下图,第一幅图表示:method 编译后,会在 段__TEXT,__text
生成一个对应的函数。第二幅图表示:这是一个函数。第三幅图表示:method 与 class 之间的关系会被存储到 段 __DATA,__objc_const
。
我们先回顾一下它们的用法。
简单来说,类似于 [[NSObject alloc]init]
的写法都是在调用 Objective-C 中“类”或“类的实例”的 method,而 int result = pow(x,y)
属于调用 function 的过程。
考虑这样一种情况,如果某个类,它的某些方法没有被编译器检测到调用,比如没有检测到[object aMethod]
这种用法。编译器是否该把这个方法的实现从编译后的 mach-o 文件内移除,从而减小可执行文件的体积,提到可用的内存空间,加快载入速度?
答案是:“非常不推荐这样做”。Objective-C 是一门动态语言,除了直接调用外,还有大量的动态调用方式。仅仅因为编译器检测不到调用就进行移除是非常危险的行为。
再考虑这样一种情况,如果某个函数,它没有被编译器检测到被调用,比如没有检测到 int result = pow(0,0)
。编译器是否该把这个函数的实现从编译后的 mach-o 文件内移除,从而减小可执行文件的体积,提到可用的内存空间,加快载入速度?
答案是:“非常推荐这样做”。虽然通过某些方案也可以动态调用
c 函数,但是,很显然,这样行为非常非常少,程序员有义务自己处理这种情况或者关闭优化器。
类似的,因为 Objective-C 可以动态添加 method 的原因,编译器不能直接把 [objc superMethod] 替换为 直接执行函数superMethod(objc,...)
,而是需要通过递归地查找方法列表,检测该方法是否被某一个父类动态添加了实现。
综上,一个动态性非常强的语言是很难依靠编译器作者的智慧进行优化的,所以,苹果才会从零开始发明一种新的编程语言。