热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

理解Swift:ObjectiveC的构建管道

Xcode 是如何将 Swift 和 Obj-C 编译到一起的?
如果你没有 xcodebuild 的话,应该要怎么做?
我们来看看“编译到一起”两种不同的方式:

  • Obj-C 使用 Swift
  • Swift 使用 Obj-C
    今天,我们将会使用 Swift 的风格来看待 Obj-C,目的是让你对这些处理过程的线索有个大致理解。改天我们再挖掘这些线索在实际过程中是如何做到的。 

Obj-C 代码是如何使用其他 Obj-C 代码的?

首先,我们来看看Obj-C 代码是如何使用其他 Obj-C 代码的。显然,使用 Swift 构建的 Obj-C 建立在此之上,所以大部分繁杂的工作实际上都发生在这个处理过程中。这也意味着,一旦你搞明白了,就对 Obj-C 如何使用 Swift 理解了 90%!

逐个编译,再链接全部

总的来说,构建一个 Obj-C 代码使用其他 Obj-C 代码的可执行文件,有两步处理:

  • 编译(Compile):每个文件都会被编译成一个目标文件:A.m -> A.o
    理解 Swift:Objective-C 的构建管道

  • 链接(Link):所有的目标文件都被链接器合并成一个可执行文件:A.o, B.o, … -> MyApp
    理解 Swift:Objective-C 的构建管道

头文件承诺,编译器信任,链接器验证

编译和组合(链接)的步骤依赖于来自头文件的信息。

头文件承诺

头文件对 API 们做出承诺。就像如下代码:

1
NSString *NSTemporaryDirectory();

 

表示:

相信我!这儿会有个叫 NSTemporaryDirectory 的方法,如果你没有用任何参数调用它,它会返回一个 NSString *。 

一个这样的接口声明:

1
2
3
@interface Something: NSObject
- (BOOL)makeItSo: (NSError **)outError;
@end

 

表示:

听我的:当你需要的时候,会有一个名为 Something 的 NSObject 类型的类。这个类的所有方法?就像这个接口声明的那样,尽管相信我吧! 

编译器信任

编译器照着这些头文件说的做,并针对他们的承诺检查了它们所提供的实现文件。
理解 Swift:Objective-C 的构建管道

假如碰到:

1
NSTemporaryDirectory(updatedTemporaryDirectoryPath);


它会大发脾气道:

 

你这什​​么意思,给这东西传个参数!而且会有一个返回值,使劲吹吧!难道还会有人想要有返回值?! 

(编译器非常容易激动,这就是他们如何对工作所需的细节表现出的极度关注。尽管如此,这些行为并没有任何意义。)
如果所有东西都检查通过,编译器将会保持安静,完成其工作,并留下从实现代码翻译而来的目标代码后离开。
(这个经过翻译的目标代码会被嵌入额外假设,这些额外的假设基于像这些承诺:如何给函数传递参数──传到堆栈上?寄存器中?向量寄存器中?以及从哪里读出返回值?但这里牵涉到需要描述什么是 ABI,所以我们下次再讨论这些。)
目标代码完全相信,头文件所承诺的函数定义在后面确实会存在!编译器输出代码,说:“嘿,去调用这个函数吧,虽然我不知道它在哪儿,但有一些头文件承诺它会在那里,所以我只是在这里做一下信任。”
结果便是,目标代码带着一堆未定义的引用。所有这些未定义的引用都是关于,什么东西在哪里、有多少个参数,是什么类型的参数与返回值的假设。
编译器对这些头文件有很大的信心,是吗?

旁白
有多少未定义的引用?你自己看!
选择一个对象文件 A.o 并运行 nm -u A.o。输出将会是列出该文件所引用的所有未定义名称。
nm 是一个工具,它以人类可读的方式格式化对象文件所引用的名称的表格,称为符号表。(nm 是 NaMes,懂了吗?),它也可以用来过滤列表,就如这里的 -u 要求它只列出未定义的名字。 

链接器验证

