views:

295

answers:

3

I have the following method which is spawned by a call for a new thread (using NSThread):

- (void) updateFMLs {   
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSArray *temp = [[NSArray alloc] initWithArray:someArrayFromAnotherProcess];

    [self performSelectorOnMainThread:@selector(doneLoading:) withObject:temp waitUntilDone:NO];
    [pool release];
}

My doneLoading: method looks like this:

- (void) doneLoading:(NSArray *)obj {
    myArray = [[NSArray alloc] initWithArray:obj copyItems:NO];
}

The contents of myArray become invalid. How can I preserve the contents of myArray so I can use them later in my app?

P.S. myArray is defined in the class header file.

A: 

The code you posted looks fine, apart from the memory leak in updateFMLs. You're probably over-releasing the objects somewhere else. I'm guessing it would be wherever someArrayFromAnotherProcess is made.

Tom Dalling
When I debug updateFMLs - temp is filled with the correct data. When it gets passed to doneLoading and then becomes invalid is what is messing me up....
zPesk
They might be deallocated when the pool is released in updateFMLs, which will happen before doneLoading is called.
Tom Dalling
That's what I was thinking. How would I fix this???
zPesk
If that's the case, then you have to find where you are releasing it incorrectly, and remove the release. It doesn't appear to be in the code you posted.
Tom Dalling
Tom Dalling: But the objects in the array are retained by the array, which he is leaking (it's not autoreleased). If he fixes the leak, then that will become a problem, but it isn't the problem in the code shown.
Peter Hosey
I figure it's something like [[[MyItem new] autorelease] autorelease] that will deallocate the objects even though the leaked array has a retain on them.
Tom Dalling
But 2 autoreleases will just add it twice to the same pool, and doesn't solve anything. He would have to defer one release to the receiving thread, which breaks standard Objective-C memory management rules, but perhaps justifiably in this case. This just makes me think how much cleaner all of this would be with Blocks and Grand Central Dispatch... I can't wait for the entire developer community to get their paws on Snow Leopard! :-)
Quinn Taylor
performSelectorOnMainThread:withObject:waitUntilDone: will actually retain the argument object until the selector is run.
Tom Dalling
+1  A: 

If your background thread does some work and needs to 'pass' an NSArray to your main thread, then all doneLoading needs to do is:

-(void)doneLoading:(NSArray *)obj
{
    [myArray release]; // release the previous array and its objects
    myArray = [obj retain];
    // now use myArray, refresh tables, etc.
}

There's (likely) no need to make another copy of the array, and that might be the underlying issue. You should also call [temp release] after your performSelector call, since arguments to that are retained already.

If the contents of myArray are becoming valid somehow, then they are being doubly released somewhere. myArray will retain any objects that are added to it. You mentioned that myArray itself is becoming invalid, so try rewriting your background thread and your doneLoading method with this pattern.

Finally you should use [pool drain] in place of [pool release].

Jason
What happens if the other thread goes to use `myArray` in between your `release` message and assignment of the new array?
Peter Hosey
Presumably (based on the question) he is using `myArray` on the main thread only. It appears that his background thread sets up a new model and then needs to pass it to the main thread for processing. His question is more about memory management than thread synchronization. To solve the issue you raise, he would need to add @synchronized(self){} blocks. Plus, `updateFMLs` quits immediately after `doneLoading` is to be called.
Jason
A: 

An alternative to the options above would be to declare myArray as an atomic property in the header

@property (atomic,retain) NSArray *myArray;

Then in updateFMLs you should be able to simply call the setter from the secondary thread. Obviously this only works if you are willing to pay the performance penalty for an atomic property.

- (void) updateFMLs {
    NSAutoreleasePool *pool - [[NSAutoreleasePool alloc] init];
    NSArray *temp = [[NSArray alloc] initWithArray:someArrayFromAnotherProcess];
    [self setMyArray:temp];
    [temp release];
    [pool drain];
}
Ira Cooke