什么是.self,.Type和.Protocol? 了解Swift元类型

亚型。 那是我每天使用事物列表中的另一个, 但是如果我的生活依赖它,就无法在采访中解释

元类型在Swift中非常有用,您肯定已经在多种场合使用过它。 不幸的是,它们在代码中看起来很奇怪,在试图理解它们的真实含义时可能会引起一些混乱。

我曾经知道这些奇怪的后缀会如何使您感到困惑,但是请放心,一旦您了解了它们之间的区别,它们实际上就非常简单了。 但是在进入这一点之前,让我们退后一步:

(PS:如果您发现在Medium上难以阅读的代码,也可以在我的博客SwiftRocks上阅读!)

如果看一下Apple的文档,就会发现一个元类型被定义为type的类型 。 等等,不是String类型吗? 已经是类型的String的类型可能是什么? SuperString

从理论上讲,这听起来很奇怪,但这是因为我们习惯了Swift的语法,该语法专门向我们隐藏了其中的一些细节,以使该语言易于使用。 要理解元类型,请尝试停止将事物视为类型,而开始更多地将它们视为实例 (uses关键字,而不是object!)。

考虑以下代码段:如何定义SwiftRocks(): SwiftRocks

  struct SwiftRocks { 
静态让作者=“布鲁诺·罗查”
func postArticle(名称:字符串){}
}

让博客:SwiftRocks = SwiftRocks()

您可以说SwiftRocks()是一个对象,而SwiftRocks是它的类型,但是相反,请尝试将SwiftRocks()作为实例 ,以及: SwiftRocks本身作为实例类型的表示 。 毕竟,您可以从blog调用实例方法postArticle() ,但不能访问类属性author

现在,我们如何访问author ? 最常见的方法是通过SwiftRocks.author ,它将直接为您返回一个String ,但是我会请您暂时忘记该String 。 还有另一种方法吗?

我知道布鲁诺! 您可以调用 type(of: blog).author

对! 这也是正确的,因为type(of)将一个对象转换为可以访问所有类属性的对象。 但是您是否曾经尝试调用type(of: blog)看看会发生什么?

  let something = type(of:blog)// SwiftRocks.Type 

奇怪的后缀之一! SwiftRocks类型SwiftRocks.Type ,这意味着SwiftRocks.TypeSwiftRocks's 类型

通过在something属性上使用Xcode的代码完成,您将看到对元类型的引用使您可以使用该类型的所有类属性和方法,包括init()

 让作者:字符串= something.author 
让实例:SwiftRocks = something.init()

当您想要一种方法来为您实例化对象(例如UITableView单元重用和Decodable工作方式),访问类属性或仅基于对象的类型执行总体操作时,这非常有用。 以通用的方式这样做很容易,因为您可以将元类型作为参数传递:

  func createWidget (ofType:T.Type)-> T { 
让小部件= T.init()
myWidgets.insert(小工具)
返回小部件
}

元类型也可以用于相等性检查,在设计工厂时,我个人觉得很方便:

  func create (blogType:T.Type)-> T { 
切换blogType {
案例是TutorialBlogPost.Type:
返回blogType.init(subject:currentSubject)
大小写为ArticleBlogPost.Type:
返回blogType.init(主题:getLatestFeatures()。random())
大小写为TipBlogPost.Type:
返回blogType.init(主题:getKnowledge()。random())
默认:
fatalError(“未知的博客种类!”)
}
}

您可以将任何类型的元类型(包括类,结构,枚举和协议)定义为该类型名称,后跟 .Type 。 简而言之,尽管SwiftRocks是指实例的类型(仅允许您使用实例属性),而元类型SwiftRocks.Type是指类本身的类型,但您可以使用SwiftRocks's类属性。 “类型的类型”现在变得更有意义了,对吧?

所以type(of)返回对象的元类型,但是如果我没有对象会发生什么呢? 如果我尝试调用create(blogType: TutorialBlogPost.Type) Xcode会给我一个编译器错误!

简而言之,不能执行此操作的原因与无法调用myArray.append(String)原因相同: String是类型的名称,而不是值! 要获取一个元类型作为值,您需要键入该类型的名称,后跟 .self

如果这听起来令人困惑,那么您可以这样看:就像String是类型, "Hello World"是实例的值, String.Type是类型,而String.self是元类型的值。

 让intMetatype:Int.Type = Int.self 
//
让小部件= createWidget(ofType:MyWidget.self)
tableView.register(MyTableViewCell.self,forReuseIdentifier:“ myCell”)

