Module System of Swift (简析 Swift 的模块系统)

原文地址: http://andelf.github.io/blog/2014/06/19/modules-for-swift/

 

Swift 中模块是什么?当写下 Swift 中一句 import Cocoa 的时候到底整了个什么玩意?官方 ibook 很含糊只是提了半页不到。

本文解决如下问题

  • 介绍 Swift 中两种可 import 的模块
  • 如何用 Swift 写一个可被其他 Swift 代码使用的模块
  • 分析 Swift 的标准库实现方式

第一部分 Clang 模块(系统模块)

Clang 模块是来自系统底层的模块,一般是 C/ObjC 的头文件。原始 API 通过它们暴露给 Swift ,编译时需要链接到对应的 Library。

例如 UIKitFoundation 模块,从这些模块 dump 出的定义来看,几乎是完全自动生成的。当然, Foundation 模块更像是自动生成 + 人工扩展(我是说其中的隐式类型转换定义、对 Swift 对象的扩展等,以及 @availability 禁用掉部分函数。)。相关函数声明可以从 我的 Github andelf/Defines-Swift 获得。

我可不觉得这些定义全部都是官方生成后给封装进去的。所以在整个 Xcode-6 beta2 目录树里进行了探索。

在 Xcode 目录寻找相关信息,最后目标锁定到了一个特殊的文件名 module.map

原来这个文件叫 Module map(这个名字还真是缺乏想象力),属于 llvm 的 Module 系统。本来是用来颠覆传统的 C/C++/Objc 中的 #include 和 #import。最早在 2012 年 11 月的 LLVM DevMeeting 中由 Apple 的 Doug Gregor 提出 1。相关内容 CSDN 也有文章介绍,不过是直译版,没有提出自己见解 2。

关于 llvm Module 系统

2012 年提出概念,所以其实这个东西已经很早就实现了 。简单说就是用树形的结构化描述来取代以往的平坦式 #include, 例如传统的 #include <stdio.h> 现在变成了 import std.io;, 逼格更高。主要好处有:

  • 语义上完整描述了一个框架的作用
  • 提高编译时可扩展性,只编译或 include 一次。避免头文件多次引用,只解析一次头文件甚至不需要解析(类似预编译头文件)
  • 减少碎片化,每个 module 只处理一次,环境的变化不会导致不一致
  • 对工具友好,工具(语言编译器)可以获取更多关于 module 的信息,比如链接库,比如语言是 C++ 还是 C
  • 等等

所以这么好的一个东西, Apple 作为 llvm 的主力,在它的下一代语言中采用几乎是一定的。

算了,我是个半路出家的,之前没接触过 iOS / MacOSX 开发,其实 2013 年的 WWDC, Apple 为 Objective-C 加入的 @import 语法就是它。可以认为,这是第一次这个 Module 系统得到应用。

module.map 文件

module.map 文件就是对一个框架,一个库的所有头文件的结构化描述。通过这个描述,桥接了新语言特性和老的头文件。默认文件名是 module.modulemapmodule.map 其实是为了兼容老标准,不过现在 Xcode 里的还都是这个文件名,相信以后会改成新名字。

文件的内容以 Module Map Language 描述,大概语法我从 llvm 官方文档 3 摘录一段,大家体会一下:

module MyLib {explicit module A {header "A.h"export *}explicit module B {header "B.h"export *}
}

  

类似上面的语法,描述了 MyLibMyLib.AMyLib.B 这样的模块结构。

官方文档 [^3] 中有更多相关内容,可以描述框架,描述系统头文件,控制导出的范围,描述依赖关系,链接参数等等。这里不多叙述,举个 libcurl 的例子:

module curl [system] [extern_c] {header "/usr/include/curl/curl.h"link "curl"    export *
}

  

将此 module.map 文件放入任意文件夹,通过 Xcode 选项或者命令行参数,添加路径到 import search path (swift 的 -I 参数)。 然后就可以在 Swift 代码里直接通过 import curl 导入所有的接口函数、结构体、常量等,(实测,发现 curl_easy_setopt 无法自动导入,看起来是声明语法太复杂导致)。甚至可以直接从 swift repl 调用,体验脚本语言解释器般的快感(因为我们已经指定了链接到 curl 库)。

