元類型
元類型就是類型的類型。
比如我們說 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
Whats Type And Self Swift Metatypes
ANYCLASS,元類型和 .SELF
微博:@沒故事的卓同學
如果想與我有更密切的交流也可以加入我的知識星球:iOS 程序員保護協會