8

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>
2
  • An NSArray instance is actually a "bridged" instance of NSCFArray (or something like that). In effect a subclass, though I don't know how the wiring behind the curtain works. Commented Nov 6, 2012 at 17:04
  • Yes I'm starting to see something like that... Interestingly, the class of the array object in the above code is __NSArrayI and __NSArrayM. I'm still not sure that explain the effect about though Commented Nov 6, 2012 at 17:18

1 Answer 1

26

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!)

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

2 Comments

Correct me if I'm wrong, but I'm pretty sure NSObject's copy method actually does return [self copyWithZone:nil] (or maybe it grabs a specific zone to use). The metaclass, like many others, implements it to be return [self retain]
Yup -- Mike is right... there is an extra method invocation through the, now no-op-esque, copyWithZone:. Fixed.

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.