使用带有类型别名的协议作为 属性
Using protocol with typealias as a property
我有一个带有类型别名的协议:
protocol Archivable {
typealias DataType
func save(data: DataType, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> DataType
}
和符合该协议的class:
class Archiver: Archivable {
typealias DataType = Int
func save(data: DataType, withNewName newName: String) throws {
//saving
}
func load(fromFileName fileName: String) throws -> DataType {
//loading
}
}
我想在另一个 class 中将 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' can only be used as a generic constraint because it has Self or associated type requiments
我的目标是 TestClass
应该只将 Archiver
视为 Archiveable
,所以如果我想更改 saving/loading 机制,我只需要创建一个新的符合Archivable
的class设置为TestClass
中的属性,但我不知道这是否可行,如果是,那么如何。
而且我想避免使用 AnyObject
而不是 DataType。
当您尝试声明和赋值时 archiver
:
let archiver: Archivable = Archiver()
它必须有具体类型。
Archivable
不是具体类型,因为它是具有关联类型的协议。
来自 "The Swift Programming Language (Swift 2)" 本书:
An associated type gives a placeholder name (or alias) to a type that
is used as part of the protocol. The actual type to use for that
associated type is not specified until the protocol is adopted.
因此需要声明继承自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)" 本书:
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.
所以基本上,当你想将 archiver
声明为 Archivable<Int>
时,你的意思是你不希望使用 archiver
的某些代码了解其具体 class 并可以访问它的其他方法、属性等。
很明显,这段代码应该包装在单独的 class、方法或函数中,并且 archiver
应该作为参数传递到那里,并且这个 class、方法或函数将是通用的。
在你的情况下,如果你通过初始化参数传递 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")
}
}
根据您实际尝试做的事情,这可以使用类型擦除来工作。如果您按照评论中 link R Menke post 中的说明进行操作,您可以实现您想要做的事情。由于 TestClass
中的 属性 似乎是一个 let,我假设您在编译时已经知道 DataType
的类型。首先你需要设置一个类型 erased Archivable
class 像这样:
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
包装在 class 中的 TestClass
中:
class TestClass {
let archiver = AnyArchiver(Archiver())
}
通过类型推断,Swift 将类型 TestClass
' archiver let 常量作为 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
我写了一个 post 关于这个 goes into even more detail 以防你 运行 使用这种方法遇到任何问题。希望对您有所帮助!
Hector 在上面给出了一个更复杂但最终更好的解决方案,但我认为无论如何我都会 post 替代答案。它更简单,但从长远来看可能不太灵活。
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()
}
我有一个带有类型别名的协议:
protocol Archivable {
typealias DataType
func save(data: DataType, withNewName newName: String) throws
func load(fromFileName fileName: String) throws -> DataType
}
和符合该协议的class:
class Archiver: Archivable {
typealias DataType = Int
func save(data: DataType, withNewName newName: String) throws {
//saving
}
func load(fromFileName fileName: String) throws -> DataType {
//loading
}
}
我想在另一个 class 中将 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' can only be used as a generic constraint because it has Self or associated type requiments
我的目标是 TestClass
应该只将 Archiver
视为 Archiveable
,所以如果我想更改 saving/loading 机制,我只需要创建一个新的符合Archivable
的class设置为TestClass
中的属性,但我不知道这是否可行,如果是,那么如何。
而且我想避免使用 AnyObject
而不是 DataType。
当您尝试声明和赋值时 archiver
:
let archiver: Archivable = Archiver()
它必须有具体类型。
Archivable
不是具体类型,因为它是具有关联类型的协议。
来自 "The Swift Programming Language (Swift 2)" 本书:
An associated type gives a placeholder name (or alias) to a type that is used as part of the protocol. The actual type to use for that associated type is not specified until the protocol is adopted.
因此需要声明继承自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)" 本书:
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.
所以基本上,当你想将 archiver
声明为 Archivable<Int>
时,你的意思是你不希望使用 archiver
的某些代码了解其具体 class 并可以访问它的其他方法、属性等。
很明显,这段代码应该包装在单独的 class、方法或函数中,并且 archiver
应该作为参数传递到那里,并且这个 class、方法或函数将是通用的。
在你的情况下,如果你通过初始化参数传递 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")
}
}
根据您实际尝试做的事情,这可以使用类型擦除来工作。如果您按照评论中 link R Menke post 中的说明进行操作,您可以实现您想要做的事情。由于 TestClass
中的 属性 似乎是一个 let,我假设您在编译时已经知道 DataType
的类型。首先你需要设置一个类型 erased Archivable
class 像这样:
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
包装在 class 中的 TestClass
中:
class TestClass {
let archiver = AnyArchiver(Archiver())
}
通过类型推断,Swift 将类型 TestClass
' archiver let 常量作为 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
我写了一个 post 关于这个 goes into even more detail 以防你 运行 使用这种方法遇到任何问题。希望对您有所帮助!
Hector 在上面给出了一个更复杂但最终更好的解决方案,但我认为无论如何我都会 post 替代答案。它更简单,但从长远来看可能不太灵活。
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()
}