我正在尝试尽可能多地使用面向协议的编程,并且对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和“保持简单,愚蠢”的设计原则。