Apple称其为.self静态元类型,即对象的编译时类型的花哨词。 您的使用超出了您的预期–还记得我告诉过您忽略SwiftRocks.author吗? 原因是因为编写与编写SwiftRocks.self.author相同。

静态元类型在Swift中无处不在,您每次直接访问类型的class属性时都隐式使用它们。 您可能会发现有趣的是,表的register(cellClass:)使用的AnyClass类型只是AnyObject.Type的别名:

 公共类型别名AnyClass = AnyObject.Type 

另一方面, type(of)将返回一个动态的metatype ,它是对象实际的运行时类型的metatype。

  let myNum:Any = 1 // myNum的编译时间类型为Any,但是运行时类型为Int。 
type(of:myNum)//整数类型

type(of:)和它的Metatype返回类型的实际内容是编译器的魔力(另一篇文章的主题),但这是方法的签名:

  func type (值:T)-> Metatype {} 

简而言之,如果对象的子类很重要,则应使用type(of)来访问该子类的元类型。 否则,您可以直接通过(name of the desired type).self直接访问静态元(name of the desired type).self

元类型的一个有趣的特性是它们是递归的,这意味着您可以拥有诸如SwiftRocks.Type.Type之类的元元SwiftRocks.Type.Type ,但值得庆幸的是,出于我们的理智,您不能对此做太多事情,因为目前无法为元类型编写扩展名。

尽管前面说过的所有内容都适用于协议,但它们之间有着重要的区别。 以下代码将无法编译:

 协议MyProtocol {} 
let metatype:MyProtocol.Type = MyProtocol.self //无法转换...的值

这样做的原因是,在协议的上下文中, MyProtocol.Type并不引用协议自身的元类型,而是引用该协议的任何类型的元类型。 苹果将​​其称为存在性元类型

 协议MyProtocol {} 
struct MyType:MyProtocol {}
let metatype:MyProtocol.Type = MyType.self //现在可以使用!

在这种情况下,元metatype只能访问MyProtocol类的属性和方法,但是将调用MyType's实现。 要获取协议类型本身的具体元类型,可以使用.Protocol后缀。 这基本上与在其他类型上使用.Type相同。

 让protMetatype:MyProtocol.Protocol = MyProtocol.self 

因为我们指的是未继承的协议本身,所以除了简单的等式检查(例如protMetatype is MyProtocol.Protocol之外,您对protMetatype所做的工作根本无法做。 如果我不得不猜测,我会说协议的具体元类型的目的更多是使协议在事物的编译器方面起作用,这很可能就是为什么我们在iOS项目中从未看到过。

通过元类型表示类型可以帮助您构建非常智能且类型安全的通用系统。 这是一个示例,说明了如何在深度链接处理程序中使用它们来防止直接处理字符串:

 公用协议DeepLinkHandler:类{ 
var handleDeepLinks:[DeepLink.Type] {get}
func canHandle(deepLink:DeepLink)->布尔
func handle(deepLink:DeepLink)
}

公共扩展DeepLinkHandler {
func canHandle(deepLink:DeepLink)-> Bool {
让deepLinkType = type(of:deepLink)
//不幸的是,不能将元类型添加到Set中,因为它们不符合Hashable!
返回handleDeepLinks.contains {$ 0.identifier == deepLinkType.identifier}
}
}

//

类MyClass:DeepLinkHandler {
var handleDeepLinks:[DeepLinks.Type] {
返回[HomeDeepLink.self,PurchaseDeepLink.self]
}

func handle(deepLink:DeepLink){
切换deepLink {
案例让deepLink作为HomeDeepLink:
//
案例让deepLink作为PurchaseDeepLink:
//
默认:
//
}
}
}

作为最近的示例,这是我们如何使用元类型来表示和检索有关A / B测试的信息(称为“实验”):

 如果ExperimentManager.get(HomeExperiment.self)?. showNewHomeScreen == true { 
//显示新家
}其他{
//显示旧家
}

//实验管理员

公共静态函数get (_实验:T.Type)-> T? {
返回shared.experimentDictionary [experiment.identifier]为? Ť
}

公共静态功能激活(_实验:实验){
shared.experimentDictionary [type(of:Experiment).identifier] =实验
}

在我的Twitter上关注我-@rockthebruno,让我知道您想分享的任何建议和更正。

Apple Docs:类型
Apple Docs:类型(of 🙂


最初发布于 swiftrocks.com