Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-c Internals for NSArray and NSMutableArray

Very interesting question for all those into Objective-c internals...

So... the NSObject returns the same implementation of copy for both and object and a class (As I expect). However, NSArray and NSMutableArray return not just different implementations of objectAtIndex: for an object and class but each object has a different implementation.

Does anyone know why the follow code produces such behaviour?... (Atleast the class implementations for NSArray and NSMutableArray are the same :) )

NSObject *obj = [[[NSObject alloc] init] autorelease];
NSLog(@"NSObject instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(obj), @selector(copy)))]);
NSLog(@"NSObject class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSObject class], @selector(copy)))]);

NSArray *array = [[[NSArray alloc] init] autorelease];
NSLog(@"NSArray instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(array), @selector(objectAtIndex:)))]);
NSLog(@"NSArray class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSArray class], @selector(objectAtIndex:)))]);

NSMutableArray *array1 = [[[NSMutableArray alloc] init] autorelease];
NSLog(@"NSMutableArray instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(array1), @selector(objectAtIndex:)))]);
NSLog(@"NSMutableArray class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSMutableArray class], @selector(objectAtIndex:)))]);

The Log

2012-11-06 16:35:22.918 otest[71367:303] NSObject instance <c0fa7200>
2012-11-06 16:35:23.757 otest[71367:303] NSObject class <c0fa7200>
2012-11-06 16:35:30.348 otest[71367:303] NSArray instance <809a9b00>
2012-11-06 16:35:31.121 otest[71367:303] NSArray class <70bfa700>
2012-11-06 16:35:33.854 otest[71367:303] NSMutableArray instance <f05f9a00>
2012-11-06 16:35:34.824 otest[71367:303] NSMutableArray class <70bfa700>
like image 484
Eamonn Moloney Avatar asked Nov 06 '12 16:11

Eamonn Moloney


People also ask

How do you declare NSArray in Objective-C?

Creating NSArray Objects Using Array Literals In addition to the provided initializers, such as initWithObjects: , you can create an NSArray object using an array literal. In Objective-C, the compiler generates code that makes an underlying call to the init(objects:count:) method.

What is difference between NSArray and NSMutableArray?

The primary difference between NSArray and NSMutableArray is that a mutable array can be changed/modified after it has been allocated and initialized, whereas an immutable array, NSArray , cannot.

Which is faster NSArray or NSMutableArray?

NSMutableArray and NSArray both are build on CFArray , performance/complexity should be same. The access time for a value in the array is guaranteed to be at worst O(lg N) for any implementation, current and future, but will often be O(1) (constant time).

When should we prefer NSMutableArray over NSArray?

NSArray & NSMutableArray NSArray is used to hold an immutable array of objects and the NSMutableArray is used to hold an mutable array of objects.


1 Answers

Implementation details, all of 'em. Thus, this is a relatively well informed bit of conjecture.

First, no need to jump through such hoops to print a hex value, just do:

NSLog(@"imp: %p", [NSObject instanceMethodForSelector:@selector(...)]);

Note that invoking methodForSelector: on a class object returns the IMP for a class method.

Now, on to the question at hand.

One thing that differentiates Objective-C from other popular OO languages (but not all), is that Class objects are actually instances of a Metaclass. That Metaclass -- which really doesn't have a name beyond "metaclass" -- happens to respond to the NSObject protocol (more or less). In fact, it is sorta-kinda a derivative of NSObject.

Thus, when you get the implementation for certain selectors on NSObject for instances and classes, they'll oft be the same. Note that this is what allows a Class to be a key in an NSDictionary; Classes can be copied. NSObject's copy method just does return [self retain]; and, of course, retain on a class is a no-op since they are [almost always] statically compiled into the binary as singletons. Well, technically, copy calls copyWithZone: that does return [self retain]; (which is why you have to subclass copyWithZone: even though zones are deprecated).

Now, as Hot Licks pointed out, NS*Array is a class cluster whose internal implementation details have changed over the last few releases. It used to be that all instances were all bridged to NSCFArray that was configured differently. In more recent releases, you'll see (sic.) __NSArrayI and __NSArrayM instances that correspond to immutable and mutable instances. Mostly -- there is more going on than just that, but that is pretty typical.

That the instance methods for objectAtIndex: are different between the two classes is typical of class clusters. The point of a cluster is to provide a primitive interface with a bunch of methods implemented in terms of that primitive interface (which is why the headers are divided between the core @interface and a series of categorical @interfaces; the categories in the base classes are implemented entirely in terms of the core API).

Internally, there are concrete subclasses of the publicly declared classes within the cluster. Those concrete subclasses are highly optimized to a particular task -- immutable vs. mutable array storage, in this case (but there may be many more non-public subclasses optimized to different purposes) -- where the subclasses override the advertised API to provide highly optimized versions of the various methods.

Thus, what you are seeing across the two classes are different implementations of objectAtIndex: optimized for the mutable vs. the immutable case.

Now, why are the class methods the same? Good question. Let's try calling it:

((void(*)(id,SEL,int))[[NSArray class] methodForSelector: @selector(objectAtIndex:)])([NSArray class], @selector(objectAtIndex:), 0);


2012-11-06 09:18:23.842 asdfasdf[17773:303] *** Terminating app due to uncaught
   exception 'NSInvalidArgumentException', reason: '+[NSArray objectAtIndex:]:
   unrecognized selector sent to class 0x7fff7563b1d0'

Aha! So, you are asking for the implementation of a method that, when invoked, barfs up a "does not recognize this method" exception. In fact:

NSLog(@"%@", 
 [NSArray class] respondsToSelector:@selector(objectAtIndex:)] ? @"YES" : @"NO");

2012-11-06 09:24:31.698 asdfasdf[17839:303] NO

It looks like the runtime is returning an IMP that will barf up a nice error indicating that you tried -- through roundabout means -- to use a selector that the targeted object (a metaclass instance, in this case) does not respond to. Which is why the documentation for instanceMethodForSelector: states that you should use respondsToSelector: prior in cases where there may be some question about whether the target implements the selector.

(well, that turned into more of a book than intended... hopefully still useful!)

like image 117
bbum Avatar answered Sep 25 '22 00:09

bbum