我想要一個包含Item. 目標是它的集合items是否是陣列/集合/等無關緊要,只要它符合Collection.
protocol Content {
var items: Collection<Item> // This doesn't work, swift doesn't support generic protocol parameters
}
一個示例用例,我使用 Realm 來持久化內容,但希望能夠在不需要匯入 Realm 的情況下模擬資料:
// Typical usage with Alamofire and Realm
struct RealmArticle: Object, Content {
var items = List<Item>()
}
func fetchContent(completion: (Content?, Error) -> ())) {
session.request(...).responseDecodable { (content: RealmArticle) in
completion(content, nil)
}
}
// Mock without needing to include Realm
func mockFetchContent(completion: (Content?, Error) -> ()) {
completion(MockArticle(items: (0...5).map { Item() }), nil)
}
struct MockArticle: Content {
var items: [Item]
}
我目前的解決方案是在協議中使用陣列,并修改領域模型:
protocol Content {
var items: [Item]
}
struct RealmArticle: Object, Content {
// I have to use a List because that is what the Realm SDK requires.
var _items = List<Item>()
var items: [Item] { Array(_items) }
}
但這并不理想,因為它在List每次items訪問時都會將整個加載到記憶體中。有更好的解決方案還是我應該采取不同的方法?
編輯:
關于關聯型別的更新:
使用時無法約束/強制執行集合型別.associatedtype實作協議的物件必須是通用的,這很麻煩。特別是如果模型有多個不同型別的集合。
包含的父物件
Content也必須是通用的。例如:protocol Content { associatedtype A: Collection associatedtype B: Collection // etc var items: A var otherItems: B //etc } struct Parent<C: Content> { var content: C? } struct RealmArticle<Item, OtherItem, etc>: Object, Content { var items: Item var otherItems: OtherItem // etc } var article = RealmArticle<List<Item>, List<OtherItem>, etc>() var parent = Parent<RealmArticle<List<Item, List<OtherItem>, etc>()
編輯2:更正
集合可以被約束為其他協議或類通過指定型別Element。這使它更接近我需要的東西:
protocol Content {
associatedtype C: Collection where C.Element: Item
var c: C { get set }
}
然而,它仍然需要將協議用作約束型別,而不是能夠直接使用它:
struct Parent<C: Content> {
var content: C?
}
什么是理想但不能編譯的:
protocol Item {
var id: String { get }
}
protocol Content {
associatedtype C: Collection where C.Element: Item
var items: C { get set }
}
struct Parent {
// Protocol 'Content' can only be used as a generic constraint because it has Self or associated type requirements
var content: Content?
}
let parent = Parent()
// Both classes conform to content so swapping should be possible.
// It is possible if I don't use associated types and just use an array
parent.content = RealmArticle()
parent.content = MockArticle()
如果這在 Swift 中是不可能的,或者是糟糕的代碼設計,我真的很感激解釋為什么。其他語言支持這一點,它有合法的用例。
uj5u.com熱心網友回復:
如果您可以不要求集合的元素必須是,Item那么您可以使用associatedtype它來使其與Collection
protocol Content {
associatedtype T: Collection
var items: T { get set }
}
這應該足以能夠根據需要進行模擬。
uj5u.com熱心網友回復:
當您可以使此方法通用時,為什么還需要一個獨立的協議?
func fetchContent<C: Collection>(completion: (Result<C, Error>) -> Void) where C.Iterator.Element == Object {
…
}
更新
根據我們在下面評論中的討論,我建議采用本文中使用的方法,從而模擬您從中獲取內容的服務。
第二次更新
不清楚您的需求是什么:例如,對于Content您定義的協議,您既沒有指定強制屬性項訪問權限。是{ get }財產嗎?
protocol Content {
var items: [Item] { get }
}
那么您的RealmArticle一致性不需要雙重存盤,因為它List是一個集合:
protocol Content {
var items: [Item] { get }
}
extension RealmArticle: Content {
var items: [Item] { Array(_items) }
}
是{ get set }財產嗎?然后,您還可以通過List從 a創建 a 來添加一致性Sequence,因此不需要雙重存盤:
protocol Content {
var items: [Item] { get set }
}
extension RealmArticle: Content {
var items: [Item] {
get { Array(_items) }
set { _items = List(newValue) /*or whatever way you create a List collection from a a sequence of Items*/ }
}
}
至于 Swift 泛型,你也可以associatedtype用一個where子句來約束:
import Foundation
protocol Content {
associatedtype C: Collection where C.Element: Hashable
var c: C { get set }
}
struct MyContent: Content {
var c: Array<Int> = []
}
var myC = MyContent()
myC.c = [1, 2, 3, 4, 5]
符合泛型協議的型別也可能隱式(如前面的示例中)、顯式和有條件地約束協議的泛型關聯型別:
struct AnotherContent<T>: Content where T: Hashable {
var c: Array<T>
}
let myA = AnotherContent(c: [1, 2, 3, 4, 5])
struct YetAnotherContent<C: Collection> {
var c: C
}
extension YetAnotherContent: Content where C.Element: Hashable {}
}
也許你想回顧一下 Swift 泛型,并更好地理解它們是如何作業的。
第三次更新
Your last problem can be solved by adopting a type-erased AnyContent concrete type. You might as well find many resources online explaining this pattern, here's one.
As per the reason behind the need of this pattern, that'll be cause Swift is strongly-typed language thus everything needs to resolve to a concrete type at compile time; other languages you've mentioned in the comments below (as in Obj-C) are weakly-typed hence you might as well resolve a type at run-time.
Anyway there have been also additions to the Swift language lately, (especially because of SwiftUI View protocol) in regards to opaque types which might be useful to have knowledge about when it comes to write code using generics and protocols with an associated type.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/356479.html
