Help me grasp a couple key concepts (Obj-C)

Discussion in 'Mac Programming' started by CaptSaltyJack, Jul 16, 2008.

  1. CaptSaltyJack macrumors 6502

    Joined:
    Jun 28, 2007
    #1
    First, my background: a lot of my recent experience comes from C#.NET. I skipped the whole C++ ship, and was a pretty decent C programmer back in the day.

    I'm trying to make sense of the whole retain/release/autorelease thing in Obj-C. I just don't get it. My C is kind of rusty, but it might be helpful to talk about retain/release/autorelease in terms of C.

    In C, you can create a string like this:

    Code:
    char *hello = "Hello World";
    which I rarely used, but I THINK that syntax does a malloc(sizeof(char) * strlen("Hello World")) and points hello to that memory location, right?

    Or you can do it like this:

    Code:
    char *hello;
    hello = (char *)malloc(sizeof(char) * 512);
    hello = "This can hold up to 512 bytes";
    
    And of course, later, you want to do free(hello) if you used malloc().

    That's about as much as I understand with memory allocation in C. Now, Obj-C makes it more complex, I think. For instance, what the heck does init do? I mean what is it REALLY doing behind the scenes? But that's another topic I suppose. Here are my real questions about retain/release:

    1) How the heck do I know when something needs to be released? In other words, when does something returned by a statement actually have a retain count of at least 1? Take the below code snippet:

    Code:
    NSString *a = @"Hi there";
    NSString *b = [NSString stringWithString:@"Hi there"];
    NSString *c = [NSString new]; //I know that new = alloc + init
    NSString *d; [d initWithString:@"Hi there"]; //I don't have a compiler handy now, this might be invalid
    
    Right after those statements, what is the retainCount of a, b, c, and d? Or maybe a better question is, how do I know which one of those needs to be released manually? And do I need to just do [x release] or [x release]; x = nil; ?

    I guess I'll stop here before getting into autorelease..one thing at a time. :) One quick question (unrelated), about delegates: they're kind of like events in C# and JavaScript, right? onMouseOver, onMouseOut, onChange, etc. Once I assign an object's delegate to another object, then I can access those special event functions..correct?
     
  2. HiRez macrumors 603

    HiRez

    Joined:
    Jan 6, 2004
    Location:
    Western US
    #2
    From the Cocoa Fundamentals Guide:

    So far the discussion of the object life cycle has focused on the mechanics of managing objects through that cycle. But a policy of object ownership guides the use of these mechanisms. This policy can be summarized as follows:

    -- If you create an object by allocating and initializing it (for example, [[MyClass alloc] init]), you own the object and are responsible for releasing it. This rule also applies if you use the NSObject convenience method new.
    -- If you copy an object, you own the copied object and are responsible for releasing it.
    -- If you retain an object, you have partial ownership of the object and must release it when you no longer need it.

    Conversely,

    -- If you receive an object from some other object, you do not own the object and should not release it. (There are a handful of exceptions to this rule, which are explicitly noted in the reference documentation.)

    As with any set of rules, there are exceptions and “gotchas”:

    -- If you create an object using a class factory method (such as the NSMutableArray arrayWithCapacity: method), assume that the object you receive has been autoreleased. You should not release the object yourself and should retain it if you want to keep it around.
    -- To avoid cyclic references, a child object should never retain its parent. (A parent is the creator of the child or is an object holding the child as instance variable.)


    a and b are autoreleased (a is a string constant so it's kind of a special case, but consider it autoreleased). b is a convenience method and is handing you an autoreleased string with a retain count of 1. You don't need to release these if you use them inside their current scope. However, if you want it to survive beyond the current scope, you need to take ownership of it by sending it a retain message and assigning it to a variable that will be around outside the current scope. Then, because you retained it, you must release it at some point in the future (the retain from the autorelease will drop off by itself at some time when the autorelease pool is drained, but you can't predict exactly when, so if you retain it yourself consider it having a retain count of 1).

    c would have a retain count of 1 and is not autoreleased. You have full ownership of this object. Therefore, you must release it, even if you're only using it in the current scope. If you don't release it or assign it to a variable outside the current scope, you'll leak that memory (there will be allocated memory with nothing referencing it).

    d won't work. You need to alloc an object before doing an init. In this case you could do:

    Code:
    NSString *d = [[NSString alloc] initWithString:@"Hello there"]; // Retain count of 1, you must manually release
    or

    Code:
    NSString *d = [NSString stringWithString:@"Hello there"]; // convenience method, autoreleased
    *** Be very careful about debugging using NSObject's retainCount: method. Objective-C does a bunch of internal optimizations and this is not always accurate. In many cases it will not give you the value you expect.
     
  3. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #3
    The string literal "Hello World" has an absolute position in memory. Your char * called hello is assigned the pointer to that absolute position. No dynamic memory allocation is performed for this, so you never need to free this pointer.

    This is a leak. You declare an initialized char *, hello. You then malloc enough space for 512 chars, and assign the pointer cast to a char * to your char * called hello. sizeof(char) can normally be assumed to be 1 byte, but you used sizeof rather than an absolute number of bytes (which is the right thing to do), but I wanted to be clear that you have room to store 512 chars in the memory now pointed to by hello. You then assign the pointer to a string literal "This can hold up to 512 bytes" to your char * hello. You no longer have a pointer to the memory allocated with malloc, so you will never be able to free it. When you free hello, which now points to a string literal, something bad (and system dependent) will happen. Since you are not passing in a pointer that was allocated with calloc/malloc/realloc, etc., it might do nothing, or might do something terrible. That will cause problems later. If you used that string literal again, the memory it points to might have been reused, etc.

    init is a message passed to an object. What it does is entirely dependent on the class of the object. Alloc allocates enough memory to hold an object, and returns the pointer to the space allocated. You normally see:
    Code:
    myClass *myObj = [[myClass alloc] init];
    Which allocates space for an object of class myClass, then returns the pointer to this space. The object that pointer is pointing to is then passed the message init, which sets up any properties, etc. and again returns the pointer to itself. This pointer is then assigned to the myClass * called myObj.

    The rest of your post has been addressed by those that are better fit to address them than myself, but I thought I'd cover the points that I could.

    -Lee

    Edit: One more thing:
    HiRez stated that this was not correct to do and told you why, but it is syntactically correct and will compile. What you have done is declared an uninitialized NSString *, then tried to pass the message
    Code:
    initWithString:(NSString *)
    to the object that your NSString * called d points to. The behavior here is undefined, but if you're lucky d will have the value of 0, and the message pass will be a no-op, as messages passed to nil are treated as a no-op by the Objective-C runtime. If you are not lucky and d has a garbage value in it, then a message will attempt to be passed to an object at that address. This won't turn out well. Since it is garbage, it COULD point to some other NSString. That is very unlikely, but could happen. In that case that other NSString will have it's value modified. So there would then be some other NSString out there that you have changed that d points to, and is likely pointed to by something else that will now be different from what you expected. In the case that there is not an NSString or some other object that responds to the message
    Code:
    initWithString:(NSString *)
    , you will get an exception thrown.
     
  4. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #4
    OK, so I have a question. First off, thanks for the replies, I'm going to re-read them more carefully. If I opt with garbage collection on OS 10.5, then I don't have to deal with retain/release/autorelease, correct? I can just alloc/init and leave things be? If that's the case, I see why so many recent apps are for OS 10.5 only. :) I never knew why, but I'm willing to bet part of it is because they finally don't have to stress over manual memory management anymore.
     
  5. Denarius macrumors 6502a

    Denarius

    Joined:
    Feb 5, 2008
    Location:
    Gironde, France
    #5
    Fascinating thread this. I've been learning objective-c for the last week or so, getting on alright but conscious that my underlying C knowledge is poor. I was very interested in your remarks on this snippet of code. I understood what you meant about losing track of the char memory space you've allocated when you move the pointer on to the literal string, but I'd be interested to know the right way to put strings into the memory space allocated here.

    Apart from that though, is it fair to say that if you assign straight off i.e.
    char *string="Whatever.";
    then you don't have to worry about it memory-wise, but if you assign a space with malloc, then you need to make sure that you free it later when it's no longer used?
     
  6. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #6
    Oh yeah, woops.

    Code:
    char *hello;
    hello = (char *)malloc(sizeof(char) * 512);
    hello = "This can hold up to 512 bytes";
    Dumb mistake on my part. That should be:

    Code:
    char *hello;
    hello = (char *)malloc(sizeof(char) * 512);
    strcpy(hello, "This can hold up to 512 bytes");
    EDIT: OK, here's some sample code (this is compilable) to illustrate the problem with the above code (the first code snippet).

    Code:
    #include <stdio.h>
    
    int main(int argc, const char *argv[])
    {
      char *good, *bad, *link;
    
      good = (char *)malloc(sizeof(char) * 512);
      printf("Address of 'good' is %#lx\n", good);
      strcpy(good, "I just copied this string");
      printf("Address of 'good' is still %#lx\n", good);
    
      bad = (char *)malloc(sizeof(char) * 512);
      link = bad; // Keep track of this memory space
      printf("Address of 'bad' is %#lx\n", bad);
      bad = "Rut roh!";
      printf("Address of 'bad' has changed to %#lx\n", bad);
    
      free(good);
      free(link); // This is why I kept that memory location!
      return 0;
    }
    
     
  7. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #7
    Garbage collection doesn't mean "woo hoo! Free memory management!", it just means you move from explicitly counting references to letting something else implicitly do it for you. You can still leak references, which will still leak memory.

    This is a contrived example, but let's say you have some sort of "object registration system" that is part of your app, and whenever you create an object of a certain type you need to track it for some reason. You "register" it with your system, and a reference is created to keep it in an Object lookup table. You're done with this object (perhaps you create them in a loop, and they live a very short useful life) and you no longer have references to them. It should be GC'd, but your registration system (you could replace this with some sort of event listener, whatever, that might hold on to object references) still has a reference. The object lives on, happily using up your memory. Once you realize this you have to make sure when you really want an Object gone that you prune it from your registration system. You have to make sure you do it before you lose the reference in your code, because otherwise you're just poring over your Object registry hoping you find which one you're no longer using. Even if you DO still have the reference, are you going to depend on an equalTo: method that compares fields? Or are you going to use == to check for pointer equivalence? If the prior, are you SURE you pruned the right one?

    This is obviously sloppy with or without GC, but the point was that sometimes garbage collection can give you a false sense of security. Accidentally holding on to the root of a very large binary tree full of objects that refer to objects can mean a very big memory leak.

    -Lee
     
  8. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #8
    I figured it was just a slip, but wanted to point it out for completeness sake. I insist on using strncpy or snprintf so you can ensure the size of the copy. If it's from a literal, you're probably in better shape, but I always like to be able to give a size argument to help safeguard against overflows. The result would be:
    Code:
    strncpy(hello,"This can hold up to 512 bytes",511);
    hello[511]='\0'; //If the source was longer than 511 chars, hello is not null terminated
    
    or
    Code:
    snprintf(hello,512,"%s","This can hold up to 512 bytes");
    
    What's nice about the latter is that the argument is the actual number of bytes including the null-terminator. The same can be done with memcpy, but you want to make sure that the number of bytes you copy is both less than or equal to the size of the destination and less than or equal to the size of the source. The business of using %s as the format specifier instead of just using the literal is that rarely are you doing this with literals, and with a string that may be input by a user, using their input as a format string could lead to various attacks if they put format specifiers in the string (extra things get popped off the stack since the printf family has variable argument lengths, and this can lead to very negative repercussions). I guess it's a little sloppy not to cast the size arguments to size_t, but I haven't met a system so far that this causes a problem on.

    -Lee

    P.S. It's rarely used, but in your sample you were printing memory locations with %#lx, and there is actually a pointer format specifier, %p.
     
  9. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #9
    Thanks, Lee, and everyone else.

    I guess the trouble I have is, how do I know when I need to explicitly release an object? It seems like the rules work like this:

    - if I've got an object that's returned from a method, I don't have to release it (e.g. stringWithString). Along similar lines, if I'm writing methods in my own class that return objects, I need to make sure I call autorelease on them before returning them.

    - if I've used alloc, I need to release it, period.

    Anything I'm missing? Also, is there any tool for Mac OS that will tell me if my app has memory leaks?
     
  10. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #10
  11. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #11

    Someone really ought to sticky a thread with the rules of thumb:

    1) The initial retainCount is 0
    2) The methods alloc, allocWithZone:, copy, mutableCopy, new, and retain increase the retainCount by one.
    3) If an object needs to ensure that another object sticks around, it should retain or copy it. For example, adding an object to an NSArray will retain it.
    4) The methods release and autorelease decrease the retainCount by one (although not immediately in the case of autorelease).
    5) If the retainCount for an object reaches 0, dealloc is called. dealloc should call [super dealloc] after cleaning up.


    As for leaks tools, the commandline tools 'heap', 'leaks', 'malloc_history', 'dtrace', and 'dtruss' can be useful, as well as the graphical tools 'Instruments', 'MallocDebug', and 'ObjectAlloc', and the environment variables MallocStackLogging and MallocStackLoggingNoCompact.
     
  12. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #12
    Quick Q about autorelease.

    This is correct:

    Code:
    - (id)myFunc:(int)val
    {
      NSString *ret = [[NSString alloc] initWithFormat:@"%d", val];
      [ret autorelease];
      return ret;
    }
    
    But this is not:

    Code:
    - (id)myFunc:(int)val
    {
      NSString *ret = [NSString stringWithFormat:@"%d", val];
      [ret autorelease];
      return ret;
    }
    
    Right?

    The question is, why doesn't the SDK documentation tell me which methods are convenience methods that I don't have to autorelease?? Sure would've been handy for the stringWithFormat documentation to say that I don't have to call autorelease.. or to tell me I DO need to autorelease for initWithString.
     
  13. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #13
    <edit> This is not true anymore due to an edit in the post it's replying to </edit>

    Both of those are correct. Basically identical, in fact.

    One habit I've been getting myself into lately is putting the autorelease in the same block as the alloc, so I can't accidentally introduce leaks with an early return.
     
  14. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #14
    Sorry man, late night. I edited my post to add the autorelease to the second code snippet. Now it should be incorrect :)
     
  15. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #15
    Aha. Right you are :)
     
  16. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #16
    See, now here's a slightly confusing thing on retain/release.

    I found some sample iPhone code on Apple's site. A grey button is created with this code:

    Code:
    - (void)createGrayButton
    {	
    	// create the UIButtons with various background images
    	// white button:
    	UIImage *buttonBackground = [UIImage imageNamed:@"whiteButton.png"];
    	UIImage *buttonBackgroundPressed = [UIImage imageNamed:@"blueButton.png"];
    	
    	CGRect frame = CGRectMake(0.0, 0.0, kStdButtonWidth, kStdButtonHeight);
    	
    	grayButton = [ButtonsViewController buttonWithTitle:@"Gray"
    								target:self
    								selector:@selector(action:)
    								frame:frame
    								image:buttonBackground
    								imagePressed:buttonBackgroundPressed
    								darkTextColor:YES];
    }
    Then is later released:

    Code:
    - (void)dealloc
    {
    	[myTableView setDelegate:nil];
    	[myTableView release];
    	
    	[grayButton release];
    	[imageButton release];
    	[roundedButtonType release];
    	
    	[detailDisclosureButtonType release];
    	[infoLightButtonType release];
    	[infoDarkButtonType release];
    	[contactAddButtonType release];
    	
    	[super dealloc];
    }
    
    OK, so I found that the buttonWithTitle method does an alloc and returns the new button. Why wouldn't that method do an autorelease before it returns the new button? Would've saved the developer some effort, no?
     
  17. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #17
    That looks like a bug in their sample code to me.
     
  18. CaptSaltyJack thread starter macrumors 6502

    Joined:
    Jun 28, 2007
    #18
    The code compiles fine. And like I said, their button creation function is doing an alloc, but not doing an autorelease. So it seems they're just choosing to manually release later. I think, anyway..
     
  19. gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #19
    When you call "autorelease", the current autorelease pool keeps track that the object needs to be released, and the actual releasing will happen when the autorelease pool itself is released. Usually an autorelease pool is created and released every time the run loop handles an event, so autoreleased items don't last very long. Anything you want to stay around longer shouldn't be autoreleased.
     

Share This Page