元型別
元型別就是型別的型別。
比如我們說 5 是 Int 型別,此時 5 是 Int 型別的一個值。但是如果我問 Int 型別佔用多少記憶體空間,這個時候與具體某個值無關,而和型別的資訊相關。如果要寫一個函式,返回一個型別的例項記憶體空間大小。那麼這個時候的引數是一個型別資料,這個型別資料可以是直接說明的比如是 Int 型別,也可以從一個值身上取,比如 5 這個值的型別。這裡的型別資料,就是一個型別的型別,術語表述為元型別:metaType。
.Type 與 .self
Swift 中的元型別用 .Type 表示。比如 Int.Type 就是 Int 的元型別。
型別與值有著不同的形式,就像 Int 與 5 的關係。元型別也是類似,.Type 是型別,型別的 .self 是元型別的值。
let intMetatype: Int.Type = Int.self
複製程式碼
可能大家平時對元型別使用的比較少,加上這兩個形式有一些接近,一個元型別只有一個對應的值,所以使用的時候常常寫錯:
types.append(Int.Type)
types.append(Int.self)
複製程式碼
如果分清了 Int.Type 是型別的名稱,不是值就不會再弄錯了。因為你肯定不會這麼寫:
numbers.append(Int)
複製程式碼
AnyClass
獲得元型別後可以訪問靜態變數和靜態方法。其實我們經常使用元型別,只是有時 Xcode 幫我們隱藏了這些細節。比如我們經常用的 tableView 的一個方法:
func register(AnyClass?, forCellReuseIdentifier: String)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
複製程式碼
這裡的 AnyClass
其實就是一個元型別:
typealias AnyClass = AnyObject.Type
複製程式碼
通過上面的定義我們可以知道,AnyClass
就是一個任意型別元型別的別名。
當我們訪問靜態變數的時候其實也是通過元型別的訪問的,只是 Xcode 幫我們省略了 .self。下面兩個寫法是等價的。如果可以不引起歧義,我想沒人會願意多寫一個 self。
Int.max
Int.self.max
複製程式碼
type(of:) vs .self
前面提到通過 type(of:)
和 .self
都可以獲得元型別的值。那麼這兩種方式的區別是什麼呢?
let instanceMetaType: String.Type = type(of: "string")
let staicMetaType: String.Type = String.self
複製程式碼
.self
取到的是靜態的元型別,宣告的時候是什麼型別就是什麼型別。type(of:)
取的是執行時候的元型別,也就是這個例項 的型別。
let myNum: Any = 1
type(of: myNum) // Int.type
複製程式碼
Protocol
很多人對 Protocol 的元型別容易理解錯。Protocol 自身不是一個型別,只有當一個物件實現了 protocol 後才有了型別物件。所以 Protocol.self 不等於 Protocol.Type。如果你寫下面的程式碼會得到一個錯誤:
protocol MyProtocol { }
let metatype: MyProtocol.Type = MyProtocol.self
複製程式碼
正確的理解是 MyProtocol.Type 也是一個有效的元型別,那麼就需要是一個可承載的型別的元型別。所以改成這樣就可以了:
struct MyType: MyProtocol { }
let metatype: MyProtocol.Type = MyType.self
複製程式碼
那麼 Protocol.self 是什麼型別呢?為了滿足你的好奇心蘋果為你造了一個型別:
let protMetatype: MyProtocol.Protocol = MyProtocol.self
複製程式碼
一個實戰
為了讓大家能夠熟悉元型別的使用我舉一個例子。
假設我們有兩個 Cell 類,想要一個工廠方法可以根據型別初始化物件。下面是兩個 Cell 類:
protocol ContentCell { }
class IntCell: UIView, ContentCell {
required init(value: Int) {
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class StringCell: UIView, ContentCell {
required init(value: String) {
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
複製程式碼
工廠方法的實現是這樣的:
func createCell(type: ContentCell.Type) -> ContentCell? {
if let intCell = type as? IntCell.Type {
return intCell.init(value: 5)
} else if let stringCell = type as? StringCell.Type {
return stringCell.init(value: "xx")
}
return nil
}
let intCell = createCell(type: IntCell.self)
複製程式碼
當然我們也可以使用型別推斷,再結合泛型來使用:
func createCell<T: ContentCell>() -> T? {
if let intCell = T.self as? IntCell.Type {
return intCell.init(value: 5) as? T
} else if let stringCell = T.self as? StringCell.Type {
return stringCell.init(value: "xx") as? T
}
return nil
}
// 現在就根據返回型別推斷需要使用的元型別
let stringCell: StringCell? = createCell()
複製程式碼
在 Reusable 中的 tableView 的 dequeue 採用了類似的實現:
func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath, cellType: T.Type = T.self) -> T
where T: Reusable {
guard let cell = self.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else {
fatalError("Failed to dequeue a cell")
}
return cell
}
複製程式碼
dequeue 的時候就可以根據目標型別推斷,不需要再額外宣告元型別:
class MyCustomCell: UITableViewCell, Reusable
tableView.register(cellType: MyCustomCell.self)
let cell: MyCustomCell = tableView.dequeueReusableCell(for: indexPath)
複製程式碼
Reference
- 微博:@沒故事的卓同學
- 如果想與我有更密切的交流也可以加入我的知識星球:iOS 程式設計師保護協會