理解 Swift 中的元類型:.Type 與 .self

元類型

元類型就是類型的類型。
比如我們說 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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章