简体   繁体   English

使用带有typealias的协议作为属性

[英]Using protocol with typealias as a property

I have a protocol with a typealias: 我有一个带有typealias的协议:

protocol Archivable {
    typealias DataType

    func save(data: DataType, withNewName newName: String) throws
    func load(fromFileName fileName: String) throws -> DataType
}

and a class that conforms to that protocol: 以及符合该协议的类:

class Archiver: Archivable {
    typealias DataType = Int

    func save(data: DataType, withNewName newName: String) throws {
        //saving
    }

    func load(fromFileName fileName: String) throws -> DataType {
        //loading
    }
}

and I would like to use Archivable as a property in another class like: 我想在另一个类中使用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
}

but it fails with 但它失败了

Protocol 'Archivable' can only be used as a generic constraint because it has Self or associated type requiments 协议'Archivable'只能用作通用约束,因为它具有Self或相关类型的要求

My goal is that TestClass should only see Archiver as Archiveable , so if I want to change the saving/loading mechanism, I just have to create a new class that conforms to Archivable as set it as the property in TestClass , but I don't know if this is poosible, and if so, then how. 我的目标是TestClass只能看到ArchiverArchiveable ,所以如果我想更改保存/加载机制,我只需要创建一个符合Archivable的新类,因为它设置为TestClass的属性,但我不是知道这是否有道理,如果是,那么如何。

And I would like to avoid using AnyObject instead of DataType. 我想避免使用AnyObject而不是DataType。

Depending on what you are actually trying to do, this can work using type erasure. 根据您实际尝试的操作,这可以使用类型擦除。 If you follow the instructions in the link R Menke posted in the comments, you can achieve what you are trying to do. 如果您按照评论中发布的链接R Menke中的说明进行操作,则可以实现您的目标。 Since your property in TestClass seems to be a let, I'm going to assume you already know the type of DataType at compile time. 由于TestClass的属性似乎是一个let,我假设你已经知道编译时DataType的类型。 First you need to setup a type erased Archivable class like so: 首先,您需要设置类型已删除的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)
    }
}

Much like Swift's AnySequence , you'll be able to wrap your Archiver in this class in your TestClass like so: 就像Swift的AnySequence ,你可以将你的Archiver包装在TestClass中的这个类中,如下所示:

class TestClass {
    let archiver = AnyArchiver(Archiver())
}

Through type inference, Swift will type TestClass ' archiver let constant as an AnyArchiver<Int> . 通过类型推断,Swift将输入TestClass的archiver let constant作为AnyArchiver<Int> Doing it this way will make sure you don't have to create a dozen protocols to define what DataType is like StringArchiver , ArrayArchiver , IntArchiver , etc. Instead, you can opt in to defining your variables with generics like this: 这样做将确保您不必创建十几个协议来定义DataType ,如StringArchiverArrayArchiverIntArchiver等。您可以选择使用泛型来定义变量,如下所示:

let intArchiver: AnyArchiver<Int>
let stringArchiver: AnyArchiver<String>
let modelArchiver: AnyArchiver<Model>

rather than duplicating code like this: 而不是复制像这样的代码:

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

I wrote a post on this that goes into even more detail in case you run into any problems with this approach. 我写了一篇关于这个问题的帖子,如果你遇到这种方法的任何问题,它会更详细 I hope this helps! 我希望这有帮助!

When you try to declare and assign archiver : 当您尝试声明并分配archiver

let archiver: Archivable = Archiver()

it must have concrete type. 它必须具体。 Archivable is not concrete type because it's protocol with associated type. Archivable不是具体类型,因为它是具有关联类型的协议。

From "The Swift Programming Language (Swift 2)" book: 摘自“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. 在采用协议之前,不会指定用于该关联类型的实际类型。

So you need to declare protocol that inherits from Archivable and specifies associated type: 因此,您需要声明从Archivable继承的协议并指定关联类型:

protocol IntArchivable: Archivable {
    func save(data: Int, withNewName newName: String) throws

    func load(fromFileName fileName: String) throws -> Int
}

And then you can adopt this protocol: 然后你可以采用这个协议:

class Archiver: IntArchivable {
    func save(data: Int, withNewName newName: String) throws {
        //saving
    }

    func load(fromFileName fileName: String) throws -> Int {
        //loading
    }
}

There are no truly generic protocols in Swift now so you can not declare archiver like this: 现在Swift中没有真正的通用协议,所以你不能像这样声明archiver

let archiver: Archivable<Int> = Archiver()

But the thing is that you do not need to do so and I explain why. 但事情是你不需要这样做,我解释原因。

From "The Swift Programming Language (Swift 2)" book: 摘自“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. 协议定义了适合特定任务或功能的方法,属性和其他要求的蓝图。

So basically when you want to declare archiver as Archivable<Int> you mean that you don't want some piece of code using archiver to know about its concrete class and to have access to its other methods, properties, etc. It's obvious that this piece of code should be wrapped in separate class, method or function and archiver should be passed there as parameter and this class, method or function will be generic. 所以基本上当你想将archiver声明为Archivable<Int>你的意思是你不希望使用archiver某些代码来了解它的具体类,并且可以访问其他方法,属性等。很显然,这个一段代码应该包含在单独的类,方法或函数中, archiver应该作为参数传递给它,这个类,方法或函数将是通用的。

In your case TestClass can be generic if you pass archivable via initializer parameter: 在您的情况下,如果您通过initializer参数传递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")
    }
}

or it can have generic method that accepts archivable as parameter: 或者它可以具有接受archivable作为参数的通用方法:

class TestClass {        
    func test<T, A: Archivable where A.DataType == T>(data: T, archivable: A) {
        try? archivable.save(data, withNewName: "Hello")
    }
}

Hector gives a more complex though ultimately better solution above but I thought I'd post an alternative take on the answer anyway. 赫克托尔提供了一个更复杂但最终更好的解决方案,但我认为无论如何我都会发布另一个答案。 It is simpler but probably less flexible in the long term. 它更简单,但从长远来看可能不太灵活。

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()
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM