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

时间:2015-12-06 16:36:48

标签: swift generics protocols

我有一个带有typealias的协议:

@Override
public void draw(Canvas canvas) {
    int sc = canvas.saveLayer(null, null,
                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                    );
    canvas.drawCircle(vinylCenter[0],vinylCenter[1],radius,mPaint);
    mDrawable.draw(testCanvas);
    canvas.drawBitmap(src,0f,0f,xfermodePaint);
    canvas.restoreToCount(sc);
}

和符合该协议的类:

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

但是

失败了
  

协议'Archivable'只能用作通用约束,因为它具有Self或相关类型要求

我的目标是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 } 只应将TestClass看作Archiver,所以如果我想更改保存/加载机制,我只需要创建一个符合Archiveable将其设置为Archivable中的属性,但我不知道这是否有效,如果是,那么如何。

我想避免使用TestClass而不是DataType。

3 个答案:

答案 0 :(得分:3)

根据您实际尝试的操作,这可以使用类型擦除。如果您按照评论中发布的链接R Menke中的说明操作,则可以实现您的目标。由于TestClass中的属性似乎是let,我假设你已经知道编译时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,将常量设为AnyArchiver<Int>。这样做将确保您不必创建十几个协议来定义DataType StringArchiverArrayArchiverIntArchiver等等。相反,您可以选择使用这样的泛型来定义变量:

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的帖子,以防你遇到这种方法的任何问题。我希望这有帮助!

答案 1 :(得分:1)

当您尝试声明并指定archiver时:

let archiver: Archivable = Archiver()

它必须具体。 Archivable不是具体类型,因为它的协议具有关联类型。

来自&#34; Swift编程语言(Swift 2)&#34;书:

  

关联类型为类型提供占位符名称(或别名)   用作协议的一部分。要使用的实际类型   在采用协议之前,不会指定关联类型。

因此,您需要声明从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()

但事情是你不需要这样做,我解释原因。

来自&#34; Swift编程语言(Swift 2)&#34;书:

  

协议定义了适合特定任务或功能的方法,属性和其他要求的蓝图。

所以基本上当你想将archiver声明为Archivable<Int>时,你的意思是你不想使用archiver来获知某些代码来了解它的具体类,并且访问其他方法,属性等 很明显,这段代码应该包含在单独的类,方法或函数中,archiver应该作为参数传递给它,这个类,方法或函数将是通用的。

如果您通过初始化参数传递TestClass,则archivable可以是通用的:

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

答案 2 :(得分:-1)

赫克托尔提供了一个更复杂但最终更好的解决方案,但我认为无论如何我都会发布另一种选择。它更简单,但从长远来看可能不那么灵活。

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