Your list of requirements is excellent, and translates directly into a type. First, let's spell it out exactly as you've said it. Anywhere you would say "and" is a struct (or class or tuple, but in this case a struct), and anywhere you would say "or" is an enum in Swift.
struct Model<Element> {
// 1. a String field
let string: String
// 2. either an Array<SomeElement>, or AnyRealmCollection<SomeObject>
enum Container {
case array([Element])
case realmCollection(AnyRealmCollection<Element>)
}
let elements: [Container]
// 3. a closure that acts upon the specific SomeElement or SomeObject.
let process: (Element) -> Void
}
EDIT: It's not actually possible to create the .realmCollection case here because AnyRealmCollection puts additional requirements on Element. To fix that, you'd have to build an extra wrapper around AnyRealmCollection that erases the requirement. It's not hard, but it's tedious, and I'd avoid that unless you really need to distinguish AnyRealmCollection from AnyCollection.
That "an array or realm collection" is probably more precise than you really mean. I suspect you really mean "a collection of elements". If that's what you mean, then we can say it a bit more easily:
struct Model<Element> {
let string: String
let elements: AnyCollection<Element>
let process: (Element) -> Void
}
Rather than forcing the caller to wrap elements in an AnyCollection, I'd also provide the following init:
init<C>(string: String, elements: C, process: @escaping (Element) -> Void)
where C: Collection, C.Element == Element {
self.string = string
self.elements = AnyCollection(elements)
self.process = process
}
As a rule, avoid tuple in Swift unless it's for something very simple and short-lived. Swift tuples are very limited, and making a struct is too simple to waste time on all the restrictions of tuples.
This type, however, can't be put into an Array with different Elements. If this is the only way you use Model, and you never have to get the elements back out, you can just make it non-generic:
struct Model {
let string: String
let process: () -> Void
init<C: Collection>(_ string: String,
_ elements: C,
_ process: @escaping (C.Element) -> Void) {
self.string = string
self.process = { elements.forEach(process) }
}
}
let list = [Model("section1", AnyRealmCollection<Car>(), { _ in return }),
Model("section2", Array<Driver>(), { _ in return }),
Model("section3", AnyRealmCollection<Passenger>(), { _ in return })
]
If sometimes you need a Model and sometimes you need to put it in an array without knowing the element, then you can build a type-eraser instead:
struct AnyModel {
let string: String
let process: () -> Void
init<Element>(_ model: Model<Element>) {
self.string = model.string
self.process = { model.elements.forEach(model.process) }
}
}
Array<Any>, which you shouldn't do, since in most cases if you need to useAnyyou're not solving the problem correctly.