Array of Structs, 2D Arrays or ?

Hello, I am an absolute newbie to swift and writing my first iPhone app and I can't figure out how to store my data within my program before I commit it to persistant storage.


My program needs to hold lots of structured data entered by the user during program execution before it is committed to persistent storage (on a remote database).


I have 3 Structs (since I thought this is the best path to go down), TopLevelStruct, MidLevelStruct and BottomLevelStruct and the latter 2 structs are nested arrays within their immediate predecessor. (note there may be typos in this code...please ignore...I'm just trying to understand how to structure my program). I want the ability to reset the array index in the bottom structure for each instance of the mid structure. See below


struct bottomLevelStruct {

var bottomNum: Int

var bottomDescription: String

var bottomResult: String

}


struct MidLevelStruct{

var midNum: Int

var midTotal: Int

var bottomLevelDetails = [bottomLevelStruct] ()

}


struct TopLevelStruct{

var date: String

var total: Int

var midLevelDetails = [MidLevelStruct] ()

}

// declare the bottom level struct and set some data

var myBottomLevelStructArray = [bottomLevelStruct(bottomNum: 1, bottomDescription: "something", bottomResult: "Failed")]

myBottomLevelStructArray.append(bottomLevelStruct(bottomNum: 1, bottomDescription: "2nd thing", bottomResult: "Success"))


/ / declare the mid level struct

var myMidLevelStructArray = [MidLevelStruct.init(midNum: 1, midTotal: 3, bottomLevelDetails: myBottomLevelStructArray)]


/ / and the top level struct

var myTopLevelStruct = TopLevelStruct.init(date: "today", total: 22, myTopLevelStruct: midLevelDetails)


/ / now add the data to the bottom struct that will be associated with the 2nd mid level array element

myBottomLevelStructArray.append(bottomLevelStruct(bottomNum: 2, bottomDescription: "New thing", bottomResult: "OK"))

myBottomLevelStructArray.append(bottomLevelStruct(bottomNum: 2, bottomDescription: "Wild thing", bottomResult: "Success"))


/ / append the data to the mid level array

myMidLevelStructArray.append = (MidLevelStruct.init(midNum: 2, midTotal: 3, bottomLevelDetails: myBottomLevelStructArray)


And here is where my problem begins. Although I can access the mid level data by array index such as (these are ok):

myMidLevelStructArray[0].midNum //and

myMidLevelStructArray[0].bottomLevelDetails[0]

myMidLevelStructArray[1].midNum


However, I want to be able to access the bottom level details in subsequent midLevelArrays starting at index 0. Right now the 1st bottom level element of the 2nd mid array is:

myMidLevelStructArray[1].bottomLevelDetails[2]


I want to be able to access this as:

myMidLevelStructArray[1].bottomLevelDetails[0]

since the number of bottomLevel details has a 1 to many relationship with the mid level structure.


Can this be done using this structure or do I need to restructure my code in another manner. Sorry for the verbose entry....couldn't figure out another way of explaning it.


Thank you.

With your data structures, if you want too save with keyedArchiving, I would do as follow.


1. Replace struct by class and implement coder / decoder in each.


For instance, for bottomLevelStruct


class BottomLevelStruct: NSObject, NSCoding {

var bottomNum: Int

var bottomDescription: String

var bottomResult: String


override init() {

self.bottomNum = 0

self.bottomDescription = String()

self.bottomResult = String()

super.init()

}


init(bottomNum: Int, bottomDescription: String, bottomResult: String) {

self.bottomNum = bottomNum

self.bottomDescription = bottomDescription

self.bottomResult = bottomResult

}


required init(coder decoder: NSCoder) {

self.bottomNum = decoder.decodeObjectForKey("NumKey") as! Int

self.bottomDescription = decoder.decodeObjectForKey("DescrKey") as! String

self.bottomResult = decoder.decodeObjectForKey("ResultKey") as! String

}


func encodeWithCoder(coder: NSCoder) {

coder.encodeObject(self.bottomNum, forKey: "NumKey")

coder.encodeObject(self.bottomDescription, forKey: "DescrKey")

coder.encodeObject(self.bottomResult, forKey: "ResultKey") }


}


2. Do the same with other classes :


class MidLevelStruct: NSObject, NSCoding {

var midNum: Int

var midTotal: Int

var bottomLevelDetails = [BottomLevelStruct] ()


To encode / decode bottomLevelDetails :

self.bottomLevelDetails = decoder.decodeObjectForKey("BottomLevelDetailsKey") as! Array<BottomLevelStruct>


coder.encodeObject(self.bottomLevelDetails, forKey: "BottomLevelDetailsKey") }


}


3. To archive, you will archive on a file the toplevel:

let data = NSMutableData()

let archiver = NSKeyedArchiver(forWritingWithMutableData: data)

let dataToSave : TopLevelStruct()

// Initialize with data

archiver.encodeObject(dataToSave, forKey: "TopLevelKey")

archiver.finishEncoding()


4. To deArchive

let unarchiver = NSKeyedUnarchiver(forReadingWithData: data)

let data = unarchiver.decodeObjectForKey("TopLevelKey") as! TopLevelStruct

unarchiver.finishDecoding()


5. Advice: capitalize the class or struct names, it helps readibility.


6. There may be better data structures for your objective, with chained lists

Thank you. I will keep that in mind as I look for a solution to my problem.

I advise you to try this, make it work. You'll learn a lot by doing so.


Then you can think of anoter better design.

Another approach - use the CoreData model creation tool in Xcode and auto-generate the BottomLevel, MidLevel and TopLevel structs (classes, actually) out of it. Use CoreData for persistence and sync with the remote database from CoreData. There are some advantages of this approach that come to mind:

- The app will work offline

- Network stuff can be done in intervals, which saves battery life

- Probably better user experience as the syncing can be done independently on user interaction

- Remote database access can be completely isolated from the user interaction code and debugging/troubleshooting might be easier

You example is a bit hard to decode, because there's too much of it, but I think what's gone wrong is that you've forgotten that you'll have multiple instances of your mid-level struct in the top-level struct, and multiple instances of your bottom-level struct in each mid-level struct.


So, it makes no sense to have a single variable for mid-level structs, and a single variable for bottom-level structs.


What you'll do is start by creating your single top-level struct. Initially, its midLevelDetails array will have 0 elements. In your example, you want two of them, so you will append 2 mid-level struct instances to the midLevelDetails array. At that point, each of the 2 mid-level instances will have 0 bottom-level instances. Separately for each of these 2 instances, you will add bottom-level instances to fill out your tree structure.


When creating mid- and top-level instances, you can pass the initial array of sub-structs to the init method as you've shown, but there's really no point, since you need to be able to add more instances later anyway. I think you'll find it easier if you use the same append method to fill out the initial substructures as to add substructures later.


The other thing to keep in mind is that Swift arrays are value types. If you pass one as a method parameter, you're passing a copy of its value. Anything you do to the copy is not reflected in the original array, which may or may not be what you want. It's possible that you will need to define your data structures as reference types (i.e. as "class", not "struct"), so that you can pass around references to the data instead of copying. That will allow you to modify the original arrays. However, it's not clear yet which approach is going to serve you better.

@MirekE and @QuinceyMorris


Thank you for your replies. Great information and it gives me lots to think about. I appreciate your responses.


Mark

Array of Structs, 2D Arrays or ?
 
 
Q