Objective-c pendulum modelling memory issues - objective-c

I am trying to implement a modelling class for a Physics project with finite difference methods for simulating a simple pendulum. I want to be able to make this class as generic as possible so I can do whatever I want with the values on each iteration of the method. For this reason I have given my methods callback blocks which can also be used to stop the method if we want to.
For example my Euler method loop looks like so:
for (NSInteger i = 0; i < n; i++) {
if (callBack) {
if(!callBack(NO, currentTheta, currentThetaDot, currentT, (CGFloat)i/n)) break;
}
currentTheta += self.dt*f_single_theta(currentThetaDot);
currentThetaDot += self.dt*f_single_thetaDot(currentTheta, currentThetaDot, gamma);
currentT += self.dt;
}
And in the callBack block I run the code
^BOOL (BOOL complete, double theta, double thetaDot, CGFloat timeElapsed, CGFloat percentComplete){
eulerT = [eulerT stringByAppendingFormat:#"%.8f\n",timeElapsed];
eulerTheta = [eulerTheta stringByAppendingFormat:#"%.8f\n",theta];
if ((currentThetaDot*currentThetaDot + cos(currentTheta)) > 0.5) {
return 0; // stops running if total E > 0.5
}
return 1;
}];
Where eulerT and eulerTheta are strings which I later save to a file. This callback method quickly results in a massive build up of memory, even for n of order 10,000 I end up with about 1Gb of RAM usage. As soon as I comment out calling the callBack block this drops right off. Is there anyway I can keep this nice functionality without the massive memory problems?

Many people who are new to Objective C do not realize the difference between [NSArray array] and [[NSArray alloc] init]. In the days before ARC, the difference was much more obvious now. Both create a new object, but the former allocates the object, assigns it to the current NSAutoreleasePool, and leaves it with a retain count of 0 while the latter allocates it and leaves it with a retain count of 1.
Objects that are assigned to an NSAutoreleasePool do not get deallocated immediately when the retain count reaches 0. Instead, they get deallocated when the OS gets time to. Generally this can be assumed to be when control returns to the current run loop, but it can also be when drain is called on the NSAutoreleasePool.
With ARC, the difference is less obvious, but still significant. Many, if not most, of the objects your allocate are assigned to an autorelease pool. This means that you don't get them back just because you're done using them. That leads to the memory usage spiking in tight loops, such as what you posted. The solution is to explicitly drain your autorelease pool, like this:
for (NSInteger i = 0; i < n; i++) {
if (callBack) {
#autoreleasepool {
if(!callBack(NO, currentTheta, currentThetaDot, currentT, (CGFloat)i/n))
break;
}
}
currentTheta += self.dt*f_single_theta(currentThetaDot);
currentThetaDot += self.dt*f_single_thetaDot(currentTheta, currentThetaDot, gamma);
currentT += self.dt;
}

You should wrap the inside of your loop in #autoreleasepool{} to clean up temporary objects.

Related

Objective-C Reusing NSString Memory Leak