编译器总结说,“嘿,东西都在这儿了,一些头文件承诺的!”
链接器则会说,“把钱拿出来看看”。他们把所有的目标文件堆在一起,并处理了那些未定义的引用。如果有东西没有检查成功,他们会翻桌子,丢出错误,并拒绝继续执行:

1
2
3
4
5
6
> cc trust-me.m
Undefined symbols for architecture x86_64:
"_thisWillTotallyBeThere", referenced from:
_main in trust-me-c9e7ba.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

 

所有的目标文件都被送往链接器,链接器连接它们的外部名称并检查所有的东西是否如实被定义了。如果是的话,链接器会吐出一个可执行文件。
理解 Swift:Objective-C 的构建管道

当然,还有更多的东西。我们没有触及模块或动态链接(frameworks!dylibs!dlopen!)。如果你觉得不够详细,可以看看Advanced Mac OS X Programming。

从 Obj-C 使用 Swift

呼,这真是有够消遣的了。幸运的是,我们差不多完成了。

我们来让 Swift 变得看起来像 Obj-C

Obj-C 的编译依赖于头文件和目标文件。但Swift不需要头文件,而且你可能也从来没有碰到过 Swift 的目标文件。那 Xcode 要如何解决这个问题?
当然是给每个 Swift 文件生成单独的头文件了目标文件啦!
每个 Swift 文件都会被编译为一个目标文件和一个供 Obj-C 使用的头文件: A.swift -> A.o, A.h
这告诉了我们,运行普通的 Obj-C 构建和链接编译管道需要什么东西:

  • 编译每个.m文件的桥接头文件
  • 将所有的目标文件(Swift 和 Obj-C)连接成一个可执行文件

复数桥接头文件

因为该头文件在 Obj-C 和特定的swift文件的世界之间架起了桥梁,所以它被称为桥接头文件。
现在,你可能会遇到这种情况,“你正在添加 Obj-C 文件!到一个 Swift 的项目!你想要一个桥接头文件?”之前在 Xcode 中的提过。这也只是一个桥接头文件,而不是一堆(很多个!每个 Swift 文件一个)桥接头文件。一座桥连接一个堤岸到另一个堤岸,而单数桥接头文件则从 Swift 架往 Obj-C 大陆,复数桥接头文件则从 Obj-C 大陆通往 Swift 之地。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ===[ CallMeFromObjC.swift ]===
// 你必须 import Foundation 使其能够调用 Obj-C.
import Foundation
 
// 一个类若要对 Obj-C 可见则必须继承自 NSObject
// (如果你想要写一个根类,则必须在 Obj-C 上写)
public class CallMeFromObjC: NSObject {
// 公开你想在 Objc-C 上使用的 API
public var name: String
public init(name: String) {
self.name = name
}
public func speak() {
print("\(self)'s name is: \(name)")
}
}

用编译管道运行下面的粗糙调用:

1
2
3
4
5
6
7
8
9
install -d build
xcrun -sdk macosx10.12 \
swift -frontend -c -primary-file CallMeFromObjC.swift \
-sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/ \
-module-name Bridgette \
-emit-module-path build/Bridgette.swiftmodule \
-emit-objc-header-path build/CallMeFromObjC.h \
-enable-testing -enable-objc-interop -parse-as-library \
-o build/CallMeFromObjC.o

 

下面则是它生成的桥接头文件的核心代码:

1
2
3
4
5
6
7
8
SWIFT_CLASS("_TtC9Bridgette14CallMeFromObjC")
@interface CallMeFromObjC : NSObject
@property (nonatomic, copy) NSString * _Nonnull name;
- (nonnull instancetype)initWithName:(NSString * _Nonnull)name OBJC_DESIGNATED_INITIALIZER;
- (void)speak;
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
@end

(为了方便起见省略了大堆顶部的定义,你可以在这个 gits 里查看完整细节。看他们如何处理警告信息也是很有趣。)

怎样导入?