Xcode 选项位于 Build Settings 下面的 Swift Compiler – Search Paths 。添加路劲即可。

再举个复杂点的 SDL2.framework 的例子,看看如何实现树形的模块结构,这个需要把 module.map 放到 .framework 目录里

framework module SDL2 [system] {umbrella header "SDL.h"link -framework SDL2module Version {header "SDL_version.h"export *}module Event {header "SDL_events.h"export *}// ....export *module * {export *}
}

  

小结

Swift 的 C 模块(也是它的标准库部分)完全就是 llvm 的 Module 系统,在 import search path 的所有 module.map 中的模块都可以被识别,唯一缺点可能是如果有过于复杂用到太多高级 C 或者黑暗 C 语法的函数,无法很好识别,相信以后的版本会有所改善。

所以当有人问 Swift 到底有多少标准库的时候,答案就是,基本上系统里所有的 Objective-C 和 C 头文件都可以调用。自 iOS 7 时代,这些头文件就已经被组织为 Module 了,包括标准 C 库 Darwin.C。同样因为 Module 系统来自于传统的 C/C++/Objc 头文件,所以 Swift 虽然可以有 import ModA.ModB.ModC 的语句,但是整个模块函数名字空间还是平坦的。

一些有意思的模块可以探索探索,比如 simd,比如 Python(没错是的,直接调用 Python 解释器)等。

另外 Swift 的 -module-cache-path 参数可以控制这类模块预编译头的存放位置( .pcm 文件: pre compiled module)。

Xcode 项目的 Build Settings , Apple LLVM 6.0 – Language – Modules 有项目对 Module 支持的相关选项,默认是打开的。

第二部分 Swift 模块

说完了系统模块,该说 Swift 模块了。 Swift 自身的这个系统还是很赞的。

本节介绍怎样用 Swift 创建一个可 import 的模块。

几个文件类型

先清楚几个文件类型。假设 ModName.swift 是我们的 Swift 源码文件。

  • ModName.swiftmodule Swift 的模块文件,有了它,才能 import
  • ModName.swiftdoc 保存了从源码获得的文档注释
    • 文档注释以 /// 开头
  • libswiftModName.dylib 动态链接库
  • libswiftModName.a 静态链接库

TODO: 目前有个疑问就是 .swiftmodule 和链接库到底什么时候用哪个,以及具体作用。

.swift 源码文件

先明确一个概念,一个 .swift 文件执行是从它的第一条非声明语句(表达式、控制结构)开始的,同时包括声明中的赋值部分(对应为 mov 指令或者 lea 指令),所有这些语句,构成了该 .swift 文件的 top_level_code() 函数。

而所有的声明,包括结构体、类、枚举及其方法,都不属于 top_level_code() 代码部分,其中的代码逻辑,包含在其他区域,top_level_code() 可以直接调用他们。

程序的入口是隐含的一个 main(argc, argv) 函数,该函数执行逻辑是设置全局变量 C_ARGC C_ARGV,然后调用 top_level_code()

不是所有的 .swift 文件都可以作为模块,目前看,任何包含表达式语句和控制控制的 .swift 文件都不可以作为模块。正常情况下模块可以包含全局变量(var)、全局常量(let)、结构体(struct)、类(class)、枚举(enum)、协议(protocol)、扩展(extension)、函数(func)、以及全局属性(var { get set })。这里的全局,指的是定义在 top level 。

这里说的表达式指 expression ,语句指 statement ,声明指 declaration 。可能和有些人对相关概念的定义不同。实际上我特无奈有些人纠结于概念问题,而不是问题本身,本来翻译过来的舶来品就有可能有误差,当你明白那指的是什么的时候,就可以了。

模块编译方法

这里先以命令行操作为例,

xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) ModName.swift -emit-library -emit-module -module-name ModName -v -o libswiftModName.dylib -module-link-name swiftModName

执行后获得 ModName.swiftdocModName.swiftmodulelibswiftModName.dylib.

这三个文件就可以表示一个可 import 的 Swift 模块。目前看起来 dylib 是必须得有的,否则链接过程报错。实际感觉 .swiftmodule 文件所包含的信息还需要继续挖掘挖掘。

多个源码文件直接依次传递所有文件名即可。

静态链接库 .a 目前还没有找到方法, -Xlinker -static 会报错。

命令行参数解释

