AnyClass,元类型和. self
AnyCalss
在Swift中被一个 typealias
所定义:
typealias AnyClass = AnyObject.Type
通过 AnyObject.Type
这种方式得到的是一个元类型(Meta)。在声明时我们总是在类型的名称后面加上 .Type
。比如A.Type代表的是A这个类型的类型。也就是说,我们可以声明一个元类型来存储A这个类型本身,而在从A中取出其类型时,我们需要使用到 .self
:
class A {}
let typeA: A.Type = A.self
其实在Swift中,
.self
可以用在类型后面取得类型本身,也可以用在某个实例的后面获得这个实例本身。
了解这个基础之后,我们就明白 AnyObject.Type
, 或者说 AnyClass
所表达的就是任意类型本身。
class A {}
let typeA: AnyClass = A.self
这样,要是A中有一个类方法时,我们就可以通过 typeA 来对其进行调用了:
class A {
class func method() {
print("Hello")
}
}
let typeA: A.Type = A.self
typeA.method()
// 或者
let anyClass: AnyClass = A.self
(anyClass as! A.Type).method()
也许你会问,这样做有什么意义呢,我们难道不是可以直接使用 A.method()
来调用么? 没错,对于单个独立的类型来说我们完全没有必要关心它的元类型,但是元类型或者元编程的概念可以变得非常灵活和强大,这在我们编写某些框架性的代码时会非常方便。比如我们想要传递一些类型的时候,就不需要不断地去改动代码。在下面的这个例子中虽然我们是用代码声明的方式获取了 MusicViewController
和 AlbumViewController
的元类型,但是其实这一步骤完全可以通过读入配置文件之类的方式来完成的。而在将这些元类型存入数组并且传递给别的方法来进行配置这一点 上,元类型编程就很难被替代了:
class MusicViewController: UIViewController {
}
class AlbumViewController: UIViewController {
}
let usingVCTypes:[AnyClass] = [MusicViewController.self, AlbumViewController.self]
func setupViewController(_ vcTypes: [AnyClass]) {
for vcType in vcTypes {
if vcType is UIViewController.Type {
let vc = (vcType as! UIViewController.Type).init()
print(vc)
}
}
}
setupViewController(usingVCTypes)
这么一来,我们完全可以搭好框架,然后用 DSL 的方式进行配置,就可以在不触及 Swift 编码的情况下,很简单地完成一系列复杂操作了。
另外,在 Cocoa API 中我们也常遇到需要一个 AnyClass
的输入,这时候我们也应该使用 .self
的方式来获取所需要的元类型,例如在注册 tableView
的 cell 的类型的时候:
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
.Type
表示的是某个类型的元类型,而在 Swift 中,除了class
,struct
和 enum
这三个类型外,我们还可以定义 protocol
。对于 protocol
来说,有时候我们也会想取得协议的元类型。这时我们可以在某个 protocol
的名字后面使用 .Protocol
来获取,使用的方法和 .Type 是类似的。
总结:
- .Type 代表的是某个类型的元类型,.Protocol代表某个协议的元类型
- anyClass 代表任意类型本身
- .self 用在类型后面取得类型本身,也可以用在某个实例的后面获得这个实例本身
协议和类方法中的 Self
在声明协议时,我们希望在协议中使用的类型就是实现这个协议本身的类型的话,就需要使用 Self
进行指代。但是在这种情况下, Self
不仅指代的是实现该协议的类型本身,也包括了这个类型的子类。从概念上来说, Self
十分简单,但是实际实现一个这样的方法却稍微要转个弯。为了说明这个问题, 我们假设要实现一个 Copyble
的协议,满足这个协议的类型需要返回一个和接受方法调用的实例相同的拷贝。一开始我们可能考虑的协议是这样的:
protocol Copyable {
func copy() -> Self
}
这是很直接明了的,它应该做的是创建一个和接受这个方法的对象同样的东西,然后将其返回, 返回的类型不应该发生改变,所以写为 Self
。然后开始尝试实现一个 MyClass
来满足这个协议:
class MyClass: Copyable {
var num = 1
func copy() -> Self {
// TODO: 返回什么?
// return
}
}
我们一开始的时候可能会写类似这样的代码:
// 这是错误代码
class MyClass: Copyable {
var num = 1
func copy() -> Self {
let result = MyClass()
result.num = num
return result
}
}
该方法要求返回的是一个抽象的、表示当前类型的Self,但是我们返回的是它的真实类型MyClass导致无法编译。也许你会尝试把方法声明中的 Self
改为 MyClass
,这样声明就和实际返回一致了,但是很快你会发现这样的话,实现的方法又和协议中的定义不一样了,依然不能编译。
// 编译器报错:Method 'copy()' in non-final class 'MyClass' must return `Self` to conform to protocol 'Copyable'。提示实现的方法和接口中定义的不一样。
func copy() -> MyClass {
let result = MyClass()
result.num = num
return result
}
为了解决这个问题,我们在这里需要的是通过一个和当前上下文 (也就是和 MyClass
) 无关的,又能够指代当前类型的方式进行初始化。希望你还能记得我们在对象类型中所提到的 type(of:)
,这里我们就可以使用它来做初始化,以保证方法与当前类型上下文无关,这样不论是 MyClass
还是它的子类,都可以正确地返回合适的类型满足 Self
的要求:
func copy() -> Self {
let result = type(of: self).init()
result.num = num
return result
}
但是很不幸,单单是这样还是无法通过编译,编译器提示我们如果想要构建一个 Self
类型的对象的话,需要有 required
关键字修饰的初始化方法,这是因为 Swift 必须保证当前类和其子类都能响应这个 init
方法。另一个解决的方案是在当前类类的声明前添加 final
关键字,告诉编译器我们不再会有子类来继承这个类型。在这个例子中,我们选择添加上 required
的 init
方法。 最后,MyClass
类型是这样的:
class MyClass: Copyable {
var num = 1
required init() {
}
func copy() -> Self {
let result = type(of: self).init()
result.num = num
return result
}
}
测试一下正确性:
let object = MyClass()
object.num = 10
let newObject = object.copy()
newObject.num = 1
print(object.num)// 10
print(newObject.num)//1
而对于 MyClass
的子类,copy()
方法也能正确地返回子类的经过拷贝的对象了 :
class ClassB: MyClass {
required init() {
}
}
let b = ClassB()
b.num = 55
let nb = b.copy()
print(nb.num)//55
另一个可以使用 Self
的地方是在类方法中,使用起来也十分相似,核心就在于保证子类也能返回恰当的类型。
//另一个使用Self的地方是在类方法中
class func gain() -> Self {
return self.init()
}
let nb1 = ClassB.gain()
nb1.num = 100
print(nb1.num)//100
获取对象类型
在 Swift 中,我们会发现不管是纯 Swift 的 class 还是 NSObject
的子类,都没有像原来那样的 class()
方法来获取类型了。对于 NSObject
的子类,因为其实类的信息的存储方式并没有发生什么大的变化,因此我们可以求助于 Objective-C 的运行时,来获取类并按照原来的方式转换:
let date = NSDate()
let name: AnyClass! = object_getClass(date)
print(name)
// 输出:
// __NSDate
其中 object_getClass
是一个定义在 ObjectiveC 的 runtime 中的方法,它可以接受任意的 AnyObject!
并返回它的类型 AnyClass!
(注意这里的叹号,它表明我们甚至可以输入 nil,并期待其返回一个 nil)。在 Swift 中其实为了获取一个 NSObject
或其子类的对象的实际类型,对这个调用其实有一个好看一些的写法,那就是 type(of:)
。上面的代码用一种 "更 Swift" 一些的语言转换一 下,会是这个样子:
let date = NSDate()
let name = type(of: date)
print(name)
// 输出:
// __NSDate
我们上面用的都是 Objective-C 的动态特性,要是换成一个 Swift 内建类型的话,会怎么样呢?比如原生的 `String` ,
let string = "Hello"
let name = type(of: string)
print(name) // 输出:String
debugPrint(name) // 输出:Swift.String
可以看到对于 Swift 的原生类型,这种方式也是可行的。(值得指出的是,其实这里的真正的类型名字还带有 module 前缀,也就是 Swift.String
。直接 print 只是调用了 CustomStringConvertible
中的相关方法而已,你可以使用 debugPrint
来进行确认。