编译和链接 Obj-C 用于 Swift,也意味着编译 Swift 并将其与其他 Swift 链接起来。当编译器为swift文件生成一个目标文件时,它也需要大量的信任。
在这种情况下,项目中的其他 Swift 文件允许编译器的外部定义。对,Swift 文件本身就是有效的头文件!
从其它文件导入命名到模块中在源码里是隐式的:当你在 A.swift 中编写代码时,你可以***使用 B.swift 中定义的类型B,并且你只需认为它是可用的就行。如果这是 Obj-C,那就好像你项目中的每个 .m 文件都会自动获得一系列你项目中的每个.h文件的 #imports
尽管如此,Swift 代码并没有 #imports 来命名特定文件,以便将其编译为单个 .swift 文件。因此,不是将模块中的所有文件都在 .swift 文件列入,而是将该列表移至编译器调用:当你编译单个 Swift 文件时,编译器调用会把该模块中别的 Swift 文件都列出来。
好消息是,Xcode 正为你编写着这些编译器调用,是吧?

总结

Obj-C 编译过程是对提供了为映射步骤带出确实上下文的头文件的映射和合并。映射到目标文件;合并为可执行文件。
用 Obj-C 使用 Swift 是为了让 Swift 看起来像 Obj-C,所以普通的 Obj-C 构建管道就能发挥其魔力。为了让 Swift 看起来像 Obj-C,每个 Swift 文件都会被编译成相应的头文件和目标文件。
这是一个高层次的概览。要真正理解正在发生的事情,我们需要仔细研究 Xcode 的构建日志以了解所有这些细节。但这又是改天的工作了。

http://blog.joyingx.me/2018/03/27/理解-Swift-Objective-C-的构建管道/


推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 认识Cutestrap,一个轻量级CSS框架
    CutestrapisabrandnewCSSframework.ThisarticlepresentsCutestrap’sfeaturesandputstheframework ... [详细]
  • ObjectiveC与Swift之间的互相调用和跳转
    Objective-C与Swift之间的互相调用和跳转-一、OC和Swift互相跳转首先在需要引入Swift的文件中导入头文件#import工程名-Swift.hOC跳转Swift ... [详细]
  • 概述UIStackView是iOS9中新增的API,类似于Android中的线性布局。UIStackView相当于一个容器,根据向UIStackView中添加视图的顺序,UIStack ... [详细]
  • ObjectiveC:语法基础
    OC特点:    1)支持C语法;    2)支持面向对象特性;    3)兼容性好,可以同时在项目中使用OC、C++,也可以引入C、C++库文件;     ... [详细]
  • 由引起的bug~
    编程经验:一个由引起的bug~文章一 转自:http:www.mamicode.cominfo-detail-506772.html1.问题描述最近遇到一个莫 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文记录了作者对x265开源代码的实现与框架进行学习与探索的过程,包括x265的下载地址与参考资料,以及在Win7 32 bit PC、VS2010平台上的安装与配置步骤。 ... [详细]
  • PG12新增的VACUUM命令的SKIP_LOCKED选项
    PG12版本的VACUUM命令新增了SKIP_LOCKED选项,该选项使得vacuum命令在遇到被lock住的table时可以跳过并被视为成功执行。之前的版本中,vacuum命令会一直处于等待状态。本文还提到了PostgreSQL 12.1版本的相关信息。 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • nil用来给对象赋值(Objective-C中的任何对象都属于id类型),NULL则给任何指针赋值,NULL和nil不能互换,nil用于类指针赋值(在Objective-C中类是一 ... [详细]
  • 嗨,我想用多处理来加速我的代码。但是,apply_async对我不起作用。我试着做一个简单的例子,比如:frommultip ... [详细]
  • Chapter4:菜单FileSettingsAppearanceBehavior
    本教程使用社区版IntelliJIDEA2021.1。1SettingsIDEA配置功能列表如图1.1所示。图1.1Settings功能预览由图1.1可知,配置列 ... [详细]
author-avatar
尹一2502904223
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有