1

Need help to model the below with Swift. I would like to build an array of tuple, Array<Tuple>, which has three elements each

  1. a String field
  2. either an Array<SomeElement>, or AnyRealmCollection<SomeObject>
  3. a closure that acts upon the specific SomeElement or SomeObject.

    As an example, Array<Tuple> can capture:

    [("section1", AnyRealmCollection<Car>(), {}),
     ("section2", Array<Driver>(), {},
     ("section3", AnyRealmCollection<Passenger>(), {}
    ]

Then, in a separate method, forEach of the Tuple within the Array, the corresponding closure is triggered to process each of the SomeElement/SomeObject within Array<SomeElement> or AnyRealmCollection<SomeObject>.

With this model, I hope to be able to swap in other SomeElement/SomeObject, add new entries to Array<Tuple> with ease in a Type safe manner.

Any advise?

2
  • 1
    You cannot have heterogenous arrays, so that won't work unless you declare the array as Array<Any>, which you shouldn't do, since in most cases if you need to use Any you're not solving the problem correctly. Commented Feb 19, 2019 at 11:05
  • 1
    Start with a struct, not a tuple. Commented Feb 19, 2019 at 13:05

2 Answers 2

0

You can't have an array of different types as mentioned but you could use a protocol that the elements in the array needs to conform to. But as far as I know you can't make a tuple conform to a protocol but a struct can.

So lets create a protocol for the array items

protocol TupleHandler {
    func iterate() -> Void
}

and a struct that conforms to the protocol and mimics your tuple

struct TupleStruct<T> : TupleHandler {
   var string: String
   var array: [T]
   var action: (T)->Void

   func iterate() {
       array.forEach({ action($0) })
   }
}

And then it can be used like

let ts1 = TupleStruct<String>(string: "abc", array: ["A", "B", "C"], action: { print($0) })
let ts2 =  TupleStruct<Int>(string: "def", array: [3, 5, 7], action: { print($0 * 2) })
let array: [TupleHandler] = [ts1, ts2]

for item in array {
    item.iterate()
}
Sign up to request clarification or add additional context in comments.

Comments

0

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

6 Comments

Thanks, I tried the enum Container before, AnyRealmCollection<Element> requires Element:RealmSwift.Object while the dynamic nature of Realm Object on Array might have some runtime issue. How should this be modelled?
I'm not sure what you mean about "the dynamic nature of Realm Object on Array" here. There generally would be no need to model the requirements of AnyRealmCollection in order to accept one as a parameter. The fact that the caller constructed the AnyRealmCollection in the first place proves that its type requirements have been met. You don't need to re-enforce that requirement.
I tried the enum Container just now, case realmCollection(AnyRealmCollection<Element>) results in Type 'Element' does not conform to protocol 'RealmCollectionValue'
Here, icanzilb, on Dec9, 2016, states that RxRealm Objects might have issues. github.com/RxSwiftCommunity/RxDataSources/issues/…
Updated; yeah, you're correct about the type requirement issue. I'm not sure how icanzlib's comments apply to this situation, however (other than you're mixing value types and "change-behind-your-back" reference types in a way that probably should never go into the same data structure).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.