13

This recent SO discussion has confused me. The NSMutableArray prototype for addObject: is

- (void)addObject:(id)anObject

and id is defined in objc.h as

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id; 

When I add an NSObject or subclass to an NSMutableArray, its retain count is incremented, and when I remove it from an NSMutableArray it is decremented. Does this mean that if an id type which is not an NSObject or subclass is added to an NSMutableArray, it has to respond to retain and release messages? The definition of id does not seem to force this. Is it an objective C directive that any id type should respond to standard memory management messages?

Community
  • 1
  • 1
jbat100
  • 16,757
  • 4
  • 45
  • 70
  • Why do you want to use an object not derived from NSObject? – semisight Oct 26 '11 at 15:32
  • I don't, i'm just interested in how NSMutableArray (and all mutable collections) memory management works internally. – jbat100 Oct 26 '11 at 15:35
  • While it doesn't answer your question, `retain` and `release` are actually declared in the NSObject protocol, not the NSObject interface, so descending from NSObject is not necessary. – Matt Wilding Oct 26 '11 at 15:43

6 Answers6

7

The hard truth about most Foundation containers (and by extent most Apple-developed classes, and by extent also most classes developed by third parties) is that when a method accepts the id type, it should really read id<NSObject>, which means any type that responds to the NSObject protocol. Instances of classes that aren't part of the NSObject hierarchy are unlikely to respond to -retain and -release, which is especially inconvenient when trying to add them to a container. They're also unlikely to respond to -hash, -isEqual:, -description, -copy, and to all the other methods Foundation containers can use on their contents for whatever reason.

For instance, if you attempt to add Class objects to a Foundation container (other than NSMapTable since this one was designed with a lot of flexibility in mind), you'll hit a wall because "modern" ObjC classes are expected to inherit from NSObject, or at least implement the NSObject protocol.

This is a pretty rare situation, though. Class is pretty much the only useful class around that doesn't inherit from NSObject.

zneak
  • 134,922
  • 42
  • 253
  • 328
4

Does this mean that if an id type which is not an NSObject or subclass is added to an NSMutableArray, it has to respond to retain and release messages?

By default, Yes, although there are workarounds to this using CF-APIs.

The definition of id does not seem to force this. Is it an objective C directive that any id type should respond to standard memory management messages?

It's just how the libraries have been written; Root classes (does not inherit from NSObject) are very unusual. An alternative could be - (void)addObject:(id<NSObject>), but that would require a rather large extension to your root class... perhaps a better solution would have been a protocol NSReferenceCounted which takes the relevant bits from NSObject.

However, the NS-collections types really assume that they are dealing with NSObjects (e.g. dictionaries use hash and description).

justin
  • 104,054
  • 14
  • 179
  • 226
  • 1
    nice thought for the NSReferenceCounted protocol, although as pointed out by zneak, other non-memory-management-related messages are used by collections (-hash, -isEqual:, -description, -copy), so maybe an NSCollectable protocol... Would be a sensible adjustment for the future of objective C. Cheers for the answer. – jbat100 Oct 26 '11 at 21:46
2

No, there is no convention that objects of type "id" should respond to retain/release messages; in fact, one might say guaranteeing the existence of those kinds of methods is the purpose of the NSObject protocol (not the class). However, "id" does tell the compiler "don't bother type checking", so when you add an object to an nsarray that does not implement those methods, it will compile, but you will get a runtime crash. See http://unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs-id.html for a more detailed explanation.

edsko
  • 1,628
  • 12
  • 19
0

You shouldn't be putting non-object types in an NSArray, for instance if you wish to store an array of CGRects then you wrap them up in NSValue objects and store those (this is the same for CGPoint, CGSize etc).

If you have a custom struct then you will need a NSObject descendent wrapper, which kind of defeats the purpose in the first place!

Simon Lee
  • 22,304
  • 4
  • 41
  • 45
  • 2
    I know, and have experienced the pain of doing so a few years ago, I'm just wondering why the prototype for addObject: is - (void)addObject:(id)anObject, and not - (void)addObject:(id)anObject; – jbat100 Oct 26 '11 at 15:38
  • @jbat100 Completely agree, it is quite misleading! – Simon Lee Oct 26 '11 at 15:38
  • I don't think he's asking about putting C structs into an array. He's asking about putting `id`s that do not descend from NSObject (like NSProxy). – Matt Wilding Oct 26 '11 at 15:40
  • NSProxy conforms to NSObject so that is probably a bad example. – Simon Lee Oct 26 '11 at 15:42
  • @Simon Lee, Yes it does, but it's the first one I could think of. – Matt Wilding Oct 26 '11 at 15:45
  • 3
    History is why; `` didn't really come into play until after `NSArray`'s interface was already written to take `(id)` under the then-mistaken notice that there might be other root classes that are still collection class compatible. Many years of experience has proven that assumption was incorrect. – bbum Oct 26 '11 at 16:30
0

If you really need an array that holds items that you don't want retained/released, you might want to create it using CFArrayCreateMutable and pass it appropriate callbacks. (CFMutableArray is toll-free bridged to NSMutableArray.)

David Dunham
  • 8,139
  • 3
  • 28
  • 41
0

Besides the technical discussions above, I think the answer is related to the history of the language and its preference for conventions over syntax. Formal protocols weren't originally part of the language — everything was informal. Apple have subsequently mostly shifted to formal protocols, but informal protocols are part of the language and are used by parts of the official API. They are therefore a fully supported, first class part of Objective-C.

If you look at the documentation to NSArray, it says, amongst other things:

In most cases your custom NSArray class should conform to Cocoa’s object-ownership conventions. Thus you must send retain to each object that you add to your collection and release to each object that you remove from the collection. Of course, if the reason for subclassing NSArray is to implement object-retention behavior different from the norm (for example, a non-retaining array), then you can ignore this requirement.

And:

NSArray is “toll-free bridged” with its Core Foundation counterpart, CFArray [...] you can sometimes do things with CFArray that you cannot easily do with NSArray. For example, CFArray provides a set of callbacks, some of which are for implementing custom retain-release behavior. If you specify NULL implementations for these callbacks, you can easily get a non-retaining array.

Your question therefore rests on a false premise, specifically either a partial reading of the documentation or an incomplete consideration of the language.

id <NSObject> is correctly not used because the NSObject protocol doesn't match the protocol that objects must implement to be usable with NSArray. Instead an informal protocol, that happens to be a subset of NSObject, is established in the documentation (albeit at a second remove of informality, which is rare). Although informal protocols are increasingly uncommon, they're valid and not yet exceptional.

So, the short version of my answer is: NSArray documents an informal protocol, that protocol isn't the same as NSObject. Informal protocols aren't represented in syntax, hence id.

Tommy
  • 99,986
  • 12
  • 185
  • 204