相关命令行参数:

  • -module-name <value> Name of the module to build 模块名
  • -emit-library 编译为链接库文件
  • -emit-module-path <path> Emit an importable module to 编译模块到路径(全路径,包含文件名)
  • -emit-module Emit an importable module
  • -module-link-name <value> Library to link against when using this module 该模块的链接库名,就是 libswiftModName.dylib,这个信息会直接写入到 .swiftmodule

使用模块

使用模块就很简单了,记住两个参数:

-I 表示 import search path ,前面介绍过,保证 .swiftmodule 文件可以在 import search path 找到(这点很类似 module.map 文件,找得到这个就可以 import 可以编译)

-L 表示 链接库搜索路径,保证 .dylib 文件可以在其中找到,如果已经在系统链接库目录中,就不需要这个参数。

例如:

xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) mymodtest.swift -I. -L.

此时表示所有 module 文件都在当前目录。

这两个选项都可以在 Xcode 中指定,所以如果你有小伙伴编译好的 module 想在你的项目里用是完全 ok 的。

For Xcode

很不幸,没能在 Xcode 中找到编译模块的相关方法。等我发现如何搞定的时候我会补上这个坑。

不过在任何含 Swift 项目的编译过程中, .swiftmodule 文件总是伴随着 .o 文件传递。

第三部分 瞎分析 .swiftmodule 文件

简单分析下一个 .swiftmodule 所包含的信息。

Foundation

这里先以标准库的 Foundation.swiftmodule 下手。

用 hexdump 查看发现它包含所有导出符号,以及 mangled name 。还有个文件列表,表示它是从哪些文件获得的(可以是 .swift 也可以是 .swiftmodule )。

用 strings 列出内容,发现 Foundation 库有如下特征:

...
Foundation
LLVM 3.5svn
/SourceCache/compiler_KLONDIKE/compiler_KLONDIKE-600.0.34.4.8/src/tools/swift/stdlib/objc/Foundation/Foundation.swift
/SourceCache/compiler_KLONDIKE/compiler_KLONDIKE-600.0.34.4.8/src/tools/swift/stdlib/objc/Foundation/KVO.swift
/SourceCache/compiler_KLONDIKE/compiler_KLONDIKE-600.0.34.4.8/src/tools/swift/stdlib/objc/Foundation/NSStringAPI.swift
CoreFoundation
Foundation
Swift
swiftFoundation
...

  

可以大胆猜测对应下:

  • -module-name => Foundation
  • 编译环境 => LLVM 3.5svn
  • 源文件列表 => …
  • 依赖列表 => CoreFoundationFoundationSwift
  • -module-link-name => swiftFoundation

我由此猜测, Foundation 的确是只有少量 Swift 代码做桥接。然后通过 Clang 模块将剩下工作交到底层。

分析其他类似模块也得到相同结果。

Swift 标准库

接下来有点好奇标准库 Swift 是怎么实现的。得到如下结果。

节选重要部分到 我的 Gist

里面有些很有意思的信息,有兴趣的同学可以去看看。

依赖模块 SwiftShims 是一个 module.map 定义的模块,桥接的部分头文件。源文件有相关信息和注释。大致意思是用来实现几个底层接口对象,比如 NSRange 邓。

其中-module-link-name 是 swift_stdlib_core

结论

LLVM Module 作为 Apple 提出的特性,已经被 Swift 完全采用,直接在它基础上建立了自己的模块系统。我相信它会影响到我们处理第三方库的方式方法。相信不久就会有相关工具基于它来管理依赖关系,比如老的 cocoapods4 可以加入新特性。

用 Swift 写模块目前并没有很好的 IDE 支持,所以不是很方便。基于猜测验证,上面的方法可以实现在 Swift 里 import Swift 模块,方法和结果看起来完全和官方模块相同。

Swift 的标准库完全是上面两种模块的结合体,用 Swift 模块封装 Clang 模块。这就解决了文章一开始提出的问题:为什么标准库大部分看起来是自动生成代码,少部分又好像是人工写的接口代码。

参考文献


  1. Modules – Doug Gregor, Apple, 2012 LLVM DevMeeting PDF Video↩

  2. 为什么应该用模块取代C/C++中的头文件?↩

  3. Modules – Clang 3.5 documentation↩

  4. CocoaPods↩

