tags:

views:

59

answers:

2

Hello,

First, I am new on this website, thus thank you in advance for your help.

My iPhone app has a class whose main role is to encapsulate the way I treat data on a bunch of raw bytes coming from a web server. Each time I need to display a defined type of information from that data (e.g. a piece of advice contained within these raw bytes) I call the method getAdviceFromGame. This method builds a displayable NSString by calling a NSString class method at the end (the object sent back by the stringWithUTF8String class method is autoreleased, according to method naming rules - no init, no alloc in the name). Please note that I did not put "New" in my method name because the caller does not own the object sent back by the method.

Here is the method:

-(NSString*) getAdviceFromGame:(NSInteger)number
                    ofLine:(NSInteger)line {
// Returns the advice at line specified as argument.
NSInteger currentOffset;
NSRange currentRange;
NSInteger offsetAdvice;
NSInteger length;
char currentCString[100];

if (line == 1)
    offsetAdvice = OFF_ADVICE1;
else
    offsetAdvice = OFF_ADVICE2;

// Length is the same whateve is the line.
length = LEN_ADVICE1;

// Point to the begnning of the requested game.
currentOffset = OFF_G1_SET + (number - 1) * LEN_SET;
// Point to the selected advice.
currentOffset = currentOffset + offsetAdvice;
// Skip TL
currentOffset = currentOffset + 2;

currentRange.location = currentOffset;
currentRange.length = length;

NSLog(@"Avant getBytes");

// Get raw bytes from pGame.
// Contains a C termination byte.
[pGame getBytes:currentCString range:currentRange];

// Turn these raw bytes into an NSString.
// We return an autoreleased string.
return [NSString stringWithUTF8String:currentCString];

}

This method if from my point of view, not critical, from a memory management point of view since I only send back an "autoreleased" object. Note: pGame is an internal class variable of type NSData.

In my app, in order to understand how autoreleased objects behave, I have looped 10000 times this method in the - (void)applicationDidFinishLaunching:(UIApplication *)application function and also 10000 times the same method in - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController. This way, I can invoke big allocations.

During code execution, when the app is launched, I can see with the alloc measurement tool that the allocated object size is growing (from 400K to 800K). Then, when the applicationDidFinishLaunching method ends, then the amount gets down to 400K. Thus I guess that the pool has been "drained" by the operating system (kind of garbage management).

When I click on a tab bar, once again there is a big allocation due to the loop. Also, I can see the size growing (because thousands of NSString are allocated and sent back). When that's done, then the size gets down to 400K.

Thus my first questions are: Q1: Can we precisely know when the autorelease pool will be "drained" or "purged"? Q2: Does this happen at the end of OS/GUI methods such as didSelectViewController? Q3: Must someone who calls getAdviceFromGame retain the object sent back by my method before using it?

Now I have another (more complicated) method where I internally allocate a mutable string on which i am working on before sending back the NSString:

-(NSString*) getBiddingArrayFromGame:(NSInteger)number
                           ofRow:(NSInteger)row
                          ofLine:(NSInteger)line {
NSInteger offset;
char readByte;
NSMutableString *cardSymbol = [[NSMutableString alloc] initWithString:@""];
NSRange range;

// Point to the begnning of the requested game.
offset = OFF_G1_SET + (number - 1) * LEN_SET;

// Returns the array value from cell (row, line)
// We must compute the offset of the line.
// We suppose that the offset cannot be computed, but
// only deduced from line number through a table.
switch (line) {
    case 1:
        offset = offset + OFF_LINE1;
        break;
    case 2:
        offset = offset + OFF_LINE2;
        break;
    case 3:
        offset = offset + OFF_LINE3;
        break;
    case 4:
        offset = offset + OFF_LINE4;
        break;
    default:
        // This case should not happen but for robustness
        // we associate any extra value with a valid offset.
        offset = OFF_LINE4;
        break;
}

// Skip TL bytes
offset = offset + 2;

// From the offset and from the row value, we can deduce
// the offset in the selected line.
offset = offset + (row - 1);

// Now, we must read the byte and build a string from
// the byte value.
range.location = offset;
range.length = 1;
[pGame getBytes:&readByte range:range];

// We must extract the family type.
// If the family if of type "Special" then we must build by
// hand the value to display. Else, we must build a string
// with the colour symbol and associated character by reading
// in the card character table.
switch (readByte & CARD_FAMILY_MASK) {
    case COLOUR_CLUBS:
        // "Trèfles" in French.
        [cardSymbol appendString:CLUBS_UTF16];
        break;
    case COLOUR_DIAMONDS:
        [cardSymbol appendString:DIAMONDS_UTF16];
        break;
    case COLOUR_HEARTS:
        [cardSymbol appendString:HEARTS_UTF16];
        break;
    case COLOUR_SPADES:
        [cardSymbol appendString:SPADES_UTF16];
        break;
    case COLOUR_SPECIAL:
        break;
    case COLOUR_ASSET:
    default:
        break;
}

[cardSymbol autorelease];

// Return the string.
return [NSString stringWithString:cardSymbol];

}

