元类型
元类型就是类型的类型。 比如我们说 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 程序员保护协会