理解 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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341