I have written a very simple test application to try to help with a larger project I'm working on.
Simply put, the test app loops a predetermined number of times and appends "1" to a string on each loop. When the loop hits a multiple of 1000, the string is reset and the process starts over again.
The code is below; but what I am finding is that the memory usage is much higher than I would expect. Each iteration adds about .5MB.
It appears that the newString is not reused, but is discarded and a new instance created, without recovering the memory it was using.
Ultimately, the software needs to count much much higher than 100000.
As a test, if I change the iteration to 10million, it takes over 5GB memory!
Has anybody got any suggestions? So far I have various ways of writing the clearing of the string and turning off ARC and releasing it/recreating manually, but none seem to be reclaiming the amount of memory I would expect.
Thank you for any help!
*ps. Yes this actual software is totally pointless, but as I say, its a test app that will be migrated into a useful piece of code once fixed.
int targetCount = 100000;
NSString * newString;
int main(int argc, const char * argv[]) {
#autoreleasepool {
process();
return 0;
}
}
void process() {
for (int i=0; i<targetCount; i++) {
calledFunction(i);
}
}
void calledFunction(count) {
if ((count % 1000) == 0) {
newString = nil;
newString = #"";
} else {
newString = [NSString stringWithFormat:#"%#1", newString];
}
}
Your calledFunction function creates an autoreleased NSString that won't be released until the current autorelease pool gets drained.
Your process function calls the calledFunction 100,000 times in a loop. During the duration of this loop, the current autorelease pool is not given a chance to drain. By the time the end of the process method is reached, all 100,000 instances of the NSString objects created in calledFunction are still in memory.
A common solution to avoid a build-up of autoreleased objects when using a large loop is to add an additional autorelease pool as shown below:
void process() {
for (int i=0; i<targetCount; i++) {
#autoreleasepool {
calledFunction(i);
}
}
}
Your problem stems from the auto release pool, a somewhat anachronistic feature in the days of ARC.
When an object is created with an alloc/init combination the resultant object is owned by the caller. The same is true for the standard new method, it is defined as alloc followed by init.
For each init... method a class by have a matching <type>... method, this is defined as alloc/init/autorelease and returns an unowned object to the caller. For example your code uses stringWithFormat: which is defined as alloc/initWithFormat/autorelease.
The unowned returned object is in the auto release pool and unless ownership is taken it will be release automatically the next time that pool is emptied, which for the main autorelease pool is once per iteration of the main event loop. In many programs iterations of the event loop are frequent enough to reclaim objects from the auto release pool quick enough that memory usage does not climb. However if objects are created and then discarded a lot with a single iteration of the event loop, as in your example of a large for loop, the auto release pool can end up with a lot of needed objects before it is emptied.
A Common Solution
A common solution to this problem is to use a local auto release pool, which is emptied as soon as it is exited. By judicious placement of such local auto release pools memory use can be minimised. A common place for them is wrapping the body of loops which generate a lot of garbage, in your code this would be:
void process()
{
for (int i=0; i<targetCount; i++)
{ #autoreleasepool
{
calledFunction(i);
}
}
}
here all auto released and discarded objects created by calledFunction() will be reclaimed on every iteration of the loop.
A disadvantage of this approach is determining the best placement of the #autoreleasepool constructs. Surely in these days of automatic referencing count (ARC) this process can be simplified? Of course...
Another Solution: Avoid The Auto Release Pool
The problem you face is objects ending up in the auto release pool for too long, a simple solution to this is to never put the objects in the pool in the first place.
Objective-C has a third object creation pattern new..., it is similar to the <type>... but without the autorelease. Originating from the days of manual memory management and heavy auto release pool use most classes only implement new - which is just alloc/init - and no other members of the family, but you can easily add them with a category. Here is newWithFormat:
#interface NSString (ARC)
+ (instancetype)newWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
#end
#implementation NSString (ARC)
+ (instancetype)newWithFormat:(NSString *)format, ...
{
va_list args;
va_start(args, format);
id result = [[self alloc] initWithFormat:format arguments:args];
va_end(args);
return result;
}
#end
(Due to the variable arguments this is more involved than most new family methods would be.)
Add the above to your application and then replace calls to stringWithFormat with newWithFormat and the returned strings will be owned by the caller, ARC will manage them, they won't fill up the auto release pool - they will never enter it, and you won't need to figure out where to place #autoreleasepool constructs. Win, win, win :-)
HTH

How can doing tasks in multiple threads be 100 times slower than doing sequentially on the main thread?