As you can see, this is not very complicated but more critical from a memory management point of view since I "internally" alloc and init an NSString. Since I use it at the end of the method, I can only autorelease it before calling stringWithString:cardSymbol (in fact I would like to release it so that it is deallocated right now) else it could be deallocated before stringWithString:cardSymbol method. Well I am not satisfied with the way to do this but maybe it is the right way to do it.

Thus my last question is: Is that the right way to do it?

I am afraid that the autorelease pool be purged before reaching stringWithString:cardSymbol.

Best Regards, Franz

A: 

Q1: Can we precisely know when the autorelease pool will be "drained" or "purged"?

Q2: Does this happen at the end of OS/GUI methods such as didSelectViewController?

Yes, the autorelease pool is drained once per the event loop.

Q3: Must someone who calls getAdviceFromGame retain the object sent back by my method before using it?

If that someone wants to keep the object after that particular event cycle, yes. If not, you don't have to, because the autoreleased object is guaranteed to be alive until your current event-dealing method returns.

Please note that I did not put "New" in my method name because the caller does not own the object sent back by the method.

Very good! But I also recommend you to change the method name from getAdviceFromGame:ofLine to adviceFromGame:ofLine. Usually in Cocoa, get... is used when something is returned by a pointer passed as a method argument, just when you used [pGame getBytes:&readByte range:range];.

As for the second part, you can use instead of your lines

[cardSymbol autorelease];

// Return the string.
return [NSString stringWithString:cardSymbol];

the sequence

NSString*s=[SString stringWithString:cardSymbol];
[cardSymbol release];
return s;

or even just

return [cardSymbol autorelease];

because NSMutableString is a subclass of NSString. But just one object in the autorelease pool won't matter much, even on the iPhone.

Yuji
@Yuji:Thanks for your advice about the method naming ("get" or not "get", I did not know the difference).After posting the question, I got the same idea in order to release cardSymbol inside the method, by using an alternate NSString pointer whose data pointed by it can be released before the "return".NSString*s=[SString stringWithString:cardSymbol];[cardSymbol release];return s;return cardSymbol; : Indeed, I did not know I could return a subclass of the type to be returned.
Thanks for your reply. I noticed that I forgot `autorelease` in the last example, which I corrected now. Stupid me.
Yuji
A: 

First, you don't have a class method anywhere in that code, but your question title says you do have a class method. Thus, I'd suggest re-reading the Objective-C guide again (seriously -- I read that thing about once every six months for the first 5 years I programmed Objective-C and learned something every time).

Thus my first questions are: Q1: Can we precisely know when the autorelease pool will be "drained" or "purged"? Q2: Does this happen at the end of OS/GUI methods such as didSelectViewController? Q3: Must someone who calls getAdviceFromGame retain the object sent back by my method before using it?

There is no magic with autorelease pools. The documentation is quite clear on how they work.

For a UIKit based application, there is an autorelease pool managed by the run loop. Every pass through the runloop causes the release pool to be drained. If you are allocating tons of temporary objects in a single pass through the runloop, then you might need to create and drain your own autorelease pool (nothing about your code indicates that is what you need to do).

As you can see, this is not very complicated but more critical from a memory management point of view since I "internally" alloc and init an NSString. Since I use it at the end of the method, I can only autorelease it before calling stringWithString:cardSymbol (in fact I would like to release it so that it is deallocated right now) else it could be deallocated before stringWithString:cardSymbol method. Well I am not satisfied with the way to do this but maybe it is the right way to do it.

Unless you explicitly create and drain an autorelease pool, your string isn't going to disappear out from under you. There is nothing automatic about the draining of pools.

There is no reason to create an immutable copy of a mutable string. Just return the mutable string. If you really really really want to create an immutable copy, then do as Yuji recommended.

bbum
@bbum:Thanks for the explanation and advice.Sorry for the lack of accuracy in my question title. In fact, the title is only a part of the question. Indeed, my method uses a class method (at the end, when returning): return [NSString stringWithUTF8String:currentCString]; I focused on "Class method" since this method is class method which autoreleases the object it sends back.I will try to be more accurate in my next questions title.
No worries -- I now see why your emphasis was as it was.
bbum