理解 Swift 中的元类型:.Type 与 .self

ReyZhang
移动开发领域新星创作者
2023-05-31 10:14:08

元类型

元类型就是类型的类型

比如我们说 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

...全文
成就一亿技术人!
拼手气红包 5.00元
347 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

420

社区成员

发帖
与我相关
我的任务
社区描述
专注移动ios平台的软件开发,多年的一线研发经验,实战经验丰富,只为你呈现有价值的信息。
iosflutterandroid 技术论坛(原bbs) 山东省·青岛市
社区管理员
  • ReyZhang
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