招聘
假如你看完觉得这篇文章对你有协助,想和咱们一起同事,欢迎参加字节跳动国际化短视频产品研制团队,团队与岗位介绍在文末。
正文开端
笔者之前一直在探究Objective-C言语的底层原理,有天忽然想到既然iOS未来的编程言语是Swift(其实国外现在已是,国内因大型app的历史包袱过重),我为什么不去研讨Swift言语的底层原理,而在一个注定被扔掉的言语上继续发掘呢。加上现在地点团队也在开端对Swift言语进行尝试,所以探究Swift言语的底层原理显着各方面的价值更大。
说干就干,开端阅读一份项目源码的第一步,是先让这个项目能够通过你自己编译通过而且顺畅跑起来,这一点特别重要。而Swift源码工程比较复杂,搭建起一个调试环境并不容易。因而能自己本地将工程编译运转起来而且可调试关于咱们了解和学习Swift底层的原理能够说是不能绕过的条件。笔者通过2个月周末时间的探索踩了N多坑后总结这篇文章,目的便是协助读者尽快到达本地可调试的状态。
网上材料现状
现在咱们在网上能搜到的这方面的材料十分少,而且几乎都存在以下几个问题
- Swift版别非最新版别:关于咱们来说继续研讨老版别的Swift代码必定没有去研讨最新版的Swift代码有价值。
- 各种编译问题:有一些材料依照其过程会遇到各种编译问题导致无法正常运转,可是并没有全面的处理计划。
- 无法调试:部分材料能调试SwiftRuntime,但却无法调试SwiftCore,而这两个关于研讨Swift底层相同重要。
- 官方计划跑不起来:官方文档的编译计划笔者尝试了很多次都没有办法正常的Run起来,假如有同学处理了请辅导我。
明确方针
关于现在的咱们,最有价值的内容应该是咱们工作中经常用到的内容,比方Foundation中的Array的底层完结,比方Swift中Runtime的底层完结,而像LLVM虚拟机
或许Swift编译器
的底层完结,关于咱们来说还不用太着急.
因而咱们的方针很简单
- 把 Swift 现在最新的安稳版别(5.8.1)本地编译运转起来,而且能够到达SwiftRuntime和SwiftCore本地可调试
盘它
-
Disk Space Prepare
整个Swift工程以及其编译后的DerivedData
缓存差不多需求64G
的磁盘空间,主张在开端前清理本地磁盘,空出至少70G的空间以便寄存工程相关文件。
-
Dependency Install
Swift的本地部署过程中,会依靠cmake
,ninja
,sccache
,因而咱们需求先把这些依靠安装好。
$ brew install cmake ninja sccache
-
Clone&Checkout
首先,咱们在咱们预备寄存咱们Swift源码的地方创立一个文件夹,然后cd
到咱们的目录:
$ mkdir swift-project
$ cd swift-project
然后,咱们在方才新建的目录中履行如下指令拉取对应的 Swift 源码,并cd
到源码目录:
$ git clone --branch swift-5.8.1-RELEASE git@github.com:apple/swift.git
$ cd swift
最后,拉取源码后还须拉取依靠:
$ ./utils/update-checkout --tag swift-5.8.1-RELEASE --clone
这一步会比较耗时,主要是拉取llvm-project
十分耗时,它有将近4G的巨细,能够耐性等待下。当咱们看到下面这样的输出时就阐明成功把代码盘下来了。
-
扩展阅读
- 假如你想跑其他的Swift版别,或许想跑和你Xcode对应版别的Swift,那么能够通过如下指令来检查
-
zixun@zixundeMBP swift % xcrun swift -version swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100) Target: arm64-apple-macosx13.0
-
Edit Code
这个时分假如咱们直接去履行Swift的build-script
脚本会报如下过错:
从报错来看问题出在cxxshim-OSX-ARM64
的脚本里,原因便是cxxshim
试图创立模块目录导致cxxshim
,咱们只需求将 swift/stdlib/public/Cxx/cxxshim/CMakeLists.txt
此文件中的如下指令删去即可。
-
Build Swift
源码修正结束后咱们开端编译,这儿咱们需求运用Swift的官方脚本,也便是在util
目录下的build-script
脚本。以下是我运用的指令:
$ utils/build-script \
--release-debuginfo --debug-swift-stdlib \
--xcode --skip-ios --skip-watchos --skip-tvos \
--skip-early-swiftsyntax --skip-build-benchmarks \
--swift-darwin-supported-archs="$(uname -m)"
这个脚本也会比较耗时,由于有这么多子工程需求编译,大家能够一边跑笔者一边给大家解说下各个参数的意思:
-
--xcode
: 运用CMake
的Xcode生成器,编译完结后会生成一个Swift.xcodeproj
的工程。 -
--release-debuginfo
: 编译出带有RelWithDebInfo
环境变量的工程,相似于Debug
和Release
形式,RelWithDebInfo
会优化一部分,但一起保留调试信息。(比较debug编译速度快十分多) -
--debug-swift-stdlib
: 编译带有调试信息的 Swift标准库,假如想调试 Swift编译器,能够运用--debug-swift
-
--skip-build-benchmarks
: 表明在构建过程中越过编译和构建基准测验的过程。 -
--skip-early-swiftsyntax
: 表示越过earlyswiftsyntax
, 这个不加会编译犯错。 -
--swift-darwin-supported-archs "$(uname -m)"
: 编译需求的架构,$(uname -m)
指令用于获取机器架构环境,例如笔者机器获取的结果为arm64
-
--sccache
: 编译缓存东西,能够进步下一次编译的速度 -
-skip-ios --skip-tvos --skip-watchos
: 越过相应渠道,这儿只编译macOS
渠道。 -
--bootstrapping=off
: 越过迭代构建过程,直接用现有的编译器构建方针编译器,从而节约编译时间。
或许你会遇到error: using unsupported Xcode version
的过错,处理办法很简单,输入如下指令即可
export SKIP_XCODE_VERSION_CHECK=1
这样build就不会检测xcode版别了,然后咱们继续履行上面build-script
指令。
当咱们看到如下界面的时分阐明咱们编译成功了:
编译成功之后,咱们会在目录 swift-project
下得到一个build
文件夹,里边便是咱们 build 的产品:
-
Run Swift
接下来,在上面生成的
build
目录中咱们找到swift-macos-arm64
文件夹下的Swift工程,双击翻开Swift.xcodeproj
来翻开咱们的Swift源码工程。翻开后,咱们能够看到如下弹框。这儿网上很多文章都叫你点击Automatically Create Schemes
来自动生成Schemes,笔者主张是点击Manually Manage Schemes
由于Automatically Create Schemes
会生成一堆scheme,Swift工程自身就现已很大了,再生成这么多scheme会十分卡,咱们只要创立咱们自己需求的就好。点击Manually Manage Schemes
后会弹出如下弹框:
没关系,直接Close
即可。接下来,咱们需求创立一个用来运转验证代码的Target,在 Swift.xcodeproj
工程里边,咱们点击 TARGETS
下面的 +
新建一个调试的 target,咱们挑选 macOS 的Command Line Tool
来创立
Product Name
按自己喜好取个喜欢的名字就行(笔者的名字叫Swiftabc)。接下来咱们为 target 引进依靠,如下图引进ALL_BUILD
:
然后承认一下Build Setting
下Hardened Runtime
是否现已关闭:
Hardened Runtime
是Apple的一种安全维护策略,具体能够查阅Hardened Runtime | Apple Developer Documentation。
接下来咱们如下图点击New Scheme...
来创立咱们需求编译的Scheme:
在如下弹框中挑选咱们创立的Target(Swiftabc),然后点击OK
:
然后咱们点击如下图的Edit Scheme...
来修改咱们需求变更的内容:
在如下图的弹框中咱们将 Target Scheme
的 Build Configuration
修正为ReWithDebInfo
完结了配置,接下去就让咱们Run
一下看看,不出意外的话这个时分就要出意外了,咱们会遇到如下的过错:
这儿其时纠结了笔者好久,苦苦寻找得不到解法,直到发现这个Options.h
的文件在llvm
的工程里,抱着试一试编译llvm
工程的心态编译了一把llvm
处理了这个问题(而且发现Swift工程依靠很多llvm编译产品,必须先编译llvm),llvm
工程在如下目录:
双击翻开后和Swift
工程一样,咱们挑选Manually Manage Schemes
:
然后单击弹框中的Close
按钮:
接下来通过New Scheme...
新建咱们需求的Scheme:
这儿咱们挑选All_BUILD
Scheme,这个Scheme能够把LLVM工程的库全部编译一遍
记住和Swift工程一样,把Build Configuration
改成RelWithDebInfo
:
挑选好之后,咱们开端Build LLVM的工程,LLVM的工程十分大,编译比较久,主张打一把王者后回来看看是否编译完结。当你打完王者回来后会发现不出意外的话这个时分又要出意外了:
一开端笔者想了各种办法去把这个被年代扔掉的i386
架构干掉,可是发现怎样干都干不掉,假如有同学知道怎样干掉请辅导我。回到这个问题自身,其实不需求处理,由于在编译i386
架构之前,我发现咱们需求的arm64
的架构现已编译出来了,所以不处理也没关系。
因而,咱们能够回到Swift
工程继续编译大计,继续等待咱们的编译效果,不出意外的话又又又要出意外了,咱们会得到如下的报错:
这是由于工程找不到cmark,这个问题很好处理,还记住咱们通过脚本生成的工程吗,有一个便是cmark:
翻开cmark-gfm.xcodeproj
工程,挑选All_BUILD
:
相同的,通过Edit Scheme...
把Build Configuration
改成RelWithDebInfo
然后Build一把
不出意外的话这次没有出意外,顺畅Build Success。继续回到Swift
工程继续编译大计。这一波,贼稳,不出意外的话美丽的Build Success
会弹在咱们的XCode界面上。那怎样验证咱们现在是否能够debug Swift的源码呢?
咱们先打一波断点,搜索HeapObject.cpp
文件,然后在文件的第141行打一个断点然后从头Run
一把:
完美,此时咱们现已具备了Swift Runtime
的debug环境。接下来咱们在main.swift
文件中添加咱们想测验的代码。
import Foundation
var arr = Array(1...10)
var arr2 = arr.filter { a in
a > 5
}
print(arr2)
然后咱们在ArrayType.swift
文件中找到filter
函数,然后打上断点
可是当咱们Run起来的时分会发现,这个断点没有进来,怎样肥四!这儿笔者纠结了好久,也尝试了网上的办法将build-script
脚本中的--debug-swift-stdlib
参数替换成--debug-swift
,依旧不行。而且--debug-swift
会把包括编译器,LLVM等所有的host tool
的符号全部编译链接进来,慢的一批,编译一次能够打3到4把王者荣耀。除非哪天咱们要去看编译器的源码,不然珍爱生命,远离--debug-swift
。
咱们回到XCode,发现控制台有如下输出:
warning: Swiftabc was compiled with optimization - stepping may behave oddly; variables may not be available.
大概意思便是咱们试图在运用编译器优化后的代码进行调试,这会让咱们的调试变得很古怪。有了这个头绪,咱们去看看Sequence.swift
文件中filter
的源码:
@inlinable
public __consuming func filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
return try _filter(isIncluded)
}
办法的正上方有个注解@inlinable
,学过C++
的同学应该知道了,这不便是内敛函数么,Swift的@inlinable
和C++相似,内敛会把咱们的办法体仿制在调用的时分在调用函数上打开。咱们要调试源码就要把这个给关掉。
- 在 Xcode 的 “Build Settings” -> “Swift Compiler – Code Generation” -> “Optimization Level” 设置成 “No Optimization”
- 在 Xcode 的 “Build Settings” -> “Apple Compiler – Code Generation” -> “Optimization Level” 设置成 “None”
然后咱们从头Run
一把,能够发现咱们完美的进入了filter
函数:
这个时分,咱们一般都会想看看_filter
函数里的内容,咱们点击step into
,可是却发现进来的却是ContiguousArray
的init
办法
又怎样肥四!咱们先找到_filter
函数
@_transparent
public func _filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
var result = ContiguousArray<Element>()
var iterator = self.makeIterator()
while let element = iterator.next() {
if try isIncluded(element) {
result.append(element)
}
}
return Array(result)
}
发现第一行var result = ContiguousArray<Element>()
好像便是初始化ContiguousArray
,莫非这个函数又被内敛了。看注解并没有@inlinable
,而是@_transparent
。
咱们在工程中大局搜@_transparent
,会搜到如下文档内容:
Semantically, `@_transparent` means something like "treat this operation as
if it were a primitive operation". The name is meant to imply that both the
compiler and the compiled program will "see through" the operation to its
implementation.
This has several consequences:
- **Any calls to a function marked** **`@_transparent`** **MUST be inlined prior to**
**doing dataflow-related diagnostics, even under** **`-Onone`**. This may be
necessary to *catch* dataflow errors.
- Because of this, a `@_transparent` function is implicitly inlinable, in
that changing its implementation most likely will not affect callers in
existing compiled binaries.
- Because of this, a public or `@usableFromInline` `@_transparent` function
MUST only reference public symbols, and MUST not be optimized based on
knowledge of the module it's in. [The former is caught by checks in Sema.]
- **Debug info SHOULD skip over the inlined operations when single-stepping**
**through the calling function**.
This is all that `@_transparent` means.
大概意思便是@_transparent
也是内敛,而且不会受咱们前面设置的Optimization Level
影响.这儿我现在找到的唯一办法便是把@_transparent
注释掉。
// @_transparent
public func _filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
var result = ContiguousArray<Element>()
var iterator = self.makeIterator()
while let element = iterator.next() {
if try isIncluded(element) {
result.append(element)
}
}
return Array(result)
}
注释后,咱们就能够完美的进入到_filter
函数啦
现在,咱们完美的把当时最新的Swift版别5.8.1本地跑了起来,而且关于咱们最有价值的SwiftRuntime
和SwiftCore
都能够调试。
One More Thing
-
当我要切换swift版别从头跑一遍需求怎样做?
-
清空缓存
-
command
+shift
+k
: 清空内存缓存 -
删去
DerivedData
-
-
删去
build
- 删去
swift-project/build
目录下所有内容
- 删去
-
切换版别
- 用脚本切换到你需求的tag,千万不要用
git checkout
,由于swift有很多子库房需求同步切换 -
utils/update-checkout --tag swift-XXX-RELEASE
- 用脚本切换到你需求的tag,千万不要用
-
Build&Run
-
依照这篇文档从头Build和Run
-
-
当我遇到问题处理不了上哪里搜/问?
- Github issue: github.com/apple/swift…
- Swift Forums: forums.swift.org/
- 参考材料
- /post/721956…
- github.com/apple/swift…
招聘
假如你看完觉得这篇文章对你有协助,想和咱们一起同事,欢迎参加字节跳动国际化短视频产品研制团队
团队介绍
国际化短视频产品研制团队,旨在完结字节跳动国际化短视频事务的研制工作,搭建及维护业界领先的产品。
参加咱们,你能接触到包括用户增长、社交 直播、内容发明、内容消费等中心事务场景,支撑产品在全球赛道上高速开展;也能接触到包括服务架构、根底技能等方向上的技能应战,保障事务继续高质量、高效率、且安全地为用户服务;一起还能为不同事务场景提供全面的技能处理计划,优化各项产品指标及用户体会。
在这儿,有大牛带队与大家一起不断探究前沿,打破想象空间。在这儿,你的每一行代码都将服务亿万用户。在这儿,团队专业且朴实,合作气氛相等且轻松。
以下岗位可base上海、杭州、北京。
iOS 高档研制工程师
岗位描绘
- 负责国际化短视频产品内容发现方向的iOS研制、功用完结和产品迭代; 2. 与产品规划配合,深度参加需求评审,功用定义,体会优化等要害讨论; 3. 规划杰出的技能架构,推进并优化代码的健壮性、可维护性,并编写明晰的技能文档。
岗位要求
- 本科及以上计算机相关专业结业,2年以上相关工作经验;
- 有较强的学习才能,将新技能应用到事务实践场景中,推进技能迭代;
- 有杰出的编程习惯,代码结构明晰,命名标准;
- 熟练掌握 Objective-C 或 Swift 言语,熟悉App开发的主流结构和开发形式;
- 对软件产品有强烈的责任心,具备杰出的交流才能和优异的团队协作才能。