2

I have a dictionary of arrays that contain one kind of objects : database records. Each array is like a table associated to a key (table name) in the dictionary.

My issue is: How do I update a table (delete a record, add a record, etc) without making any temporary copy of the table, like I would do with an NSDictionary of NSArrays ?

Currently, I do :

func addRecord(theRecord: DatabaseRecord, inTable tableName: String)
{
    if var table = self.database[tableName]
    {
        table.append(theRecord)
        self.database[tableName] = table
    }
    else
    {
        self.database[tableName] = [theRecord];
    }
}

The issue is: a temporary copy of the table is made twice. This is pretty inefficient.

2 Answers 2

4

Swift arrays use value (copy) semantics but in fact they are passed and assigned as references. Only when a change of the array size or an reordering of the elements occurs the array is really copied. This is called "lazy copy" or "copy on write". You do not have to be scared to simply assign an array like this:

var temp = someBigArray 

It is not copied. But when you call:

temp.append(someValue)

Then an actual copy happens.

In your code in this line:

if var table = self.database[tableName]

The array is not copied.

In this line:

table.append(theRecord)

It is copied.

In this line:

self.database[tableName] = table

It is not copied but the ref-count of the array in the dictionary is decremented by one and if no other reference to this array exists it is deallocated. The "table" array is then just passed as a reference into "self.database[tableName]". No copy occurs here.

But Eric D's answer is correct, by calling append() directly on the array (with the ? operator) the least overhead is created. This is how you should do it:

self.database[tableName]?.append(theRecord)

But it can happen also in append() that the array get's copied - which is the case if the array size has to be extended and there is no pre-allocated space. This is because arrays always has to have contiguous memory.

So by calling append() it can happen that the array has to do this:

  • allocate a new contiguous memory block with the current count size plus at least one entry more. (usually it allocates even more then that, just in case you will append more in the future)
  • copy the whole content into the new memory block (including the new entry)
  • release the "old" memory (this is in fact more complicated because other arrays could reference the old memory, in this case it's not released)

So usually you do not have to care about "inefficient" copy of arrays - the compiler knows better.

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

2 Comments

Thank you very much for this well detailed answer. I guess, NSArray had to do that kind of voodoo when inserting or deleting elements in arrays. I always trusted NSArray to be implemented to do it extremely efficiently. So I guess I can also trust the swift compiler to be efficient. But I just want to avoid the overhead of programming mistakes like the code I did show in my question. I know, premature optimization... but in the present situation, a little bit of knowledge on how things work doesn't do any harm. So thank you. I marked the first correct answer as solving the issue +1 to everyone
It could be that the compiler is even smart enough to see that the initial array is also assigned in the last step and temp is just, well - temp, so irrelevant. In this case also not even one copy occurs. But of course I am not sure about that.
2

I think this is a case where you can use optional binding with ? and directly append the value (here with a ternary operator):

func addRecord(theRecord: DatabaseRecord, inTable tableName: String) {

    self.database[tableName] == nil ? self.database[tableName] = [theRecord] : self.database[tableName]?.append(theRecord)

}

4 Comments

Thank you for this elegant solution. So, this won't at any stage copy the (large) table of the database ? I am sorry that I can't upvote yet.
You're welcome. Indeed, no copy in this version. 1st part checks for nil, 2nd part creates an array with one item, 3rd part appends to the array only if it exists (and we checked that it does so it won't fail anyway).
I think this is not fully true, because there is at least one copy we have to make for the comparison with nil. We are using the getter and this will copy the array once. Correct me if I'm wrong. (Btw. copies aren't that bad in Swift. Copies are also lightweight and fast. ... I also would ease your code like this if self.database[tableName]?.append(theRecord) == nil { self.database[tableName] = [theRecord] }. This makes no difference for the functionality but it is better to read in my opinion.
Well, I strongly believe that Swift does not make any copy for the nil comparison but of course I have no proof of that, although I guess it could be verified in Instruments... -- And anyway, in Swift, values are copied on write, meaning for example "let b = a" doesn't actually copy "a" until "b" is used, and here we don't do anything with the value.

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.