如果你看一下苹果的文档,你会发现元类型(Metatype) 被定义为类型的类型(type of a type )。但是,String
不是类型吗?什么可能是已经是类型的String
的类型?SuperString
??
这在理论上听起来很奇怪,但这是因为我们已经习惯了Swift的语法,它特别隐藏了一些细节,以使语言易于使用。要理解元类型,请尝试停止将事物视为类型,而是开始将它们视为实例(instances) 和类(classes)(使用关键字,而不是对象!)。
考虑以下代码片段:如何定义SwiftRocks()
和: SwiftRocks
?
swift
struct SwiftRocks {
static let author = "Bruno Rocha"
func postArticle(name: String) {}
}
let blog: SwiftRocks = SwiftRocks()
你可以说SwiftRocks()
是一个对象,SwiftRocks
是它的类型。但是相反,尝试将SwiftRocks()
视为实例 ,并将: SwiftRocks
本身视为实例的类型的表示。毕竟,你可以从blog
调用实例方法postText()
,但是你无法访问类属性author
。
现在,我们如何访问author
?最常见的方法是通过SwiftRocks.author
直接返回一个字符串,但请先暂时忘记这个,还有其他方法吗?
scss
type(of: blog).author
是的!type(of: )
将对象转换为允许您访问所有类属性的对象。但是你有没有试过调用type(of: blog)
来看看会发生什么?
scss
let something = type(of: blog) // SwiftRocks.Type
奇怪的后缀之一!SwiftRocks的类型是SwiftRocks.Type
,这意味着SwiftRocks.Type
是SwiftRocks的
元类型。
在Xcode中观察something
,可以发现对元类型的引用 允许您使用该类型的所有类属性和方法,包括init()
:
ini
let author: String = something.author
let instance: SwiftRocks = something.init()
当您需要一个方法来为您实例化对象(例如UITableView
的cell重用Decodable
时)、访问类属性或仅基于对象类型执行操作时,这非常有用。以通用方式执行此操作很容易,因为您可以将元类型作为参数传递:
swift
func createWidget<T: Widget>(ofType: T.Type) -> T {
let widget = T.init()
myWidgets.insert(widget)
return widget
}
元类型也可以用于相等性检查,我个人在设计工厂时觉得这很方便。
swift
func create<T: BlogPost>(blogType: T.Type) -> T {
switch blogType {
case is TutorialBlogPost.Type:
return blogType.init(subject: currentSubject)
case is ArticleBlogPost.Type:
return blogType.init(subject: getLatestFeatures().random())
case is TipBlogPost.Type:
return blogType.init(subject: getKnowledge().random())
default:
fatalError("Unknown blog kind!")
}
}
你可以定义任何类型的元类型,包括类、结构体、枚举和协议,作为该类型的名称后缀.Type
。
简而言之,SwiftRocks
指的是实例的类型,它只允许你使用实例属性;元类型SwiftRocks. Type
指的是类本身的类型,它允许你使用SwiftRocks
的类属性。"type of a type" 现在更有意义了,对吧?
type(of:)
动态元类型 vs .self
静态元类型
所以type(of:)
返回对象的元类型,但是如果我没有对象会发生什么?当尝试执行下面的命令时,Xcode会提示我们编译错误:
scss
create(blogType: TutorialBlogPost.Type)
简而言之,不能这样做的原因与不能调用myArray.append(String)
的原因相同。String
是类型的名称,而不是值!要将元类型作为值,您需要键入该类型的Name+.self
rust
myArray.append(String.self)
如果这听起来令人困惑,你可以这样理解:就像String
是类型,"Hello World"
是实例的值一样;String.Type
是类型,String.self
是元类型的值。
原文:
String.Type
is the type andString.self
is the value of a metatype
swift
let intMetatype: Int.Type = Int.self
//
let widget = createWidget(ofType: MyWidget.self)
tableView.register(MyTableViewCell.self, forReuseIdentifier: "myCell")
.self
是苹果所谓的静态元类型 ,它指的是对象的编译时类型。你使用它的次数比你预期的要多。还记得开头时我们要求忽略的SwiftRocks.author
吗?它还可以写成是SwiftRocks.self.author
。
静态元类型在Swift中无处不在,每次直接访问类型的class属性时都隐式使用它们。您可能会发现有趣的是,table
中的register(cellClass:)
使用的AnyClass
类型只是AnyObject.Type
的别名。
swift
public typealias AnyClass = AnyObject.Type
另一方面,type(of:)
将返回一个动态元类型,它是对象的真实运行时类型的元类型。
python
let myNum: Any = 1 // Compile time type of myNum is Any, but the runtime type is Int.
type(of: myNum) // Int.type
type(of:)
和它的返回类型Metatype
是编译器魔法compiler magic,这是方法的声明:
swift
func type<T, Metatype>(of value: T) -> Metatype {}
简而言之,如果对象的子类很重要,你应该使用type(of:)
来访问该子类的元类型。否则,你可以直接通过.self
访问静态元类型。
元类型的一个有趣特性是它们是递归的,这意味着您可以拥有元类型SwiftRock.Type.Type
,但幸运的是,swift限制里、了不能对其做太多事情,目前不能为元类型编写扩展。
协议元类型(Protocol Metatypes)
尽管之前所说的一切都适用于协议,但它们有一个重要的区别。以下代码将无法编译:
swift
protocol MyProtocol {}
let metatype: MyProtocol.Type = MyProtocol.self // Cannot convert value of...
原因是在协议的上下文中,MyProtocol.Type
不引用协议自己的元类型,而是任何继承该协议的元类型。苹果称之为existential metatype。
The reason for that is that in the context of protocols,
MyProtocol.Type
doesn't refer to the protocol's own metatype, but the metatype of whatever type is inheriting that protocol. Apple calls this an existential metatype.
swift
protocol MyProtocol {}
struct MyType: MyProtocol {}
let metatype: MyProtocol.Type = MyType.self // Now works!
在这种情况下,metatype
只能访问MyProtocol
类属性和方法,但会调用MyType
的实现。要获取协议类型本身的具体元类型 ,可以使用.Protocol
后缀。这与在其他类型上使用.Type
基本相同。
swift
let protMetatype: MyProtocol.Protocol = MyProtocol.self
因为我们指的是未继承的协议本身,所以除了简单的相等检查之外,protMetatype
真的没有什么可以做的,比如protMetatype is MyProtocol.Protocol
。如果我不得不猜测,我会说协议的具体元类型的目的更多地是让协议在编译器方面工作,这可能是我们从未在iOS项目中看到它的原因。
总结:Metatypes的更多用途
通过元类型表示类型可以帮助您构建非常智能和类型安全的泛型系统。以下是我们如何在深度链接处理程序中使用它们以防止直接处理字符串的示例:
swift
public protocol DeepLinkHandler: class {
var handledDeepLinks: [DeepLink.Type] { get }
func canHandle(deepLink: DeepLink) -> Bool
func handle(deepLink: DeepLink)
}
public extension DeepLinkHandler {
func canHandle(deepLink: DeepLink) -> Bool {
let deepLinkType = type(of: deepLink)
//Unfortunately, metatypes can't be added to Sets as they don't conform to Hashable!
return handledDeepLinks.contains { $0.identifier == deepLinkType.identifier }
}
}
//
class MyClass: DeepLinkHandler {
var handledDeepLinks: [DeepLinks.Type] {
return [HomeDeepLink.self, PurchaseDeepLink.self]
}
func handle(deepLink: DeepLink) {
switch deepLink {
case let deepLink as HomeDeepLink:
//
case let deepLink as PurchaseDeepLink:
//
default:
//
}
}
}
一个更贴切的例子,以下是我们如何使用元类型来表示和检索有关A/B实验的信息:
swift
if ExperimentManager.get(HomeExperiment.self)?.showNewHomeScreen == true {
//Show new home
} else {
//Show old home
}
// Experiment Manager
public static func get<T: Experiment>(_ experiment: T.Type) -> T? {
return shared.experimentDictionary[experiment.identifier] as? T
}
public static func activate(_ experiment: Experiment) {
shared.experimentDictionary[type(of: experiment).identifier] = experiment
}