为什么在Swift中我的不透明类型上的关联类型会出现编译器错误?

时间:2019-06-30 00:46:06

标签: ios swift protocols swift-protocols opaque-result-type

我正在尝试尽可能多地使用面向协议的编程,并且对Swift 5中的不透明类型很感兴趣。说我有:

public protocol Model {
  var identifier: String { get }
}

然后:

public protocol Query {
    associatedtype ObjectType: Model
    var predicate: NSPredicate? { get }
    var results: [ObjectType] { get }
    // everything omitted for brevity...
}

并想管理东西:

public protocol ObjectManager {
    associatedtype QueryType: Query

    /// get some objects matching query criteria.  nil means return all
    func objects(matching query: Self.QueryType?) -> [Self.QueryType.ObjectType]
}

但是我的应用程序有很多对象类型,因此有很多经理。

因此,我想采用ObjectManager的语法方法,但是它们需要具有针对我将要拥有的不同值类型的风格。在这种情况下,让我们拿书。我们称它们为BookItems。

public protocol BookItem: Model {
    var title: String { get }
}

因此BookItem需要查询,然后才能为他创建一个Manager:

public protocol BookItemQuery: Query where ObjectType: BookItem {

}

现在我们对ObjectManager进行一些约束,使其成为BookItemManager(协议)

public protocol BookItemManager: ObjectManager where QueryType: BookItemQuery {

    func createItem(with title: String, authorName: String) throws -> BookItem  // adds it to the Library
}

我还希望能够成为持久性框架不可知的对象,无论是出于测试目的,还是因为例如我可能希望摆脱Real Core.io的Core Data。我不想重写我的代码库,因此更喜欢使用面向协议的编程。所以我以为我会创建一个具体的总体“ LibraryManager”,但是即使在这里,我也不确定基于协议的内容必须在哪里开始和结束。看来我在想像的是不可能的?

无论如何,我定义了一个基类,其中直接子类将实现持久性框架特定的东西,这是我唯一的一次在这些管理器中处理具体类型的方法。否则,我期望处理面向协议的类型,它们的具体类型无关紧要(这是我的希望...如果我错了,请告诉我...?):

public class AnyLibraryManager {

    public enum Error: Swift.Error {
        case abstractImplementationRequiresOverride
    }

    @available(iOS 13.0.0, *)
    public var bookItemManager: some BookItemManager {
        return NullBookItemManager()  // purely to prevent the compiler complaints. You should subclass
    }

    @available(iOS 13.0.0, *)
    public var tagManager: some TagManager {
        return NullTagManager()  // purely to prevent the compiler complaints. You should subclass
    }

    @available(iOS 13.0.0, *)
    public var listManager: some ListManager {
        return NullListManager() // purely to prevent the compiler complaints. You should subclass
    }

    @available(iOS 13.0.0, *)
    public var authorManager: some AuthorManager {
        return NullAuthorManager()  // purely to prevent the compiler complaints. You should subclass
    }
}

我为实现这一点而自拍。现在我以为我会测试自己做了什么。

func testCreateNewBookItemThenDeleteIt() {

        do {

            let item = try libraryManager.bookItemManager.createItem(with: "MyBook", authorName: "Stephen O")  // works....

            try libraryManager.bookItemManager.saveChanges()  // all good...
            var allItems = libraryManager.bookItemManager.objects(matching: nil)  // compiler also doesn't complain... 
            XCTAssertTrue(allItems.count == 1, "There should be only 1 item in the library")


            // THIS IS THE OFFENDING LINE, see below:
            try libraryManager.bookItemManager.remove([item])

            try libraryManager.bookItemManager.saveChanges()
            allItems = libraryManager.bookItemManager.objects(matching: nil)
            XCTAssertTrue(allItems.count == 0, "There should be no items in the library")

        } catch let error {
            XCTFail("Test failed due to error: \(error.localizedDescription)")
        }
    }

所以到了这一行:

try libraryManager.bookItemManager.remove([item])

我收到编译器警告:

Cannot convert value of type '[BookItem]' to expected argument type '[(some BookItemManager).QueryType.ObjectType]'

但是BookItemManager说它的QueryType是BookItemQuery,因此ObjectType是BookItem。那有什么呢?这是一个错误,因为不透明的类型是新的,也许编译器还没有为这种情况做好准备?

这很烂,因为我一直花时间在技术上,这些技术最终使我回到了OOP和“保持简单,愚蠢”的设计原则。

0 个答案:

没有答案