2 Replies Latest reply: Mar 9, 2008 5:27 AM by Karl Woo
Karl Woo Level 1 Level 1 (0 points)
I'm trying to find my way around Objective-C and Cocoa, and I've stumbled upon a problem I just can't figure out. I think I'm missing something fundamental here. Here's the issue:

I have a subclass of UIView with a NSMutableArray *_balls declared in its interface. In its initWithFrame method, I write

+_balls = [[NSMutableArray arrayWithCapacity:100] retain];+

Somewhat later, I add an instance of Ball to the array:

+Ball* ball = [[Ball init] retain];+
+[_balls addObject:ball];+

Ball has a method draw; here's how it looks in the interface:

+- (void) draw;+

However, when I try to call "draw" on all Balls, I get an exception. Here's how I call draw:

+int arrayCount = [_balls count];+
+NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];+
+int i;+
+for (i = 0; i < arrayCount; i++) {+
+[[_balls objectAtIndex:i] draw];+
}
+[pool release];+

And here's the Exception I get:

+2008-03-09 09:42:39.069 Foo[3203:60b] * +[Ball draw]: unrecognized selector sent to class 0x4160+
+2008-03-09 09:42:39.073 Foo[3203:60b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[Ball draw]: unrecognized selector sent to class 0x4160'+
+2008-03-09 09:42:39.074 Foo[3203:60b] Stack: (+
2484785739,
2502521083,
...

This is running on the iPhone simulator.

Any ideas what I'm doing wrong here? All help would be greatly appreciated.

MacBook Pro, Mac OS X (10.5.2)
  • PsychoH13 Level 2 Level 2 (445 points)
    The problem is simple, when you do [Ball init] you don't instantiate Ball, but you send the message -init to a class whereas it's an instance method... Why does it work ? Well -init is defined in the root class so the Class object can execute it too... However it just returns self and does nothing else.
    So here, what you do, is just retaining the Class object, that is the object that represents your Ball class.

    What you want to do is to instantiate that class. You need, to do so, to use the factory methods. I think you misunderstood what +arrayWithCapacity: was doing. That method returns an allocated initialized and autoreleased objects. -init just returns an initialized objects, it doesn't allocate anything.
    So before sending the init message, you have to send the alloc message to the class object to get an allocated instance that you'll then initialize using -init message, it gives you that :


    Ball *ball = [[Ball alloc] init]; // no retain message


    ball will contain a reference to a newly allocated and initialized instance of Ball class. And that instance will accept the -draw message, you won't get that exception.

    What is important here is memory management. When you own an object you must release the object. There are 3 ways to be responsible of an object ::

    // 1. With the alloc/init technique :
    MyClass *obj = [[MyClass alloc] init];
    // Which can be done using +new message which just make alloc/init in the same message :
    MyClass *obj = [MyClass new]; // that technique is not recommanded

    // 2. With the copy message :
    obj = [anotherObj copy];

    // 3. With the retain message :
    obj = [anotherObj retain];


    Each use of one of this technique must be balanced by a release or an autorelease message :

    // 1. The object is deallocated immediately
    [obj release];

    // 2. The object is deallocated "later" (in AppKit apps lik your,
    // it's deallocated at the end of an event loop)
    [obj autorelease];


    In any other case, you're not responsible of releasing objects, which means that an object you get with another techniques than the 3 up there will be deallocated soon, that's why you do a -retain on your mutable array and that's why you do not have to do a retain on the ball objects for which you use alloc/init messages.

    By the way, an array (mutable or not) retains the objects that are being added to it, so if you don't need the reference outside of the array you can release it.

    So after adding the object to the array, release it :


    Ball *ball = [[Ball alloc] init];
    [_balls addObject:ball];
    [ball release];


    If you don't do that, you'll probably lose the reference to the ball object and you'll have a memory leak.
    Also, the autorelease pool is useless with your draw loop.


    PS : if you want to format the code beautifully like me, you simply have to use the
     markups :

    // your formatted code here


    becomes :

    // your formatted code here


    And if you want to avoid the format specifier like the
    or the [] to be transformed into what they mean (code formating, links, etc.) just put an anti-slash '\' before it.
  • Karl Woo Level 1 Level 1 (0 points)
    Wow, thanks, your explanation cleared up a lot of questions I had about when exactly to retain objects! It now works

    And thanks for the hint about formatting code, I'm sure it'll come in handy