Swift 解讀 - 前言

源起

四年前串远,在我強(qiáng)大的忽悠能力下,成功說服了領(lǐng)導(dǎo)使用 Swift 來開發(fā)新 APP芽卿。那時(shí)候自己也沒有多想太多揭芍,例如穩(wěn)定性問題胳搞、效率問題卸例、維護(hù)成本問題等等称杨。選擇 Swift 開發(fā)的理由,僅是懷著學(xué)習(xí)的心態(tài)筷转,以及對(duì) OC 反人類的語法感到厭惡姑原。也正因選擇了 Swift,這幾年來呜舒,我每年都得以學(xué)習(xí)一門新語言锭汛,它們分別為 Swift 2、Swift 3袭蝗、Swift 5…...

在這幾年時(shí)間了唤殴,我做兩件非常有意思的小事情,第一件是將 OC 的 APP 用 Swift 來重寫到腥,以跟進(jìn)時(shí)代的步伐朵逝;第二件是為了滿足公司對(duì)"熱"的要求,而將 Swift 的代碼改成 OC乡范。雖說這兩件微不足道的小事情并不是發(fā)生在同一家公司中配名,但卻感觸深刻,每每想起晋辆,總有那么一種沖動(dòng)渠脉,覺得自己需要寫點(diǎn)什么。

現(xiàn)在瓶佳,Swift 來到了 5.0 這個(gè)重要的里程碑上芋膘,作為一名 Swifter,希望自己能對(duì)這門語言有更深入的了解霸饲,同時(shí)嘗試給大家分享下自己對(duì)這門語言的一些經(jīng)驗(yàn)與見解索赏。在這個(gè)系列中,主要會(huì)針對(duì) Swift 的 "現(xiàn)代"贴彼、"安全"潜腻、"快" 三個(gè)方面,從源碼層去了解其具體的實(shí)現(xiàn)器仗,當(dāng)然在這個(gè)系列中還會(huì)包含 Swift 標(biāo)準(zhǔn)庫的應(yīng)用與實(shí)現(xiàn)進(jìn)行分析融涣,從底層角度了解 Swift 這門有那么一點(diǎn)意思的語言。

在立下這個(gè) flag 的同時(shí)精钮,也給自己挖了一個(gè)巨坑威鹿,這希望自己在填坑的過程中,可以與大家共同學(xué)習(xí)成長轨香,在 Swift 造詣上忽你,更上一層樓。

環(huán)境準(zhǔn)備

開發(fā)環(huán)境

masOS 10.14.5臂容,Xcode 10.3科雳,默認(rèn)使用當(dāng)下最新的正式版本根蟹。

編譯 Swift 源碼

我們可以在 GitHub 上的 Swift 倉庫 查看支 Swift 的所有源碼代碼,然后 Clone 到本地進(jìn)行閱讀糟秘,如果直接閱讀 Clone 下來的 Swift 庫简逮,你們發(fā)現(xiàn)大量的 .swift.gyb 的文件,GYB 文件是蘋果團(tuán)隊(duì)預(yù)處理的一種文件尿赚,里面包含著其它語言散庶,會(huì)影響到我們對(duì) Swift 源碼的閱讀,所以我們需要對(duì)從 GitHub Clone 的代碼進(jìn)行一次編譯凌净,生成閱讀性更強(qiáng)的 Swift 代碼悲龟。具體操作如下:

# 安裝編譯工具
brew install cmake ninja
# 創(chuàng)建源碼放置目錄
mkdir swift-source
cd swift-source
# 拉取源碼
git clone https://github.com/apple/swift.git
# 拉取編譯所需依賴的倉庫
./swift/utils/update-checkout --clone

最后一行命令會(huì)拉取編譯 Swift 庫所依賴的倉庫,在天朝冰寻,如果下載失敗或者下載慢躲舌,可能要多試幾次才能成功,我們都懂性雄。完成上面的步驟后没卸,我們可以執(zhí)行 apple 給我們提供好的 build 腳本進(jìn)行編譯:

./swift/utils/build-script -x -R
  • -x 會(huì)生成一個(gè) Xcode 的項(xiàng)目,我們可能通過 Xcode 來閱讀源碼
  • -R 指的是使用 release 進(jìn)行編譯秒旋,編譯更快约计,產(chǎn)物也更小,機(jī)器快迁筛,硬盤大的小伙伴們可無視煤蚌。

整個(gè)編譯過程是比較漫長的,當(dāng)然最終編譯速度最終還是得看機(jī)器的性能细卧,歐洲人請(qǐng)無視尉桩。

更新 Swift 源碼