转载于:https://www.cnblogs.com/FranZhou/p/5007754.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/471361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

四、MySQL分页查询 + 子查询复习 学习笔记 (复习连接查询相关内容 详解)

8&#xff1a;分页查询 应用场景&#xff1a;当要显示的数据&#xff0c;一页显示不全&#xff0c;需要分页提交sql请求 语法&#xff1a; SELECT 查询列表 FROM 表名 【JOIN type JOIN 表2 ON 连接条件 WHERE 筛选条件 GROUP BY 分组字段 HAVING 分组后的筛选 ORDER BY 排序的…

LeetCode 2121. 相同元素的间隔之和(前缀和)

文章目录1. 题目2. 解题1. 题目 给你一个下标从 0 开始、由 n 个整数组成的数组 arr 。 arr 中两个元素的 间隔 定义为它们下标之间的 绝对差 。更正式地&#xff0c;arr[i] 和 arr[j] 之间的间隔是 |i - j| 。 返回一个长度为 n 的数组 intervals &#xff0c;其中 interva…

jquery中怎么删除ul中的整个li包括节点

1.$(ul li).remove(); 2.$(ul li).each(function(){ $(this).remove(); }); 3.$("ul").find("li").remove(); 4.$(ul).children().filter(li).remove();转载于:https://www.cnblogs.com/zhujiabin/p/5008006.html

在android添加数据采集,一种基于Android系统的地理信息数据采集方法与流程

本方法属于采集地理信息数据的发明&#xff0c;是一种基于android操作系统和gis地理信息系统进行户外地理信息数据采集的方法。背景技术&#xff1a;众所周知地理信息数据采集在很多行业中都有应用&#xff0c;比如说农业中的土地普查、城市管理中的地下管线普查、工业中的地质…

五、MySQL联合查询学习笔记 + 查询总结(详解)

9、 联合查询 union 联合 合并&#xff1a;将多条查询语句的结果合并成一个结果 语法&#xff1a; 查询语句1 UNION 查询语句2 UNION … 应用场景&#xff1a;要查询的结果来自多个表&#xff0c;且多个表之间没有直接的连接关系&#xff0c;但查询的信息相同 特点&#xff…

LeetCode 2124. 检查是否所有 A 都在 B 之前

文章目录1. 题目2. 解题1. 题目 给你一个 仅 由字符 a 和 b 组成的字符串 s 。 如果字符串中 每个 ‘a’ 都出现在 每个 ‘b’ 之前&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;s "aaabbb" 输出&#x…

设计模式—桥接模式

前言 这里以电视遥控器为例子引出桥接模式&#xff0c;首先每个牌子的电视都有一个遥控器&#xff0c;可以设计吧遥控器作为一个抽象类&#xff0c;抽象类中提供遥控器的所有实现&#xff0c;其他具体电视品牌的遥控器都继承这个抽象类 这样的实现使得每个不同型号的电视都有自…

Android8.1怎么装谷歌,谷歌PixelXL安卓9.0/8.1/8.0/7.X安装面具ROOT方案

免费预览&#xff1a;注意1&#xff1a;请提前备份资料&#xff0c;解锁BL会清空所有数据&#xff01;注意2&#xff1a;请提前移除谷歌账户(设置—账户—你的谷歌账户—移除)全套资料在教程末尾1. 解锁BL在手机开机状态下&#xff0c;启用开发者选项并勾选「允许USB调试」和「…

六、MySQL DML数据操纵语言学习笔记(插入、修改、删除详解 + 强化复习)

DML语言 数据操作语言&#xff1a; 插入&#xff1a;insert修改&#xff1a;update删除&#xff1a;delete 一、插入语句 &#xff08;1&#xff09;方式一&#xff1a;经典的插入方式 语法&#xff1a; insert into 表名&#xff08;列名&#xff0c;…&#xff09;values…

LeetCode 2125. 银行中的激光束数量

文章目录1. 题目2. 解题1. 题目 银行内部的防盗安全装置已经激活。 给你一个下标从 0 开始的二进制字符串数组 bank &#xff0c;表示银行的平面图&#xff0c;这是一个大小为 m x n 的二维矩阵。 bank[i] 表示第 i 行的设备分布&#xff0c;由若干 ‘0’ 和若干 ‘1’ 组成。…

