使用带有 typealias 的协议作为属性
Posted
技术标签:
【中文标题】使用带有 typealias 的协议作为属性【英文标题】:Using protocol with typealias as a property 【发布时间】:2015-12-06 16:36:47 【问题描述】:我有一个带有 typealias 的协议:
protocol Archivable
typealias DataType
func save(data: DataType, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> DataType
以及符合该协议的类:
class Archiver: Archivable
typealias DataType = Int
func save(data: DataType, withNewName newName: String) throws
//saving
func load(fromFileName fileName: String) throws -> DataType
//loading
我想将Archivable
用作另一个类中的属性,例如:
class TestClass
let arciver: Archivable = Archiver() //error here: Protocol 'Archivable' can only be used as a generic constraint because it has Self or associated type requiments
但它失败了
Protocol 'Archivable' 只能用作通用约束,因为它具有 Self 或关联类型要求
我的目标是TestClass
应该只将Archiver
视为Archiveable
,所以如果我想更改保存/加载机制,我只需要创建一个符合Archivable
设置的新类作为TestClass
中的属性,但我不知道这是否可行,如果可以,那么如何。
我想避免使用AnyObject
而不是DataType。
【问题讨论】:
阅读:the link @RMenke 感谢您的链接,所以基本上这种方法不起作用:\ 目前泛型只是部分实现。 Swift 3 的目标之一是使其完全通用。使一切通用化是最新的趋势,但它与 Swift 的工作方式并不一致。每种类型都必须在编译时知道,而 Swift 中的泛型还不能在每种情况下都做到这一点。我个人尽量避免使用泛型。 @DánielNagy 如果你有空闲时间:***.com/questions/34285246/… 【参考方案1】:根据您实际尝试执行的操作,这可以使用类型擦除来实现。如果您按照 cmets 中发布的链接 R Menke 中的说明进行操作,您就可以实现您想要做的事情。由于您在TestClass
中的属性似乎是一个让,我将假设您在编译时已经知道DataType
的类型。首先,您需要像这样设置一个类型已擦除的Archivable
类:
class AnyArchiver<T>: Archivable
private let _save: ((T, String) throws -> Void)
private let _load: (String throws -> T)
init<U: Archivable where U.DataType == T>(_ archiver: U)
_save = archiver.save
_load = archiver.load
func save(data: T, withNewName newName: String) throws
try _save(data, newName)
func load(fromFileName fileName: String) throws -> T
return try _load(fileName)
很像 Swift 的 AnySequence
,您可以像这样将您的 Archiver
包装在您的 TestClass
中:
class TestClass
let archiver = AnyArchiver(Archiver())
通过类型推断,Swift 将键入TestClass
'archiver let constant 作为AnyArchiver<Int>
。这样做可以确保您不必创建十几个协议来定义 DataType
类似于 StringArchiver
、ArrayArchiver
、IntArchiver
等。相反,您可以选择使用以下方式定义变量像这样的泛型:
let intArchiver: AnyArchiver<Int>
let stringArchiver: AnyArchiver<String>
let modelArchiver: AnyArchiver<Model>
而不是像这样复制代码:
protocol IntArchivable: Archivable
func save(data: Int, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> Int
protocol StringArchivable: Archivable
func save(data: String, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> String
protocol ModelArchivable: Archivable
func save(data: Model, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> Model
let intArchiver: IntArchivable
let stringArchiver: StringArchivable
let modelArchiver: ModelArchivable
我写了一篇关于 goes into even more detail 的帖子,以防您在使用这种方法时遇到任何问题。我希望这会有所帮助!
【讨论】:
我读过你关于通用协议的帖子,我很喜欢它。你善于解释事情。但有一件事让我感到困惑——“The Swift Programming Language”一书中没有“通用协议”一词,因为 Swift 中没有通用协议:) 查找关联类型。我称它们为泛型协议,因为它们与泛型类非常相似,并且本质上不太具体。【参考方案2】:当你尝试声明和分配archiver
:
let archiver: Archivable = Archiver()
它必须有具体的类型。
Archivable
不是具体类型,因为它是具有关联类型的协议。
来自“The Swift Programming Language (Swift 2)”一书:
关联类型为一个类型提供一个占位符名称(或别名) 用作协议的一部分。实际使用的类型 在协议被采用之前不指定关联类型。
所以你需要声明继承自Archivable
并指定关联类型的协议:
protocol IntArchivable: Archivable
func save(data: Int, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> Int
然后你就可以采用这个协议了:
class Archiver: IntArchivable
func save(data: Int, withNewName newName: String) throws
//saving
func load(fromFileName fileName: String) throws -> Int
//loading
现在 Swift 中没有真正的通用协议,所以你不能像这样声明 archiver
:
let archiver: Archivable<Int> = Archiver()
但问题是你不需要这样做,我会解释原因。
来自“The Swift Programming Language (Swift 2)”一书:
协议定义了适合特定任务或功能的方法、属性和其他要求的蓝图。
所以基本上当你想将archiver
声明为Archivable<Int>
时,你的意思是你不希望使用archiver
的某些代码知道它的具体类并访问它的其他方法、属性,等等
很明显,这段代码应该包装在单独的类、方法或函数中,archiver
应该作为参数传递到那里,并且这个类、方法或函数将是通用的。
如果您通过初始化参数传递archivable
,则TestClass
可以是通用的:
class TestClass<T, A: Archivable where A.DataType == T>
private let archivable: A
init(archivable: A)
self.archivable = archivable
func test(data: T)
try? archivable.save(data, withNewName: "Hello")
或者它可以有接受archivable
作为参数的通用方法:
class TestClass
func test<T, A: Archivable where A.DataType == T>(data: T, archivable: A)
try? archivable.save(data, withNewName: "Hello")
【讨论】:
"当您尝试声明和分配归档器时:let archiver: Archivable = Archiver()
它必须具有具体类型。"这实际上是不正确的。那行特定的代码是完全合法的。问题出在其他地方。
@VinceO'Sullivan 那行代码是不合法的,并给出编译器错误Protocol 'Archivable' can only be used as a generic constraint because it has Self or associated type requirements
。问题全在于这个错误。
编译错误不是因为那行是非法的,而是因为类和/或协议的定义方式。请看下面我的回答。那行编译得很好。
@VinceO'Sullivan 编译错误是因为该行在 OP 在他的问题中发布的其他代码的上下文中是非法的,我正在回答它。【参考方案3】:
Hector 在上面给出了一个更复杂但最终更好的解决方案,但我想我还是会发布另一种答案。它更简单,但从长远来看可能不太灵活。
typealias DataType = Int
protocol Archivable
var data: DataType get set
func save(data: DataType, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> DataType
class Archiver: Archivable
var data:DataType = 0
func save(data: DataType, withNewName newName: String) throws
//saving
func load(fromFileName fileName: String) throws -> DataType
return data
class TestClass
let arciver: Archivable = Archiver()
【讨论】:
您是否刚刚将typealias
置于全局范围内?这使得它非泛型
是的,我做到了。是的,它确实。你说的原样非法的线路现在是合法的。
让我们解决所有这样的问题......它编译得很好,但它没有做我们需要做的事情。以上是关于使用带有 typealias 的协议作为属性的主要内容,如果未能解决你的问题,请参考以下文章