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。
例如?UIKit、Foundation?模塊,從這些模塊 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.modulemap,module.map?其實是為了兼容老標準,不過現在 Xcode 里的還都是這個文件名,相信以后會改成新名字。
文件的內容以?Module Map Language?描述,大概語法我從 llvm 官方文檔?3?摘錄一段,大家體會一下:
module MyLib {explicit module A {header "A.h"export *}explicit module B {header "B.h"export *} }
類似上面的語法,描述了?MyLib、MyLib.A、MyLib.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.swiftdoc、ModName.swiftmodule、libswiftModName.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
- 源文件列表 => …
- 依賴列表 =>?CoreFoundation,?Foundation,?Swift
- -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 模塊。這就解決了文章一開始提出的問題:為什么標準庫大部分看起來是自動生成代碼,少部分又好像是人工寫的接口代碼。
參考文獻
Modules – Doug Gregor, Apple, 2012 LLVM DevMeeting?PDF?Video?
為什么應該用模塊取代C/C++中的頭文件??
Modules – Clang 3.5 documentation?
CocoaPods?
轉載于:https://www.cnblogs.com/FranZhou/p/5007754.html
總結
以上是生活随笔為你收集整理的Module System of Swift (简析 Swift 的模块系统)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 专利电子申请那些事儿 |入股不亏 |专利
- 下一篇: QImage