try! Swift 2018 第一天筆記 (1)
Swift Secret Tour
Yuka Ezura
- 一些有關 Swift 的 type 和 closure 的你不知道的小知識
- 用 AST 在這些情況了解 Swift 運作的秘訣
SIL for first time learners
Yusuke Kita
- 基本上就是說
swiftc -emit-sil -O ...
可以看到 swift 的 Swift Intermediate Language (SIL)。
Exploring Clang Modules
Samuel E. Giddins @segiddins
CocoaPods 貢獻者 Samuel E. Giddins 在開發 CocoaPods 時所學會關於 clang modules 的心得。
- 一開始有 c headers 和 #include
- 但是 header 只是簡單的替換,有效能的問題
- 同一個檔案可能會 include 多次,其效果可能是未定義的
- 人們被迫用 #ifndef 之類的 hacks
- 接著有 Objective-C 的 #import 嘗試解決問題
- 接著有 Xcode 的 module map 的 @import
- 在開發 cocoapods 時了解到 module map 的難處:
- Umbrella Directories - 把一整個路徑作 umbrella header
- Explicit Submodules - Submodule,一些可選的 module 功能
- Private Headers - 如名字所述 -- 私有的 header
- Textual Headers - 一些如 macro 的 header
- Requires - 指定 module 所需要的編譯器功能如 objc_arc. blocks 等
- Conflicts - 指定和另一個 framework 有 conflict
- 自動尋找 Module Map - 自動產生 module map
- 相對路徑
- 在開發 cocoapods 時了解到 module map 的難處:
- 就算你只寫 Swift ,你的程式碼也會有 header -- swiftmodule
- 在 cocoapods 裡使用 static library: https://github.com/CocoaPods/CocoaPods/pull/6966
- 總的來說 modules 的設計仍然基於 C 的 headers,它的 module 還沒穩定,很多時很難明,但總算是個比 C 的 header 好的方案
Optimising Swift Code for separation of concern and simplicity
Javier Soto @javi
Pebble, Twitter (Fabric) 和現在 Twitch 的開發者 Javier Soto 關於讓 Swift 提高源碼可讀性的演講,有很充實的例子。
- 編程的原則:簡單、簡潔和明確
- 關注分離:閱讀源碼的機會比編寫源碼多,分離目的和實作可以增加可讀性
- 作者舉了十個例子,其中幾個:
個案:Twitter app 裡計算因長度限制輸入
(前)
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange,
replacementText text: String) -> Bool {
sendButton.isEnabled =
textView.text.utf16.count
+ text.utf16.count
- range.length <= 140
return true
}
(後)
抽出概念 characterCountUsingBackendPolicy
和 characterLimit
,讓人不必知道實作或注解也明白__為何__要那樣做。
private extension String {
var characterCountUsingBackendPolicy: Int {
return utf16.count
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange,
replacementText text: String) -> Bool {
let characters =
textView.text.characterCountUsingBackendPolicy
+ text.characterCountUsingBackendPolicy
- range.length
let characterLimit = 140
sendButton.isEnabled = characters <= characterLimit
return true
}
個案:Twitter app 裡過漏已封鎖用戶的回應
(前)
api.requestReplies(postID: 4815162342) { [weak self] result in
switch result {
case .success(let replies):
var filteredReplies: [Reply] = []
for reply in replies {
if !user.isBlocking(reply.author) {
filteredReplies.append(reply)
}
}
self?.replies = filteredReplies
case .failure:
// ...
}
}
(後)
同樣把 filteringBlockedContent
的概念抽出來,特別是 Collection 的 extension,可以直接用 filter
把目的明確地寫出來。
extension Collection where Element == Reply {
var filteringBlockedContent: [Reply] = {
return filter { !user.isBlocking($0.author) }
}
}
api.requestReplies(postID: 4815162342) { [weak self] result in
switch result {
case .success(let replies):
self?.replies = replies.filteringBlockedContent
case .failure:
// ...
}
}
個案:Auto Layout
(前)
NSLayoutConstraint.activate([
subview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left),
subview.topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top),
view.trailingAnchor.constraint(equalTo: subview.trailingAnchor, constant: insets.right),
view.bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: insets.bottom)
])
(後)
NSLayoutConstraint.activate([
subview.leadingAnchor = view.leadingAnchor + insets.left,
subview.topAnchor = view.topAnchor + insets.top,
view.trailingAnchor = subview.trailingAnchor + insets.right,
view.bottomAnchor = subview.bottomAnchor + insets.bottom
])
// 用 infix operator 去實作
(後 2)
利用自訂 opeartor 和 extension 把重覆的工作簡化。
NSLayoutConstraint.activate(NSLayoutConstraint.anchroing(subview, within: view))
// 實作
extension NSLayoutConstraint {
static func anchroing(_ subview: UIView, within view: UIView, insets: UIEdgeInset = .zero) -> [NSLayoutConstraint] {
// ...
}
}
個案:Table View Cell
(前)
func tableView(_ tableView: UITableVIew, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableVIew, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ID", for: indexPath)
if indexPath.row == 0 {
cell.textLabel.text = "general"
} else if indexPath.row == 1 {
cell.textLabel.text = "notifications"
} else if indexPath.row == 2 {
cell.textLabel.text = "logout"
}
return cell
}
(後)
用 enum 取代不同地方的 if 和 switch。
enum Row {
case general, notification, logout
var title: String {
case .general: return "general"
// ...
}
}
let rows: [Row] = [.general, .notification, .logout]
func tableView(_ tableView: UITableVIew, numberOfRowsInSection section: Int) -> Int {
return rows.count
}
func tableView(_ tableView: UITableVIew, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ID", for: indexPath)
cell.textLabel.text = rows[indexPath.row].title
return cell
}
個案:應對不同 SDK 的新 API
(前)
不同的地方有很多 #available ,重覆且不易閱讀
if #available(iOS 11.0, *) {
constraints = [
subview.topAnchor = view.safeAreaLayoutGuide.topAnchor,
// ...
]
} else {
constraints = [
subview.topAnchor = view.topAnchor,
// ...
]
}
(後)
自己做一個兼容的 API ,讓不同版本也可以使用同一特源碼,對應不同版本的部份只在需要的地方出現
extension UIView {
var tw_safeAreaLayoutGuide: UILayoutGuide {
if #available(iOS 11.0, *) {
return safeAreaLayoutGuide
}
// 自己實作一個 UILayoutGuide
}
}
個案:UIViewController 的狀態
(前)
由於載入 user info 是非同步的,有些資料和 view 可能有不同狀態,也有可能是 nil.
final class ProfileViewController: UIViewController {
var userInfo: UserInfo?
var headerView: ProfileHeaderView?
var spinner: UIActivityIndicatorView?
var retryButton: UIButton?
}
(後)
使用 enum 表達不同的 UI 狀態,直接用 enum value 去代表本來需要用 optional 的部份,這樣 UI Code 就可以不用使用 optional。
final class ProfileViewController: UIViewController {
enum State {
case pending
case loading(spinner: UIActivityIndicatorView)
case failed(retryButton: UIButton)
case loaded(userInfo: UserInfo, headerView: ProfileHeaderView)
}
var state: State = .pending
func loadUserDetails() {
state = .loading(spinner: UIActivityIndicatorView())
api.requestUserDetails(userID) { [weak self] result in
switch result {
case let .success(userInfo):
self?.state = .loaded(userInfo: userInfo, headerView: createHeaderView(userInfo))
case let .failure:
self?.state = .failed(createRetryButton())
}
}
}
}
總結:
- 利用 local scope 的extension 和 value 去優化可讀性
- DRY -- Don't Repeat Yourself
- Swift enum 是個好東西
Getting to Know the Responder Chain
Samuel Goodwin
- Responder Chain 簡介
- Responder Chain 的應用
- Send action 去 nil target 就可以把 message 跑一遍整個 responder chain,讓懂得那 message 的 responder 去回應。這種實作讓不同元件可以互相溝通而不需要知道對方存在
(待續)