Jonas Jonas - 7 months ago 13
Swift Question

Swift: Setting one value of an array changes others simultane

This is how I have set up my data structure:

class Commit: NSObject, NSCoding {

//MARK: Properties
var contents : String
var repeatStatus : Bool
var completionStatus : Bool

//MARK: Initialization
init(contents: String, repeatStatus: Bool, completionStatus: Bool) {

self.contents = contents
self.repeatStatus = repeatStatus
self.completionStatus = completionStatus

super.init()

}

//MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder){
aCoder.encodeObject(contents, forKey: PropertyKey.contentsKey)
aCoder.encodeObject(repeatStatus, forKey: PropertyKey.repeatStatusKey)
aCoder.encodeObject(completionStatus, forKey: PropertyKey.completionStatusKey)

}

required convenience init?(coder aDecoder: NSCoder) {

let contents = aDecoder.decodeObjectForKey(PropertyKey.contentsKey) as! String
let repeatStatus = aDecoder.decodeObjectForKey(PropertyKey.repeatStatusKey) as! Bool
let completionStatus = aDecoder.decodeObjectForKey(PropertyKey.completionStatusKey) as! Bool

self.init(contents: contents, repeatStatus: repeatStatus, completionStatus: completionStatus)
}

}

class ToDoList: NSObject, NSCoding {

//MARK: Properties
var commitArray : [Commit]
var date : Int

// MARK: Archiving Paths

static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("toDoList")

//MARK: Initialization
override init(){

let defaultCommit = Commit(contents: "", repeatStatus: false, completionStatus: false)
self.date = dateTodayAsInt
self.commitArray = [defaultCommit, defaultCommit, defaultCommit]

super.init()
}

init(commitArray: [Commit], date: Int) {

self.date = date
self.commitArray = commitArray

super.init()

}

//MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder){

aCoder.encodeObject(commitArray, forKey: PropertyKey.commitArrayKey)
aCoder.encodeObject(date, forKey: PropertyKey.dateKey)

}

required convenience init?(coder aDecoder: NSCoder) {

let commitArray = aDecoder.decodeObjectForKey(PropertyKey.commitArrayKey) as! [Commit]
let date = aDecoder.decodeObjectForKey(PropertyKey.dateKey) as! Int

self.init(commitArray: commitArray, date: date)
}


}

func findIndexOfActiveToDoList(allToDoLists: [ToDoList]) -> Int {

var j = Int(floor(Double((allToDoLists.count/2))))
var count = 0

repeat {
count += 1

if allToDoLists[j].date < dateTodayAsInt {
j = Int(j + j/2)
} else if allToDoLists[j].date > dateTodayAsInt {
j = Int(j - j/2)
} else { return j }

} while count < Int(sqrt(Double(allToDoLists.count/2)))

print(j)
return j }


// MARK: Types

struct PropertyKey {

static let contentsKey = "contents"
static let repeatStatusKey = "repeatStatus"
static let completionStatusKey = "completionStatus"

static let commitArrayKey = "commitArray"
static let dateKey = "date"

}

var toDoList = [Commit]()
var allToDoLists = [ToDoList]()


Later, when I want to update the allToDoLists array, like this:

FirstCommitTextField.text! = allToDoLists[findIndexOfActiveToDoList(allToDoLists)].commitArray[0].contents


No matter what I do, as soon as I set:

SecondCommitTextField.text! = allToDoLists[findIndexOfActiveToDoList(allToDoLists)].commitArray[1].contents


The commit array's first element's content property is changed as well. I have run the app with print comments printing the commit array's first element's content property right before and after setting the commit array's second element's content property - just setting the second property somehow changed the first one.

This leads me to the conclusion that somehow setting on value of an element in an array changes the value of the second element in that array as well. Is this the case? If yes, how can this be avoided/circumvented?

Thanks in advance!

Answer

I suspect you meant to write:

allToDoLists[findIndexOfActiveToDoList(allToDoLists)].commitArray[0].contents = 
    FirstCommitTextField.text!
allToDoLists[findIndexOfActiveToDoList(allToDoLists)].commitArray[1].contents = 
    SecondCommitTextField.text!

If that's the case, the reason why you see the second update affect the contents of commitArray[0] is that you initialized the ToDoList.commitArray:

self.commitArray = [defaultCommit, defaultCommit, defaultCommit]

And your Commit is a regular class not struct so it is passed by reference (see: structure vs class in swift language). Therefore when you change commitArray[0].contents you will see the same value in commitArray[1].contents and commitArray[2].contents.

Note that the object references are preserved by NSCoder so if they were the same object before serialization, they will be the same after deserialization.

Solution

Instead of re-using let defaultCommit = Commit(...) you should make a new instance initialized with the default arguments. For example add a convenience initializer to Commit:

convenience init() {
    self.init(contents: "", repeatStatus: false, completionStatus: false)
}

Then use it:

self.commitArray = [Commit(), Commit(), Commit()]