Swift 解读 – 前言

作者 : 开心源码 本文共4488个字,预计阅读时间需要12分钟 发布时间: 2022-05-13 共207人阅读

源起

四年前,在我强大的忽悠能力下,成功说服了领导使用 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-sourcecd swift-source# 拉取源码git clone  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 的那些事。

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

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Swift 解读 – 前言

发表回复