I have this other question of mine where I have asked about converting a code from sequential to parallel processing using Grand Central Dispatch.
I will copy the question text to makes things easy...
I have an array of NSNumbers that have to pass thru 20 tests. If one test fails than the array is invalid if all tests pass than the array is valid. I am trying to do it in a way that as soon as the first failure happens it stops doing the remaining tests. If a failure happens on the 3rd test then stop evaluating other tests.
Every individual test returns YES when it fails and NO when it is ok.
I am trying to convert the code I have that is serial processing, to parallel processing with grand central dispatch, but I cannot wrap my head around it.
This is what I have.
First the definition of the tests to be done. This array is used to run the tests.
#define TESTS #[ \
#"averageNotOK:", \
#"numbersOverRange:", \
#"numbersUnderRange:",\
#"numbersForbidden:", \
// ... etc etc
#"numbersNotOnCurve:"]
- (BOOL) numbersPassedAllTests:(NSArray *)numbers {
NSInteger count = [TESTS count];
for (int i=0; i<count; i++) {
NSString *aMethodName = TESTS[i];
SEL selector = NSSelectorFromString(aMethodName);
BOOL failed = NO;
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation setArgument:&numbers atIndex:2];
[invocation invoke];
[invocation getReturnValue:&failed];
if (failed) {
return NO;
}
}
return YES;
}
This work perfectly but performs the tests sequentially.
After working on the code with the help of an user, I got this code using grand central dispatch:
- (BOOL) numbersPassedAllTests:(NSArray *)numbers {
volatile __block int32_t hasFailed = 0;
NSInteger count = [TESTS count];
__block NSArray *numb = [[NSArray alloc] initWithArray:numbers];
dispatch_apply(
count,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^(size_t index)
{
// do no computation if somebody else already failed
if(hasFailed) {
return;
}
SEL selector = NSSelectorFromString(TESTS[index]);
BOOL failed = NO;
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation setArgument:&numb atIndex:2];
[invocation invoke];
[invocation getReturnValue:&failed];
if(failed)
OSAtomicIncrement32(&hasFailed);
});
return !hasFailed;
}
Activity Monitor shows what appears to be the cores being used with more intensity but this code is at least 100 times slower than the older one working sequentially!
How can that be?
If your methods that you're calling are simple, the overhead of creating all of these threads could offset any advantage gained by concurrency. As the Performing Loop Iterations Concurrently section of the Concurrency Programming Guide says:
You should make sure that your task code does a reasonable amount of work through each iteration. As with any block or function you dispatch to a queue, there is overhead to scheduling that code for execution. If each iteration of your loop performs only a small amount of work, the overhead of scheduling the code may outweigh the performance benefits you might achieve from dispatching it to a queue. If you find this is true during your testing, you can use striding to increase the amount of work performed during each loop iteration. With striding, you group together multiple iterations of your original loop into a single block and reduce the iteration count proportionately. For example, if you perform 100 iterations initially but decide to use a stride of 4, you now perform 4 loop iterations from each block and your iteration count is 25. For an example of how to implement striding, see “Improving on Loop Code.”
That link to Improving on Loop Code walks through a sample implementation of striding, whereby you balance the number of threads with the amount of work done by each. It will take some experimentation to find the right balance with your methods, so play around with different striding values until you achieve the best performance.
In my experiments with a CPU-bound process, I found that I achieved a huge gain when doing two threads, but it diminished after that point. It may vary based upon what is in your methods that you're calling.
By the way, what are these methods that you're calling doing? If you're doing anything that requires the main thread (e.g. UI updates), that will also skew the results. For the sake of comparison, I'd suggest you take your serial example and dispatch that to a background queue (as a single task), and see what sort of performance you get that way. This way you can differentiate between main vs. background queue related issues, and the too-many-threads overhead issue I discuss above.
Parallel computing only makes sense if you have enough tasks for each node to do. Otherwise, the extra overhead of setting up/managing the parallel nodes takes up more time than the problem itself.
Example of bad parallelization:
void function(){
for(int i = 0; i < 1000000; ++i){
for(int j = 0; j < 1000000; ++j){
ParallelAction{ //Turns the following code into a thread to be done concurrently.
print(i + ", " + j)
}
}
}
Problem: every print() statement has to be turned into a thread, where a worker node has to initialize, acquire the thread, finish, and find a new thread.
Essentially, you've got 1 000 000 * 1 000 000 threads waiting for a node to work on them.
How to make the above better:
void function(){
for(int i = 0; i < 1000000; ++i){
ParallelAction{ //Turns the following code into a thread to be done concurrently.
for(int j = 0; j < 1000000; ++j){
print(i + ", " + j)
}
}
}
This way, every node can start up, do a sizeable amount of work (print 1 000 000 things), finish up, and find a new job.
http://en.wikipedia.org/wiki/Granularity
The above link talks about granularity, the amount breaking up of a problem that you do.

Is this a memory leak (using NSArray and NSCopying)

I tried something out in my code to see the effect on memory utilization. I wanted to find out if the line inside the loop was leaking. Running this loop took utilization up to 100MB and it didn't go back down again. Does this indicate a memory leak? If so why? (I'm using ARC)
for (i = 0; i < 10000000; i++)
{
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
(accounts is an array of AccountSummary objects which implements NSCopying like this: name city state phone are all NSStrings, isLocal is BOOL)
- (id)copyWithZone:(NSZone *)zone {
AccountSummary *newAccount = [[AccountSummary allocWithZone:zone] init];
newAccount.name = [self.name copyWithZone:zone];
newAccount.city = [self.city copyWithZone:zone];
newAccount.state = [self.state copyWithZone:zone];
newAccount.phone = [self.phone copyWithZone:zone];
newAccount.isLocal = self.isLocal;
return newAccount;
}
There's no leak here that I can see. There will, however, be quite a bit of peak memory usage.
The exact behaviour of things that should logically release memory varies. Quite often, instead of releasing the memory it's autorelased. (With MRR, there used to be a method called autorelease.) When you autorelease something, it isn't really released but is instead scheduled for release later, when your code is finished because it's returned to the main event loop.
If part of this is being autoreleased — and my guess is that the property assignment is autoreleasing, because autorelease is "safer" than hard releasing — that memory won't be deallocated until your next autoreleasepool flush. Code on the main thread has an autoreleasepool set up by the OS itself, so each time you return to the main event loop everything that's been autoreleased gets flushed out. Here, that probably means that all 10,000,000 copies are kept in memory until you return to the main event loop. Darn right that'll crash a real device. :)
(That's assuming you're on the main thread; if you're not, you may not even have an autorelasepool set up, which means you probably will get a leak. But I think you get warnings to console in this case, so you'd already have a hint about which way to go.)
You can reduce this peak memory usage by using #autoreleasepool:
for (i = 0; i < 10000000; i++) #autoreleasepool {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
What will happen now is that the memory scheduled for release later in each iteration of the loop will actually be released each iteration of the loop. That should solve your immediate problem.
That said, i can't imagine why you're doing this except to check the behaviour. And if that's the case, this is unlikely your core problem.
Assuming your core problem is a leak, with ARC you're not really looking for leaks. You're looking for circular references. That means your answer likely lies elsewhere in your code. If you're sure it's self's accounts rather than dsl's accounts that are leaking, look for self being involved in a circular loop.
Also, keep in mind that calling copyWithZone: on a NSString will probably not copy the string. (There's no need to copy a read-only string, as the read-only string can't be changed. Both "copies" can be the same object.) So if you're leaking just strings, they could be associated with the original objects.
When creating lots of objects inside a loop, you should do that inside an auto release pool.
#autoreleasepool {
for (i = 0; i < 10000000; i++) {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
}
or, more likely in the real world...
for (i = 0; i < 10000000; i++) {
#autoreleasepool {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
}

Is it more efficient to schedule a method to spawn enemies or use the update method of an Enemy cache?

I am using Cocos2d for iPhone and I am wondering if it is more efficient to structure the logic of my code to spawn enemies using this method:
-(void) schedule:(SEL)selector interval:(ccTime)interval
or using the update in an EnemyCache class and verify each time if the time interval is met. Here is the code snippet that is called in the update method of the EnemyCache class (the relative time is an integer value that is updated by the GameScene at each update in the GameScene class - the GameScene update method call is scheduled with an interval of 1 second):
-(void) checkForPlayerCollisionsAndSpwanTime
{
int count = [elements count];
//CCLOG(#"count %i", count);
Element* element;
for(int i=0; i<count;i++){
element = [elements objectAtIndex:i];
NSAssert(element!=nil, #"Nil enemy");
if (element.visible)
{
[element justComeDown];
ShipEntity * ship = [[GameScene sharedGameScene]defaultShip];
CGRect rect = [ship boundingBox];
if (CGRectIntersectsRect([element boundingBox], rect)){
[element doWhatever];
element.visible=FALSE;
[element stopAllActions];
}
}
else{
if(element.spawnTime == relativeTime) {
[self addChild:element];
element.visible=TRUE;
}
}
}
}
The difference is that in this way at each update the checkForPlayerCollisionsAndSpwanTime method goes through the array of enemies. In the first way, via scheduling a selector to call a similar method, I could reduce the time spent by the CPU to look through the array and conditions.
I am not sure how costly is this call:
[self schedule:selector interval:interval repeat:kCCRepeatForever delay:0];
Looking through I see that calls this method (See below) but I wanted to ask in general what is your approach for this problem and whether I should keep using the EnemyCache update method or use the scheduleSelector methods.
-(void) scheduleSelector:(SEL)selector forTarget:(id)target interval:(ccTime)interval paused:(BOOL)paused repeat:(uint) repeat delay:(ccTime) delay
{
NSAssert( selector != nil, #"Argument selector must be non-nil");
NSAssert( target != nil, #"Argument target must be non-nil");
tHashSelectorEntry *element = NULL;
HASH_FIND_INT(hashForSelectors, &target, element);
if( ! element ) {
element = calloc( sizeof( *element ), 1 );
element->target = [target retain];
HASH_ADD_INT( hashForSelectors, target, element );
// Is this the 1st element ? Then set the pause level to all the selectors of this target
element->paused = paused;
} else
NSAssert( element->paused == paused, #"CCScheduler. Trying to schedule a selector with a pause value different than the target");
if( element->timers == nil )
element->timers = ccArrayNew(10);
else
{
for( unsigned int i=0; i< element->timers->num; i++ ) {
CCTimer *timer = element->timers->arr[i];
if( selector == timer->selector ) {
CCLOG(#"CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->interval, interval);
timer->interval = interval;
return;
}
}
ccArrayEnsureExtraCapacity(element->timers, 1);
}
CCTimer *timer = [[CCTimer alloc] initWithTarget:target selector:selector interval:interval repeat:repeat delay:delay];
ccArrayAppendObject(element->timers, timer);
[timer release];
}
Do you have a performance problem in your app? If not, the answer is: it doesn't matter. If you do, did you measure it and did the issue come from the method in question? If not, the answer is: you're looking in the wrong place.
In other words: premature optimization is the root of all evil.
If you still want to know, there's just one way to find out: measure both variants of the code and pick the one that's faster. If the speed difference is minimal (which I suspect it will be), favor the version that's easier for you to work with. There's a different kind of performance you should consider: you, as a human being, reading, understanding, changing code. Code readability and maintainability is way more important than performance in almost all situations.
No one can (or will) look at this amount of code and conclude "Yes, A is definitely about 30-40% faster, use A". If you are concerned about the speed of the method, don't let anyone tell you which is faster. Measure it. It's the only way you can be sure.
The reason is this: programmer's are notorious about making assumptions about code performance. Many times they're wrong, because the language or hardware or understanding of the topic have made big leaps the last time they measured it. But more likely they're going to remember what they've learned because once they've asked a question just like yours, and someone else gave them an answer which they accepted as fact from then on.
But coming back to your specific example: it really doesn't matter. You're much, much, much, much, much more likely to run into performance issues due to rendering too many enemies than the code that determines when to spawn one. And then it really, really, really, really, really doesn't matter if that code is run in a scheduled selector or a scheduled update method that increases a counter every frame. This boils down to being a subjective coding style preference issue a lot more than it is a decision about performance.

Class method: is that the right way to avoid memory leakage?

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
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.
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.