Swift 解读 - 前言

源起

四年前,在我强大的忽悠能力下,成功说服了领导使用 Swift 来开发新 APP。那时候自己也没有多想太多,例如稳定性问题、效率问题、维护成本问题等等。选择 Swift 开发的理由,仅是怀着学习的心态,以及对 OC 反人类的语法感到厌恶。也正因选择了 Swift,这几年来,我每年都得以学习一门新语言,它们分别为 Swift 2、Swift 3、Swift 5…...

在这几年时间了,我做两件非常有意思的小事情,第一件是将 OC 的 APP 用 Swift 来重写,以跟进时代的步伐;第二件是为了满足公司对"热"的要求,而将 Swift 的代码改成 OC。虽说这两件微不足道的小事情并不是发生在同一家公司中,但却感触深刻,每每想起,总有那么一种冲动,觉得自己需要写点什么。

现在,Swift 来到了 5.0 这个重要的里程碑上,作为一名 Swifter,希望自己能对这门语言有更深入的了解,同时尝试给大家分享下自己对这门语言的一些经验与见解。在这个系列中,主要会针对 Swift 的 "现代"、"安全"、"快" 三个方面,从源码层去了解其具体的实现,当然在这个系列中还会包含 Swift 标准库的应用与实现进行分析,从底层角度了解 Swift 这门有那么一点意思的语言。

在立下这个 flag 的同时,也给自己挖了一个巨坑,这希望自己在填坑的过程中,可以与大家共同学习成长,在 Swift 造诣上,更上一层楼。

环境准备

开发环境

masOS 10.14.5,Xcode 10.3,默认使用当下最新的正式版本。

编译 Swift 源码

我们可以在 GitHub 上的 Swift 仓库 查看支 Swift 的所有源码代码,然后 Clone 到本地进行阅读,如果直接阅读 Clone 下来的 Swift 库,你们发现大量的 .swift.gyb 的文件,GYB 文件是苹果团队预处理的一种文件,里面包含着其它语言,会影响到我们对 Swift 源码的阅读,所以我们需要对从 GitHub Clone 的代码进行一次编译,生成阅读性更强的 Swift 代码。具体操作如下:

# 安装编译工具
brew install cmake ninja
# 创建源码放置目录
mkdir swift-source
cd swift-source
# 拉取源码
git clone https://github.com/apple/swift.git
# 拉取编译所需依赖的仓库
./swift/utils/update-checkout --clone

最后一行命令会拉取编译 Swift 库所依赖的仓库,在天朝,如果下载失败或者下载慢,可能要多试几次才能成功,我们都懂。完成上面的步骤后,我们可以执行 apple 给我们提供好的 build 脚本进行编译:

./swift/utils/build-script -x -R
  • -x 会生成一个 Xcode 的项目,我们可能通过 Xcode 来阅读源码
  • -R 指的是使用 release 进行编译,编译更快,产物也更小,机器快,硬盘大的小伙伴们可无视。

整个编译过程是比较漫长的,当然最终编译速度最终还是得看机器的性能,欧洲人请无视。

更新 Swift 源码

随着 Swift 的迭代开发,我们想查看 Swift 新特性的源码时,就需要我们去更新 Swift 源码,当然我们不需要重新执行上面的操作,我们只需要执行 update-checkout 脚本并重新编译:

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

切换 Swift 版本

如果我们想查看某个版本中特有的特性,这个时候我们需要切换 Swift 的版本,但是如果只是简单地切换 Swift 仓库的分支是无法编译通过的,他对应的依赖也需要变更,我们可以通过 apple 提供的脚本进行切换版本:

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

gyb 转换为 Swift 代码

因为 Swift 的编译需要较长时候,并且占用大量硬盘空间,那么我们可以针对单个 gyb 文件进行转换:

./swift/utils/gyb \
  --line-directive '' \
  -o ./xxxxxxx/Sequence.swift \
  ./swift/stdlib/public/core/Sequence.swift.gyb
  • --line-directive '' 用于在生成的文件中去掉不必要的说明;
  • -o 指定输出目录;
  • 最后是指定需要转换的 gyb 文件目录。

Hello World

在编译完成 Swift 源码后,可以通过 Xcode 来查看 Swift 的源码。当然,我们还是从万码起源的 Hello World 开始去尝试阅读 Swift 源码。

print("Hello World")

这可能是你曾经写下的第一行代码,那么,我们来看看在 Swift 中,这行代码到底是怎么实现的。

查看源码

如果你想查看一个方法的源码的方式有两种:

  • 如果你知道是具体的那个文件的类,则可以通过打开对应的文件查看,但是这种方法通常都是不太好使的,毕竟我们记不住那么多文件名;
  • 通过全局搜索:**public func 方法名( **的方式来定位 public 方法的具体位置,协议、类也是同理。

因为 print() 方法的源码放在 Print.swift 文件中,所以上面所说的两种方式都是可行的。

print 源码解读

通过上述方式,我们可以找到 print() 的源码实现如下:

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() 方法是一个全局的 public 方法,所以我们可以在任意的地方调用他,其次该方法支持三个参数,最后两位是带默认值的参数。这就是给我们最直观的感觉,接下来我们来看下他的内部实现:

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

判断当前开发环境是不是 playground,如果是则将 output 定向到了 _TeeStream,如果不是则使用 _Stdout。

01.png

这样我就可以很好理解 Playground 右边显示值的功能是怎么实现的了。

接下来我们继续来阅读 print() 的源码,不管是否 plyground,最终都会调用 _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 关键字在函数 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)
}

从源码来看,可以看出来,如果输入的对象如果实现 TextOutputStreamable,则打印出来的是它的值,如果它实现的是 CustomStringConvertible 或者 CustomDebugStringConvertible 时,print 实际打印出来的 description 内容。

CustomStringConvertible 和 CustomDebugStringConvertible 都是熟悉的协议,没有必要过多但是绝对,但对于 TextOutputStreamable 这种比较陌生的协议,必要时需要我们去查阅读文档:

02.png

遇到陌生的关键字或者协议时,不需要着急,果爸爸的文档一般都很详细的,可以优先查看文档,阅读 API 文档可以快速了解学习一门语言。

继续阅读源码发现,如 value 实现以上协议时,则可以输出相应的描述内容,如果未实现以上协议:

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

则通过 Mirror 映射输出相应的值, _adHocPrint_unlocked() 的具体实现就不继续纠缠,否则就没完没了,感觉兴趣的读者可以自行研究下去。

小结

作为开篇,写到这里就结束了,介绍了如何搭建环境和一个简单例子,在后面章节里,我们再来详细聊聊 Swift 的那些事。

作为一名作者,最大的成就感就是读者在看了你的文章后,又有提笔写文的冲动。作为一名程序员的我,最大的成就感莫过于你看了我的文章后,有想打开电脑写下几行代码的冲动。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容