隨著 Swift 的迭代開發(fā),我們想查看 Swift 新特性的源碼時(shí)贪庙,就需要我們?nèi)ジ?Swift 源碼蜘犁,當(dāng)然我們不需要重新執(zhí)行上面的操作,我們只需要執(zhí)行 update-checkout 腳本并重新編譯:

./swift/utils/update-checkout
./swift/utils/build-script -x -R

切換 Swift 版本

如果我們想查看某個(gè)版本中特有的特性止邮,這個(gè)時(shí)候我們需要切換 Swift 的版本这橙,但是如果只是簡單地切換 Swift 倉庫的分支是無法編譯通過的,他對(duì)應(yīng)的依賴也需要變更导披,我們可以通過 apple 提供的腳本進(jìn)行切換版本:

./swift/utils/update-checkout --tag swift-5.0-RELEASE

gyb 轉(zhuǎn)換為 Swift 代碼

因?yàn)?Swift 的編譯需要較長時(shí)候屈扎,并且占用大量硬盤空間,那么我們可以針對(duì)單個(gè) gyb 文件進(jìn)行轉(zhuǎn)換:

./swift/utils/gyb \
  --line-directive '' \
  -o ./xxxxxxx/Sequence.swift \
  ./swift/stdlib/public/core/Sequence.swift.gyb
  • --line-directive '' 用于在生成的文件中去掉不必要的說明撩匕;
  • -o 指定輸出目錄鹰晨;
  • 最后是指定需要轉(zhuǎn)換的 gyb 文件目錄。

Hello World

在編譯完成 Swift 源碼后,可以通過 Xcode 來查看 Swift 的源碼模蜡。當(dāng)然漠趁,我們還是從萬碼起源的 Hello World 開始去嘗試閱讀 Swift 源碼。

print("Hello World")

這可能是你曾經(jīng)寫下的第一行代碼哩牍,那么棚潦,我們來看看在 Swift 中令漂,這行代碼到底是怎么實(shí)現(xiàn)的膝昆。

查看源碼

如果你想查看一個(gè)方法的源碼的方式有兩種:

  • 如果你知道是具體的那個(gè)文件的類,則可以通過打開對(duì)應(yīng)的文件查看叠必,但是這種方法通常都是不太好使的荚孵,畢竟我們記不住那么多文件名;
  • 通過全局搜索:**public func 方法名( **的方式來定位 public 方法的具體位置纬朝,協(xié)議收叶、類也是同理。

因?yàn)?print() 方法的源碼放在 Print.swift 文件中共苛,所以上面所說的兩種方式都是可行的判没。

print 源碼解讀

通過上述方式,我們可以找到 print() 的源碼實(shí)現(xiàn)如下:

public func print(
  _ items: Any...,
  separator: String = " ",
  terminator: String = "\n"
) {
  if let hook = _playgroundPrintHook {
    var output = _TeeStream(left: "", right: _Stdout())
    _print(items, separator: separator, terminator: terminator, to: &output)
    hook(output.left)
  }
  else {
    var output = _Stdout()
    _print(items, separator: separator, terminator: terminator, to: &output)
  }
}

首先我們可以看到 print() 方法是一個(gè)全局的 public 方法隅茎,所以我們可以在任意的地方調(diào)用他澄峰,其次該方法支持三個(gè)參數(shù),最后兩位是帶默認(rèn)值的參數(shù)辟犀。這就是給我們最直觀的感覺俏竞,接下來我們來看下他的內(nèi)部實(shí)現(xiàn):

if let hook = _playgroundPrintHook {...} else {...}

判斷當(dāng)前開發(fā)環(huán)境是不是 playground,如果是則將 output 定向到了 _TeeStream堂竟,如果不是則使用 _Stdout魂毁。

01.png

這樣我就可以很好理解 Playground 右邊顯示值的功能是怎么實(shí)現(xiàn)的了。

接下來我們繼續(xù)來閱讀 print() 的源碼出嘹,不管是否 plyground席楚,最終都會(huì)調(diào)用 _print() 方法:

internal func _print<Target : TextOutputStream>(
  _ items: [Any],
  separator: String = " ",
  terminator: String = "\n",
  to output: inout Target
) {
  var prefix = ""
  output._lock()
  defer { output._unlock() }
  for item in items {
    output.write(prefix)
    _print_unlocked(item, &output)
    prefix = separator
  }
  output.write(terminator)
}

我們可以看到,首先將 output 加鎖税稼,然后通過 defer 關(guān)鍵字在函數(shù) return 前將 output 解鎖酣胀,保證了 output 在 write 過程中是線程安全的。然后我們再看 _print_unlocked() 方法:

