user1880342
user1880342

Reputation: 128

NSArray's mutableCopy - performance concerns

I have the following concern. I have a piece of code where I care about performance a lot (to be honest, I'm trying to make it as much optimized as possible).

I have something like this:

NSArray *array = [someInstance getArray];
if (something) {
   NSMutableArray *marray = [array mutableCopy];
   [marray removeObject:someObject];
   [marray insertObject:someObject atIndex:0];

   array = marray;
}

The goal is to move someObject to the beginning of the array - quite simple. However, the point is elsewhere. Method [someInstance getArray] returns NSArray *, but internally it also uses NSMutableArray:

- (NSArray *)getArray {
    NSMutableArray *array = [NSMutableArray new];
    // do some stuff here
    return array;
}

I'm trying to keep all my interfaces returning immutable collections. But in the above case I'm thinking of creating an exception from that rule since I care a lot about performance in this piece of code. So I can change the getArray method to the following:

- (NSMutableArray *)getArray {
    NSMutableArray *array = [NSMutableArray new];
    // do some stuff here
    return array;
}

and then change my ultra-efficient code as follows:

NSMutableArray *array = [someInstance getArray];
if (something) {
   [array removeObject:someObject];
   [array insertObject:someObject atIndex:0];
}

Does above change make any sense in the terms of performance?

Upvotes: 0

Views: 760

Answers (1)

Ken Thomases
Ken Thomases

Reputation: 90531

NSArray supports copy-on-write. From the Foundation Release Notes for macOS 10.13 and iOS 11:

Collection Copy-on-Write

NSArray, NSDictionary, and NSSet now implement “copy on write” (CoW), which means that a copy of a mutable instance will often not be a full copy, but a much cheaper, O(1) operation. A full copy is done only when either one of the objects (the original or the copy) is mutated down the line.

Please see WWDC 2017 session 244 “Efficient Interactions with Frameworks” for more info on this.

What that means is that copying the array is cheap. Modifying it while the original is still around, though, is potentially expensive.

So, you could do this:

if (something) {
   NSMutableArray *marray = [array mutableCopy];
   array = nil;
   [marray removeObject:someObject];
   [marray insertObject:someObject atIndex:0];
   array = [marray copy];
}

The copy results in two objects sharing a backing store. By clearing array before mutating marray, the original should be released leaving only one. Then, the mutations don't need to copy it because it's no longer shared.

Also, as recommended in that WWDC video, when you have a method that creates an array that's mutable while you set it up, but that you want to return as immutable, you can and should return a copy instead of just a direct pointer:

- (NSArray *)getArray {
    NSMutableArray *array = [NSMutableArray new];
    // do some stuff here
    return [array copy];
}

That's safer and still cheap.

Upvotes: 2

Related Questions