線上收聽與節目筆記
又是歡樂的一集!今天聊起 #weakself挑戰賽的 Type Erasure 與 Opaque Return Type。不過,怎麼某人的聲音到後半段就 erased 了!?
開場
- 上一集成為最熱門集數,所以跟新朋友打招呼、解釋一下我們的名稱
- weak self 這個名稱,寫 Swift 或 iOS 工程師一看就知道。但我們其實沒有中文名稱,大家可以幫忙想
- weak self, strong community
- 昨天得知某人間接透過社群找到工作
- 鐵人賽
- xcode-install
我恨 PAT
- PAT 指的是 Protocol with Associated Type,如下:
// 有 associatedtype 的 protocol
public protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
// 有 Self 的 protocol, Hashable 的 Self 來自所繼承的 Equatable
public protocol Hashable: Equatable {
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
func _rawHashValue(seed: Int) -> Int
}
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
你是忘記還是害怕想起?
// 以下寫法都會被 compiler 抱怨
// 有 Self
func generateDictionaryKey() -> Hashable {
return 8
}
// 有 associatedtype
func getIterator() -> IteratorProtocol {
return ["1", "2", "3"].makeIterator()
}
- PAT 三不能:
- 不能做為變數型別用
- 不能做為 function 的 return
- 不能放在 Collection 裡
Type Erasure 為何而生
PAT 難搞的地方有兩種:
- 有
Self
的 Protocol:Hashable
- 有
associatedtype
的 Protocol:IteratorProtocol
對應的做法:
AnyHashable
:黑魔法 C++- 對
AnyHashable
有興趣請右轉 AnyIterator
用 generic struct 解決,如下:
// 利用 init with closure 來自動代入 `Element` 真實的型別,而不是直接用 <某型別> 的方式來指定
// 先宣告一個 generic struct
struct MyAnyIterator<Element> {
// 內部使用的 Box ,裡面就是把 closure 存起來,本身也滿足 IteratorProtocol
private class AnyIteratorBox<Element>: IteratorProtocol {
typealias Base = () -> Element?
private var _base: Base
init(_ base: @escaping Base) {
self._base = base
}
func next() -> Element? {
return _base()
}
}
// 把 closure 包起來的 box ( 簡單一點也可讓 box 就是個 closure type 直接存 closure)
private let box: AnyIteratorBox<Element>
init(_ body: @escaping () -> Element?) {
self.box = AnyIteratorBox(body)
}
}
// 再讓本身滿足 IteratorProtocol
extension MyAnyIterator: IteratorProtocol {
mutating func next() -> Element? {
return box.next()
}
}
// Fibonacci iterator
// 可以看到我們仍然要在外部定義好 closure 與它的 return type 來決定 Element
var state = (0, 1)
var myIterator = MyAnyIterator { () -> Int in
let upcomingNumber = state.0
state = (state.1, state.0 + state.1)
return upcomingNumber
}
// Xcode 按 option + 左鍵會看到 myIterator 是 MyAnyIterator<Int>
print(myIterator.next()) // 0
print(myIterator.next()) // 1
print(myIterator.next()) // 1
print(myIterator.next()) // 2
print(myIterator.next()) // 3
print(myIterator.next()) // 5
Opaque Return Type
- weakself 的中文名稱是個 Opaque Type (Caller 決定 generic 是什麼)
- Opaque Return Type 則由 Callee 決定 generic 是什麼
- 避免 generiception:
public typealias LazyCompactMapCollection<Elements, ElementOfResult>
-> <C: Collection> C where C.Element == ElementOfResult
= LazyMapSequence<LazyFilterSequence<LazyMapSequence<Elements, ElementOfResult?>>, ElementOfResult>
- 也是對於 Type Erasure 的一種根本性解決手法
// 想像以下 function 定義在某一個 module 裡,外部無法決定 Element 是什麼
// 此 func 你可以呼叫但裡面實作看不到
func getIterator() -> some IteratorProtocol {
var state = (0, 1)
return MyAnyIterator { () -> Int in
let upcomingNumber = state.0
state = (state.1, state.0 + state.1)
return upcomingNumber
}
}
// 以下是我們在外部呼叫,在外面呼叫的人只知道它是 some IteratorProtocol
// 從 Xcode 的提示只知道 someIterator 型別是 `some IteratorProtocol`
var someIterator = getIterator()
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
那第三個問題,不能把 PAT 放入 collection 呢?
抱歉,還無法🤷♂️
更多 weak self
- 主持人 一三、波肥、喬喬
- 官網 https://weakself.dev
- Twitter @weak_self
- 提問箱
- 贊助 weak self