2

I'm fairly new to Swift, so be gentle...

I have a class, called ProgramOptions, as so:

class ProgramOptions {
   var startDate: Date = Date()
   var trackedActivities = [TrackedItem]()
   var trackedFoods = [TrackedItem]()
   var trackedDrugs = [TrackedItem]()
   ...
}

TrackedItem is another class of mine.

In a TableViewController's code I want to select one of the arrays from an instance of ProgramOptions, based on what section of the table is in question. I want to then do many possible things, like remove or add items, edit them, etc.

Being a Swift beginner, I naively wrote this function:

func trackedArrayForSection(_ section: Int) -> [TrackedItem]? {
      switch(section) {
         case 1:     return programOptions.trackedActivities
         case 2:     return programOptions.trackedFoods
         case 3:     return programOptions.trackedDrugs
         default:    return nil
      }
   }

(Section 0 and Sections > 3 don't have associated arrays so I return nil)

But then harsh reality bit me. I guess the array is a copy. (Or my weak understanding of similar question on StackOverflow indicates that it is sometimes copied.)

So here's the question for you... How could I write my trackedArrayForSection so that I get a reference to actual array sitting in ProgramOptions that I can then add and remove items from?

I could, of course, have a switch statement every place I use this, but there are close to a zillion of them and I'd like to avoid that. I'm assuming there is an easy answer to this and I'm just too ignorant at this point to know it.

Thanks for your help!

9
  • One of the purposes of having Arrays be value types is to discourage exactly what you're trying to do. Rather than passing references to arrays on their own as data models, Swift encourages that you wrap the arrays in a class, and create methods for the various legal operations that should be allowed on the arrays. This makes it easier to change the model, e.g. what if I want my data to change to stored in an associative collection (a Dictionary) rather than an ordered collection (an Array)? I would just change the implementation of my class's methods, but the API would remain the same Commented May 18, 2017 at 19:08
  • You should extract your switch statement to an instance method on your class Commented May 18, 2017 at 19:08
  • Fair enough. Not sure I totally agree with that decision, since that will necessitate a fair amount of boilerplate code... but so be it. Thank you for your thoughtful reply. Commented May 18, 2017 at 19:12
  • 1
    It's really not much boiler plate. Swift takes the belief that Arrays, Dictionaries, Strings or Integers on their own don't constitute models. They really don't, they're an implementation detail of a model. If you, for example, wanted to switch to using an in-memory database for your model, you wouldn't be able to, because all the existing code expects to be working with raw arrays Commented May 18, 2017 at 19:17
  • 1
    You also couldn't change to using a remote back end, a caching layer, a local database, a file (plist/JSON/XML/CSV), either. Commented May 18, 2017 at 19:17

2 Answers 2

2

One possibility is to create a wrapper class to store the array of TrackedItems. Say, for example:

class TrackedItemCollection {
    var items = Array<TrackedItem>()
}

and then in your implementation:

class ProgramOptions {
    var startDate: Date = Date()
    var trackedActivities = TrackedItemCollection()
    var trackedFoods = TrackedItemCollection()
    var trackedDrugs = TrackedItemCollection()
    ...
}

func trackedCollectionForSection(_ section: Int) -> TrackedItemCollection? {
    switch(section) {
        case 1:     return programOptions.trackedActivities
        case 2:     return programOptions.trackedFoods
        case 3:     return programOptions.trackedDrugs
        default:    return nil
    }
}

When you want the array, you use the items property in TrackedItemCollection. As TrackedItemCollection is a class and thus a reference type, you won't have the arrays copyed.

Sign up to request clarification or add additional context in comments.

1 Comment

I'm not a big fan on this... but I believe this is the best answer.
1

I would extract your switch logic to an instance method of your class:

class ProgramOptions {
    var startDate: Date = Date()
    private var trackedActivities = [TrackedItem]()
    private var trackedFoods = [TrackedItem]()
    private var trackedDrugs = [TrackedItem]()

    func trackedItems(forSection section: Int) -> [TrackedItem]? {
        switch section {
            case 1:  return self.trackedActivities
            case 2:  return self.trackedFoods
            case 3:  return self.trackedDrugs
            default: return nil
        }
    }

    func add(trackedActivity activity: TrackItem){
        self.trackedActivities.append(activity)
    }
}

and then in you view you only have to reload the table.

4 Comments

Dumb question: Wouldn't this still be returning a copy of the array though... so when I went to add an element to it (for example) it wouldn't appear in the "official" copy in the ProgramOptions instance?
You could save the change in the property desired and then do a reloadtable. So it will fetch the new data inserted.
@ErikWestwig Well that's just a read only copy. To make changes, you'll add the appropriate methods (e.g. add(trackedActivity:)) that modify the model. In this simple case, they append to the array. A protocol can be made that defines this model's interface, to which this particular implementation conforms. You could then add other implementations, such as MockProgramOptions that provided mock data for unit testing
@ErikWestwig The whole point is to isolate the consumer of this model from the knowledge that this data is stored in an array. That's not their concern.

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.