元類型就是類型的類型。 好比咱們說 5 是 Int 類型,此時 5 是 Int 類型的一個值。可是若是我問 Int 類型佔用多少內存空間,這個時候與具體某個值無關,而和類型的信息相關。若是要寫一個函數,返回一個類型的實例內存空間大小。那麼這個時候的參數是一個類型數據,這個類型數據能夠是直接說明的好比是 Int 類型,也能夠從一個值身上取,好比 5 這個值的類型。這裏的類型數據,就是一個類型的類型,術語表述爲元類型:metaType。html
Swift 中的元類型用 .Type 表示。好比 Int.Type 就是 Int 的元類型。 類型與值有着不一樣的形式,就像 Int 與 5 的關係。元類型也是相似,.Type 是類型,類型的 .self 是元類型的值。git
let intMetatype: Int.Type = Int.self
複製代碼
可能你們平時對元類型使用的比較少,加上這兩個形式有一些接近,一個元類型只有一個對應的值,因此使用的時候經常寫錯:程序員
types.append(Int.Type)
types.append(Int.self)
複製代碼
若是分清了 Int.Type 是類型的名稱,不是值就不會再弄錯了。由於你確定不會這麼寫:github
numbers.append(Int)
複製代碼
得到元類型後能夠訪問靜態變量和靜態方法。其實咱們常常使用元類型,只是有時 Xcode 幫咱們隱藏了這些細節。好比咱們常常用的 tableView 的一個方法:swift
func register(AnyClass?, forCellReuseIdentifier: String)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
複製代碼
這裏的 AnyClass
其實就是一個元類型:app
typealias AnyClass = AnyObject.Type
複製代碼
經過上面的定義咱們能夠知道,AnyClass
就是一個任意類型元類型的別名。 當咱們訪問靜態變量的時候其實也是經過元類型的訪問的,只是 Xcode 幫咱們省略了 .self。下面兩個寫法是等價的。若是能夠不引發歧義,我想沒人會願意多寫一個 self。函數
Int.max
Int.self.max
複製代碼
前面提到經過 type(of:)
和 .self
均可以得到元類型的值。那麼這兩種方式的區別是什麼呢?ui
let instanceMetaType: String.Type = type(of: "string")
let staicMetaType: String.Type = String.self
複製代碼
.self
取到的是靜態的元類型,聲明的時候是什麼類型就是什麼類型。type(of:)
取的是運行時候的元類型,也就是這個實例 的類型。spa
let myNum: Any = 1
type(of: myNum) // Int.type
複製代碼
不少人對 Protocol 的元類型容易理解錯。Protocol 自身不是一個類型,只有當一個對象實現了 protocol 後纔有了類型對象。因此 Protocol.self 不等於 Protocol.Type。若是你寫下面的代碼會獲得一個錯誤:code
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) 複製代碼