Other than giving old school Objective-C programmers heart attacks, are there any other performance implications to doing this:
NSMutableArray *derp = @[].mutableCopy
Versus this:
NSMutableArray *derp = [[NSMutableArray alloc] init];
Other than giving old school Objective-C programmers heart attacks, are there any other performance implications to doing this:
NSMutableArray *derp = @[].mutableCopy
Versus this:
NSMutableArray *derp = [[NSMutableArray alloc] init];
I am going to give a different sort of answer than the ones below.
Unless you've measured the performance of your app and found it lacking, and found it lacking right in that part of the code, you're optimizing for the wrong thing. (And if you have done that, you can easily measure which one is faster.)
You should be optimizing for clarity of code, speed with which you can write it, and likelihood that it's correct.
In that respect, I would favor the second code snippet over the first. It says pretty much exactly what you're trying to do. The first snippet is not only harder to read; it also commits the style error of using dot notation to invoke a method, not get the value of a property.
Plus causing heart attacks just isn't nice. :)
Class clusters do not have an extra allocation and it has nothing to do with compiler magic. So yes, there is a significant difference between your examples (btw, -1000 internet points for dot-notation abuse.) Your first example has two allocations, the second has only one.
Since we don't have access to the actual source code for NSArray, we can look at GNUStep (an open source implementation) to see how they handle it. In NSArray.m (simplifying and omitting irrelevant stuff):
static GSPlaceholderArray *defaultPlaceholderArray;
+ (void) initialize
{
defaultPlaceholderArray = (GSPlaceholderArray*)
NSAllocateObject(GSPlaceholderArrayClass, 0, NSDefaultMallocZone());
}
+ (id) alloc
{
return defaultPlaceholderArray;
}
What's going on here is that NSArray defines a singleton, placeholder object that it always returns in alloc
. When init
is called on this singleton it instantiates the proper private subclass and returns that.
So, how can we tell if Apple's Foundation is doing the same thing? Pretty easy, we just run this test:
NSArray *a1 = [NSArray alloc];
NSArray *a2 = [NSArray alloc];
NSLog(@"%p, %p", a1, a2);
> "0x100102f30, 0x100102f30"
a1
and a2
do have the same memory location meaning Apple is also likely using the singleton approach. If we print out the class name it's __NSPlaceholderArray
so that pretty much confirms it.
So yeah, stick with [NSMutableArray new]
:)
UPDATE: Greg Parker points out that @[]
is also a singleton so @[].mutableCopy
results in only one allocation. So performance-wise the two examples are the same.
It's hard to tell exactly how many objects are created, especially in the case of the array literal.
The official docs of clang
say that @[]
expands to arrayWithObjects:count:
, which I suspect is implemented as [[[self alloc] initWithObjects:objects count:count] autorelease]
.
So, there may be a second object allocated at that point, since NSArray
is a class cluster, and the implementation of - [NSArray init]
may look something like this:
- (id)init
{
[self release];
self = [[__NSArrayI alloc] init];
return self;
}
To confirm my suspicion, I've written a little program that prints the class of various types of NSArray
objects at different stages. Note that in order to be really sure, it would be necessary to catch all of the allocation and initialization methods of NSArray
. Until we do that, we can only speculate. But anyway, here's the code:
#import <Foundation/Foundation.h>
int main()
{
NSArray *a = [NSArray alloc];
NSLog(@"NSArray before init: %@", a.class);
a = [a init];
NSLog(@"NSArray after init: %@", a.class);
NSArray *al = @[];
NSLog(@"Array literal: %@", al.class);
NSMutableArray *ma1 = [a mutableCopy];
NSLog(@"NSMutableArray (copied): %@", ma1.class);
NSMutableArray *ma2 = [NSMutableArray alloc];
NSLog(@"NSMutableArray (manufactured) before init: %@", ma2.class);
ma2 = [ma2 init];
NSLog(@"NSMutableArray (manufactured) after init: %@", ma2.class);
return 0;
}
And here's what it prints (NSLog()
clutter removed for clarity):
h2co3-macbook:~ h2co3$ ./quirk
NSArray before init: __NSPlaceholderArray
NSArray after init: __NSArrayI
Array literal: __NSArrayI
NSMutableArray (copied): __NSArrayM
NSMutableArray (manufactured) before init: __NSPlaceholderArray
NSMutableArray (manufactured) after init: __NSArrayM
Edit: here's some more code with the hooking involved. The results are very interesting but this prints a lot of text, so you are encouraged to compile and run it. What turns out is, basically, that the initializers do not go through [NSArray init]
directly:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
void hook(Class cls, SEL sel, IMP newimp, IMP *old)
{
Method m = class_getInstanceMethod(cls, sel);
*old = method_setImplementation(m, newimp);
}
#define CLS(c) objc_getClass(#c)
#define META(c) objc_getMetaClass(#c)
IMP old_$_NSArray_$_alloc;
IMP old_$_NSMutableArray_$_alloc;
IMP old_$_NSPlaceholderArray_$_alloc;
IMP old_$_NSArrayI_$_alloc;
IMP old_$_NSArrayM_$_alloc;
IMP old_$_NSArray_$_init;
IMP old_$_NSMutableArray_$_init;
IMP old_$_NSPlaceholderArray_$_init;
IMP old_$_NSArrayI_$_init;
IMP old_$_NSArrayM_$_init;
id new_$_NSArray_$_alloc(id self, SEL _cmd)
{
printf("+ [NSArray<%p> alloc]\n", self);
return old_$_NSArray_$_alloc(self, _cmd);
}
id new_$_NSMutableArray_$_alloc(id self, SEL _cmd)
{
printf("+ [NSMutableArray<%p> alloc]\n", self);
return old_$_NSMutableArray_$_alloc(self, _cmd);
}
id new_$_NSPlaceholderArray_$_alloc(id self, SEL _cmd)
{
printf("+ [NSPlaceholderArray<%p> alloc]\n", self);
return old_$_NSPlaceholderArray_$_alloc(self, _cmd);
}
id new_$_NSArrayI_$_alloc(id self, SEL _cmd)
{
printf("+ [NSArrayI<%p> alloc]\n", self);
return old_$_NSArrayI_$_alloc(self, _cmd);
}
id new_$_NSArrayM_$_alloc(id self, SEL _cmd)
{
printf("+ [NSArrayM<%p> alloc]\n", self);
return old_$_NSArrayM_$_alloc(self, _cmd);
}
id new_$_NSArray_$_init(id self, SEL _cmd)
{
printf("- [NSArray<%p> init]\n", self);
return old_$_NSArray_$_init(self, _cmd);
}
id new_$_NSMutableArray_$_init(id self, SEL _cmd)
{
printf("- [NSMutableArray<%p> init]\n", self);
return old_$_NSMutableArray_$_init(self, _cmd);
}
id new_$_NSPlaceholderArray_$_init(id self, SEL _cmd)
{
printf("- [NSPlaceholderArray<%p> init]\n", self);
return old_$_NSPlaceholderArray_$_init(self, _cmd);
}
id new_$_NSArrayI_$_init(id self, SEL _cmd)
{
printf("- [NSArrayI<%p> init]\n", self);
return old_$_NSArrayI_$_init(self, _cmd);
}
id new_$_NSArrayM_$_init(id self, SEL _cmd)
{
printf("- [NSArrayM<%p> init]\n", self);
return old_$_NSArrayM_$_init(self, _cmd);
}
int main()
{
hook(META(NSArray), @selector(alloc), (IMP)new_$_NSArray_$_alloc, &old_$_NSArray_$_alloc);
hook(META(NSMutableArray), @selector(alloc), (IMP)new_$_NSMutableArray_$_alloc, &old_$_NSMutableArray_$_alloc);
hook(META(__NSPlaceholderArray), @selector(alloc), (IMP)new_$_NSPlaceholderArray_$_alloc, &old_$_NSPlaceholderArray_$_alloc);
hook(META(__NSArrayI), @selector(alloc), (IMP)new_$_NSArrayI_$_alloc, &old_$_NSArrayI_$_alloc);
hook(META(__NSArrayM), @selector(alloc), (IMP)new_$_NSArrayM_$_alloc, &old_$_NSArrayM_$_alloc);
hook(CLS(NSArray), @selector(init), (IMP)new_$_NSArray_$_init, &old_$_NSArray_$_init);
hook(CLS(NSMutableArray), @selector(init), (IMP)new_$_NSMutableArray_$_init, &old_$_NSMutableArray_$_init);
hook(CLS(NSPlaceholderArray), @selector(init), (IMP)new_$_NSPlaceholderArray_$_init, &old_$_NSPlaceholderArray_$_init);
hook(CLS(NSArrayI), @selector(init), (IMP)new_$_NSArrayI_$_init, &old_$_NSArrayI_$_init);
hook(CLS(NSArrayM), @selector(init), (IMP)new_$_NSArrayM_$_init, &old_$_NSArrayM_$_init);
NSArray *a = [NSArray alloc];
NSLog(@"NSArray before init: %@<%p>", a.class, a);
a = [a init];
NSLog(@"NSArray after init: %@<%p>", a.class, a);
NSArray *al = @[];
NSLog(@"Array literal: %@<%p>", al.class, al);
NSMutableArray *ma1 = [a mutableCopy];
NSLog(@"NSMutableArray (copied): %@<%p>", ma1.class, ma1);
NSMutableArray *ma2 = [NSMutableArray alloc];
NSLog(@"NSMutableArray (manufactured) before init: %@<%p>", ma2.class, ma2);
ma2 = [ma2 init];
NSLog(@"NSMutableArray (manufactured) after init: %@<%p>", ma2.class, ma2);
return 0;
}
Yeah, one is allocating 2 objects, the other is not. I do [NSMutableArray new]
UPDATE: Greg Parker is right, both allocate only one object.