internal func _print_unlocked<T, TargetStream : TextOutputStream>(
  _ value: T, _ target: inout TargetStream
) {
  if _isOptional(type(of: value)) {
    let debugPrintable = value as! CustomDebugStringConvertible
    debugPrintable.debugDescription.write(to: &target)
    return
  }
  if case let streamableObject as TextOutputStreamable = value {
    streamableObject.write(to: &target)
    return
  }
  if case let printableObject as CustomStringConvertible = value {
    printableObject.description.write(to: &target)
    return
  }
  if case let debugPrintableObject as CustomDebugStringConvertible = value {
    debugPrintableObject.debugDescription.write(to: &target)
    return
  }
  let mirror = Mirror(reflecting: value)
  _adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)
}

從源碼來看娶聘,可以看出來闻镶,如果輸入的對(duì)象如果實(shí)現(xiàn) TextOutputStreamable,則打印出來的是它的值丸升,如果它實(shí)現(xiàn)的是 CustomStringConvertible 或者 CustomDebugStringConvertible 時(shí)铆农,print 實(shí)際打印出來的 description 內(nèi)容。

CustomStringConvertible 和 CustomDebugStringConvertible 都是熟悉的協(xié)議,沒有必要過多但是絕對(duì)墩剖,但對(duì)于 TextOutputStreamable 這種比較陌生的協(xié)議猴凹,必要時(shí)需要我們?nèi)ゲ殚喿x文檔:

02.png

遇到陌生的關(guān)鍵字或者協(xié)議時(shí),不需要著急岭皂,果爸爸的文檔一般都很詳細(xì)的郊霎,可以優(yōu)先查看文檔,閱讀 API 文檔可以快速了解學(xué)習(xí)一門語言爷绘。

繼續(xù)閱讀源碼發(fā)現(xiàn)书劝,如 value 實(shí)現(xiàn)以上協(xié)議時(shí)狂芋,則可以輸出相應(yīng)的描述內(nèi)容薇宠,如果未實(shí)現(xiàn)以上協(xié)議:

let mirror = Mirror(reflecting: value)
_adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)

則通過 Mirror 映射輸出相應(yīng)的值, _adHocPrint_unlocked() 的具體實(shí)現(xiàn)就不繼續(xù)糾纏觅够,否則就沒完沒了陶因,感覺興趣的讀者可以自行研究下去骡苞。

小結(jié)

作為開篇,寫到這里就結(jié)束了楷扬,介紹了如何搭建環(huán)境和一個(gè)簡單例子解幽,在后面章節(jié)里,我們再來詳細(xì)聊聊 Swift 的那些事烘苹。

作為一名作者躲株,最大的成就感就是讀者在看了你的文章后,又有提筆寫文的沖動(dòng)螟加。作為一名程序員的我徘溢,最大的成就感莫過于你看了我的文章后,有想打開電腦寫下幾行代碼的沖動(dòng)捆探。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末然爆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子黍图,更是在濱河造成了極大的恐慌曾雕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件助被,死亡現(xiàn)場離奇詭異剖张,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)揩环,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門搔弄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丰滑,你說我怎么就攤上這事顾犹。” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵炫刷,是天一觀的道長擎宝。 經(jīng)常有香客問我,道長浑玛,這世上最難降的妖魔是什么绍申? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮顾彰,結(jié)果婚禮上极阅,老公的妹妹穿的比我還像新娘。我一直安慰自己拘央,他們只是感情好涂屁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布书在。 她就那樣靜靜地躺著灰伟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪儒旬。 梳的紋絲不亂的頭發(fā)上栏账,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音栈源,去河邊找鬼挡爵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甚垦,可吹牛的內(nèi)容都是我干的茶鹃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼艰亮,長吁一口氣:“原來是場噩夢啊……” “哼闭翩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迄埃,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤疗韵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后侄非,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蕉汪,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年逞怨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了者疤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叠赦,死狀恐怖驹马,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤窥翩,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布业岁,位于F島的核電站,受9級(jí)特大地震影響寇蚊,放射性物質(zhì)發(fā)生泄漏笔时。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一仗岸、第九天 我趴在偏房一處隱蔽的房頂上張望允耿。 院中可真熱鬧,春花似錦扒怖、人聲如沸较锡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚂蕴。三九已至,卻和暖如春俯邓,著一層夾襖步出監(jiān)牢的瞬間骡楼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工稽鞭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸟整,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓朦蕴,卻偏偏與公主長得像篮条,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吩抓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容