2-Second Scrum Meeting-20151202

任务安排 闫昊&#xff1a; 今日完成&#xff1a;设计学习进度的管理。 明日任务&#xff1a;请假。&#xff08;编译计组&#xff0c;压力有点大&#xff09; 金哉仁&#xff1a; 今日完成&#xff1a;继续商讨APP相关界面与设计&#xff0c;安装AndroidStudio。 明日任务&…

android查询所有照片,Android查询外部存储中所有照片

最近写了个自定义相册模块&#xff0c;其中比较核心的算是查询照片了&#xff0c;直接上代码吧val contentUri MediaStore.Files.getContentUri("external")val sortOrder MediaStore.Files.FileColumns.DATE_MODIFIED " DESC"val selection "(${M…

七、MySQL DDL数据定义语言 学习笔记(库和表的创建、修改、删除详解 + 强化复习)

DDL语言 数据定义语言 库和表的管理&#xff1a; 一、库的管理: 创建、修改、删除 二、表的管理: 创建、修改、删除 创建&#xff1a; create 修改&#xff1a; alter 删除&#xff1a; drop 一、库的管理 1、库的创建: 语法&#xff1a; create database [if not exists…

LeetCode 2126. 摧毁小行星(贪心)

文章目录1. 题目2. 解题1. 题目 给你一个整数 mass &#xff0c;它表示一颗行星的初始质量。 再给你一个整数数组 asteroids &#xff0c;其中 asteroids[i] 是第 i 颗小行星的质量。 你可以按 任意顺序 重新安排小行星的顺序&#xff0c;然后让行星跟它们发生碰撞。如果行星…

bzoj:2018 [Usaco2009 Nov]农场技艺大赛

Description Input 第1行&#xff1a;10个空格分开的整数: N, a, b, c, d, e, f, g, h, M Output 第1行&#xff1a;满足总重量最轻&#xff0c;且用度之和最大的N头奶牛的总体重模M后的余数。 Sample Input 2 0 1 5 55555555 0 1 0 55555555 55555555Sample Output 51HINT 样例…

android操作系统+流量,为什么我的安卓操作系统走这么多流量?

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼这是用流量管理软件拦截安卓os偷跑流量的记录&#xff01;国内的软件没个是能拦截系统联网的&#xff01; AppID :-1 应用程序名称:Kernel 总数据包阻塞:170 ff02:0000:0000:0000:0000:0000:0000:0016(1) 163.177.66.11(3) 58.250.…

八、一篇文章快速搞懂MySQL 常见的数据类型(整型、小数、字符型、日期型详解)

常见的数据类型 1、数值型&#xff1a; 整型 小数&#xff1a; 定点数 浮点数 2、字符型&#xff1a; 较短的文本&#xff1a;char、varchar 较长的文本&#xff1a;text、blob&#xff08;较长的二进制数据&#xff09; 3、日期型&#xff1a; 一、整型 1&#xff09;分类…

LeetCode 2129. 将标题首字母大写

文章目录1. 题目2. 解题1. 题目 给你一个字符串 title &#xff0c;它由单个空格连接一个或多个单词组成&#xff0c;每个单词都只包含英文字母。请你按以下规则将每个单词的首字母 大写 &#xff1a; 如果单词的长度为 1 或者 2 &#xff0c;所有字母变成小写。否则&#xf…

九、MySQL常见约束相关知识总结 学习笔记 + 强化复习(六大约束)

常见约束&#xff1a; 一、含义&#xff1a;一种限制&#xff0c;用于限制表中的数据&#xff0c;为了保证表中的数据的准确和可靠性 二、分类&#xff1a;六大约束 1、NOT NULL&#xff1a;非空&#xff0c;用于保证该字段的值不能为空 比如姓名、学号等 2、DEFAULT:默认&…

html 获取鼠标在canvas上的坐标,html5-canvas 检测鼠标在画布上的位置

示例本示例将说明如何获取鼠标相对于画布的位置&#xff0c;例如(0,0)HTML5 Canvas的左上角。的e.clientX和e.clientY将获得相对于文档的顶部位置的鼠标&#xff0c;来改变这种是基于画布的顶部&#xff0c;我们减去left&#xff0c;并right从客户端X和Y的画布位置var canvas …