Background
I come from a C background and find giving up manual memory management extremely distressing. The old objective c retain and release model was ok if not a little clumsy.
I have written a osX app that reads data from an unspecified data source with an unknown size and makes a visual presentation of the data. In order to stress the app I generated a very large dataset which generates over a hundred million objects in less that 3 minutes (timing is a guess) but when I tare down the view containing the mutable arrays with hundred million objects the app beach balled while deallocating the objects. I solved this problem by passing the array to a background thread which did the release. Anyway I had to redesign The app so it could be sandboxed and I decided to use ARC and now I am back to the same stress test and the beach balling is back. Is there a way I can get a background thread to do the release of the objects created under ARC or do I need to go back a non ARC design and what if I wanted to re write the app in Swift.
Regards Christian Andersen
Is there a way I can get a background thread to do the release of the objects created under ARC
Yes. Just assign nil to the variable on the background thread. But you should ensure that the variable is the only one variable for referring the value.
#import <Foundation/Foundation.h>
#import <pthread.h>
pthread_t g_mainThread;
#interface Test : NSObject
#end
#implementation Test
- (void)dealloc
{
NSLog(#"Test %p was dealloced on %s", self,
g_mainThread == pthread_self() ? "the main queue" : "global queue");
}
#end
// main
int main()
{
g_mainThread = pthread_self();
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 10; ++i) {
Test *test = [[Test alloc] init];
[array addObject:test];
}
__block NSMutableArray *arrayOnGlobalQueue = array;
/*
* The array object was referenced from *array* and *arrayOnGlobalQueue* variables.
* The reference count of the array object is 2.
*/
array = nil;
/*
* The *array* variable doesn't refer the array object any more.
* The reference count of the array object is 1.
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
/*
* Assign nil to the variable for releasing the array object
*/
arrayOnGlobalQueue = nil;
/*
* The *arrayOnGlobalQueue* variable doesn't refer the array object any more.
* The reference count of the array object is 0.
* Thus the array object was dealloced on the background thread(queue).
*/
});
dispatch_main();
return 0;
}
The result is this.
Test 0x7f96aac033b0 was dealloced on global queue
...
But, in common situation, Autorelease Pool refers objects, and Autorelease Pool releases the objects on the main thread.
For instance, if you use NSMutableArray +array class method to instanciate the array object instead of +alloc and -init method,
NSMutableArray *array = [NSMutableArray array];
// NSMutableArray *array = [[NSMutableArray alloc] init];
It means the array object was registered to Autorelease Pool. So, the reference count of the array object is 2. You can avoid the situation using #autoreleasepool.
do I need to go back a non ARC design
Use release under non-ARC instead of assigning null to the variable under ARC.
what if I wanted to re write the app in Swift
Do exactly the same as Objective-C.
Related
I have a QuantumClone class which has an array of CGPoints. The single QuantumPilot object creates a QuantumClone at the beginning of each level. During the next level the QuantumPilot records its velocities to its QuantumClone. At the beginning of a new level the game loop runs this code
QuantumClone *c = [[self.pilot clone] copy];
c.bulletDelegate = self;
c.weapon = self.pilot.weapon;
[self.clones addObject:c];
But eventually the game will be reset and each QuantumClone object in the clones NSMutableArray will be removed.
Am I leaking memory by assigning values to the CGPoint pastVelocities[4551] ?
How do I reset these? I can't release them since they are not Objective-C objects. Do I need to call C functions to release this memory?
#interface QuantumClone : QuantumPilot <NSCopying> {
CGPoint pastVelocities[4551];
}
- (id)copyWithZone:(NSZone *)zone {
QuantumClone *c = [[[QuantumClone alloc] init] autorelease];
c.weapon = self.weapon;
for (NSInteger i = 0; i < 4551; i++) {
[c recordVelocity:pastVelocities[i] firing:pastFireTimings[i]];
}
[c recordLatestIndex:timeIndex];
return c;
}
- (void)recordVelocity:(CGPoint)vel firing:(BOOL)firing {
CGPoint p = pastVelocities[timeIndex];
p.x = vel.x;
p.y = vel.y;
pastVelocities[timeIndex] = p;
bool fired = firing;
pastFireTimings[timeIndex] = fired;
timeIndex++;
}
#interface QuantumPilot : CCNode {}
....
#property (nonatomic, retain) QuantumClone *clone;
- (void)copyDeltas {
[self.clone recordVelocity:ccp(self.vel.x, -self.vel.y) firing:self.firing];
}
- (void)createClone {
self.clone = [[[QuantumClone alloc] init] autorelease];
self.clone.active = YES;
self.clone.weapon = self.weapon;
}
Am I leaking memory by assigning values to the CGPoint pastVelocities[4551] ?
Short answer: No.
Long answer: The array in your code is a big chunk of contiguous memory where all CGRects live in, and it has automatic storage, which means it will be allocated and deallocated automatically (when it goes out of scope). In other words, when its parent object is destroyed, the array will be gone along with those 4551 objects.
You can verify its size by printing the result of sizeof(pastVelocities). Dividing the result by sizeof(CGRect) will tell you how many objects of this type can be stored in it.
A deallocation must be married to an explicit allocation. You only need to release memory that is allocated dynamically (explicitly), for example, using the alloc function family (malloc, calloc, realloc, etc).
How do I reset these?
memset(pastVelocities, 0, sizeof(pastVelocities));
This will reset the entire array.
jweyrich beat me to it, but I'll go ahead and post this in case it helps ;)
––
You aren't leaking. The runtime allocates enough memory to hold all ivars. In this case, each QuantumClone instance will use ~18k (~36k on 64-bit) more memory than a QuantumPilot, since you have told the runtime that it needs to allocate enough ivar storage for 4551 CGPoints.
If pastVelocities were a CGFloat * rather than a CGFloat[4551], you would need to manually allocate the memory via malloc and then call free in -dealloc. However, by declaring it as a fixed size C-array, the runtime handles it (but at the expense of making every QuantumClone a rather huge object).
That said, this whole approach seems fragile. Why 4551? Why C arrays? There is nothing wrong with using C arrays for performance, but I strongly suspect this is premature optimization.
I made simple experiment and found some strange behavior. Here some code - part of long method with ARC enabled:
MKObject *obj = [[MKObject alloc]init];
NSMutableArray *temp = [[NSMutableArray alloc]init];
[temp addObject:obj];
obj = nil;
temp = nil;
//here deallocating is called on obj (MKObject)
//other stuff
but if I change NSMutableArray to NSArray and literal initialisation
NSArray *temp = #[obj];
deallocating executed before autoreleasepool closed, not after setting nil to all references. Did I missed something ?
A few observations:
In your first example, neither the MKObject nor the NSMutableArray is an autorelease object, so these objects will be deallocated immediately, not waiting for the autorelease pool to drain:
MKObject *obj = [[MKObject alloc] init];
NSMutableArray *temp = [[NSMutableArray alloc] init];
[temp addObject:obj];
obj = nil;
temp = nil;
In your second example, the NSArray is an autorelease object, so the NSArray (and therefore, the MKObject) will not be deallocated until the autorelease pool is drained.
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = #[obj];
obj = nil;
temp = nil;
To understand why the array literal, #[], creates an autorelease object, one should note that it expands to +[NSArray arrayWithObjects:count:]. Autorelease objects are created whenever you instantiate an object with any method other than using alloc followed by an init (whether init, or one of the permutations, such as initWithObjects:).
As you observed, when an app creates autorelease object, the object will not be immediately be deallocated, but it will when the autorelease pool drains. Since we generally yield back to the runloop quickly (at which point the pool will be drained), the choice of autorelease objects or non-autorelease objects has little practical impact in simple cases. But if the app, for example, has a for loop in which it creates many autorelease objects without yielding back to the runloop, it could be problematic (especially if MKObject was large or you were doing this many times). For example:
for (NSInteger i = 0; i < 100; i++) {
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = #[obj];
// Note, because they are local variables which are falling out of scope, I don't have to manually `nil` them.
}
Because we are instantiating autorelease NSArray objects in this example, the above would keep all 100 arrays and objects in memory until you yielded back to the runloop and the autorelease pool had a chance to drain. This means that the app's "high water mark" (the maximum amount memory it uses at any given time), would be higher than it might need to be. You could remedy this by either:
use a non-autorelease object (such as by using alloc/init) instead of using the array literal:
for (NSInteger i = 0; i < 100; i++) {
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = [[NSArray alloc] initWithObjects:obj, nil];
}
or
by introducing your own, explicitly declared #autoreleasepool:
for (NSInteger i = 0; i < 100; i++) {
#autoreleasepool {
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = #[obj];
}
}
In this final example, the autorelease pool will be drained for each iteration of the for loop, resolving any challenges with autorelease objects that would otherwise have their deallocation deferred until the end of the loop.
One final caveat: In general, methods that begin with alloc and init (or a variation of the init method), will not generate autorelease objects, whereas all other methods, such as arrayWithObjects:count: will generate autorelease objects. One notable exception is the NSString class, which due to internal memory optimizations, does not conform to this rule. So, if you have any doubt, you can employ your own manual #autoreleasepool if you are repeatedly instantiating and releasing objects, and you are unsure as to whether the objects are autorelease objects or not. And, as always, profiling your app with the Allocations tool in Instruments is a good way of observing the app's high water mark. For an illustration of how these various techniques can impact the memory usage of your app, see https://stackoverflow.com/a/19842107/1271826.
The array was being retained by the autorelease pool. As described in Clang's Objective-C Literals documentation, the array literal syntax expands to a call to +[NSArray arrayWithObjects:count:], which creates an autoreleased array.
A couple of things I see, though I'm not entirely clear on the question so I can't say which applies:
Adding an object to an NSArray or NSMutableArray increments the object's retain count.
In the first instance, you manually instantiate obj, which gives it retain count 1.
Adding it to the NSMutableArray makes its retain count 2.
In this case, obj = nil decrements retain to 1;
temp = nil tells the array to handle releasing its contents. Those w/retain count 0 get dealloc'd immediately.
In the 2nd instance with #[] literal creation, the literal syntax under the hood creates an autoreleased object using the method arrayWithObjects: count:. When this is no longer needed it goes to the autorelease pool for eventual deallocation.
It isn't an issue of the objects IN the array but the way the arrays themselves were created.
Edited my original response to address comments below - I was confusing the issue.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Objective C Memory Management
My code is showing a memory leak here:
NSMutableArray* newImageArray = [[NSMutableArray alloc] init];
NSMutableArray* newMediaArray = [[NSMutableArray alloc] init];
if (self.categoryIndex == 0) {
for (int i=1; i < [categoryArray count]; i++)
{
newImageArray = [NSMutableArray arrayWithArray:[newImageArray arrayByAddingObjectsFromArray:[self getImageArrayByCategoryIndex:i]]];
}
}
else {
newImageArray = [self getImageArrayByCategoryIndex:self.categoryIndex];
}
for (int i=0; i < [newImageArray count]; i++)
{
Media* media = [[Media alloc] init];
NSString* imageFile = [newImageArray objectAtIndex: i];
media.imageFile = [UIImage imageNamed:imageFile];
media.imageLabel = [[imageFile lastPathComponent] stringByDeletingPathExtension];
media.soundFile = [appFolderPath stringByAppendingString:[[[imageFile stringByDeletingPathExtension] stringByAppendingString: #".wav"] stringByReplacingOccurrencesOfString: IMAGES_FOLDER withString: SOUNDS_FOLDER]];
[newMediaArray addObject:media];
}
self.mediaArray = newMediaArray;
[self setNextMediaIndex];
I am not releasing media because it is being used by newMediaArray (which is used by mediaArray, which is used my my main object). Shouldn't everything get released when I release my main object?
It looks like you are leaking all over the place in a variety of ways
newImageArray gets allocated but never released, additionaly you are overwriting the version that you allocated in the first line of you code with another version. So even if you released it at the end of this code segment, the wrong version would get released. It looks like you don't even need to allocate this one.
newMediaArray gets allocated but never released, you assign it to a property mediaArray if you are using #synthesize to create the code for that property, depending on how you declared that property, the setter will retain the value i.e. newMediaArray creating a leak.
media gets allocated but never released, it get added to a NSMutableArray which means it will get retained by the array. If your app crashes when you release media in the for loop the problem is somewhere else
The Memory Management Programming Guide is pretty much a must read
When an NSMutableArray such as newMediaArray adds an object, it will retain that object. You don't need to (nor should you) retain the object on the array's behalf. This is fundamentally how memory management in Objective-C works: each object retains the things it references, and releases them when finished. newMediaArray is its own object, so it'll manage its own references.
You should release media near the end of the body of your for loop because you're done using that object. If you don't release it then, you'll lose your reference to it and you'll have no way to release it in the future.
You do
[newMediaArray addObject:media];
that means that newMediaArray has done a retain on media. You can then release media (you should). The retain done in the array method will keep it alive as long as the array references it. If you don't release it in your method, the retain count will remain 2, and even if the array releases it, it will still be 1 and not be dealloc-ed.
You could do:
Media *media = [[[Media alloc] init] autorelease];
Then the autorelease pool will release it in time, but not before this method ends.
You need to have the [media release] statement at the bottom of the loop. newMediaArray should also be released after it is assigned to the mediArray property.
I am new in objective-c and I am trying to understand memory management to get it right.
After reading the excellent
Memory Management Programming Guide for Cocoa by apple my only concern is when
actually an autoreleased object is released in an iphone/ipod application. My understanding is at the end of a run loop. But what defines a run loop in the application?
So I was wondering whether the following piece of code is right. Assume an object
#implementation Test
- (NSString *) functionA {
NSString *stringA;
stringA = [[[NSString alloc] initWithString:#"Hello"] autorelease]
return stringA;
}
- (NSString *) functionB {
NSString *stringB;
stringB = [self functionA];
return stringB;
}
- (NSString *) functionC {
NSString *stringC;
stringC = [self functionB];
return stringC;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString* p = [self functionC];
NSLog(#"string is %#",p);
}
#end
Is this code valid?
From the apple text I understand that the NSString returned from functionA is valid in the scope of functionB. I am not sure whether it is valid in functionC and in viewDidLoad.
Thanks!
Yes, your functions are valid, and return objects using correct Cocoa conventions for retain/release/autorelease/copy.
To answer your question about what the runloop is, in your application's main() function, it invokes UIApplicationMain(). You can imagine UIApplicationMain looks something like this:
void int UIApplicationMain (int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName) {
UIApplication *app = /* create app using principalClassName */;
[app setDelegate:/* create delegate using delegateClassName */];
while (![app shouldTerminate]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
event = [app getNextEvent];
[app dispatchEvent:event];
[pool drain];
}
}
That while loops is similar to what the UIKit is actually doing, and each trip through that while loop is like a trip through the runloop, where the function getNextEvent blocks waiting for some event to happen. All of your methods are typically called from within something like dispatchEvent:. You might try setting a break point in one of your methods, like an IBAction, and looking in the debugger call stack way up at the top to see the names of the UIKit methods that handle the events and runloop. Since each of your methods are called from within that while loop, each time you call autorelease on an object, that object is added to that outter pool in the run loop. When the current event is finished being dispatched, the pool is drained, and those objects are finally sent release messages.
One last note. There can be more than one autorelease pool, that aren't always at the end of the event loop. Sometimes you might allocate tens of thousands of objects in one trip thorough the event loop. When that happens, you might setup additional inner auto release pools in your own methods to keep the number of autoreleased objects in autorelease pools down. Auto release pools can stack.
There's nothing wrong with that code. It will compile and run as you expect.
The NSString object returned from functionA is still valid upon return since it's being passed down the stack to the next guy (functionB) who is now keeping track of it.
As you can see in the code below I am creating and initialising a Vertex which I then add into a NSMutableArray instance variable within my Frame object. As I currently have this setup myVert is owned by main and pointed to by vertexList. Would I be better setting this up so that I make a copy of inVertex within the addVertex method so the object takes ownership of its data?
-(void)addVertex:(Vertex*) inVertex {
[vertexList addObject:inVertex];
}
// -------------------
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Frame *myFrame;
Vertex *myVert;
NSLog(#"MDD_ObjectTest ... ");
myFrame = [[Frame alloc] init];
myVert = [[Vertex alloc] initWithxPos:111 yPos:222 zPos:333];
[myFrame addVertex:myVert];
[myVert release];
// Clean up
[myFrame release];
[pool drain];
return 0;
}
Finally if I should make a copy what is the best way to do that, should I be looking at ...
-(void)addVertex:(Vertex*) inVertex {
[vertexList addObject:[inVertex copy]];
}
Although I am a little unsure what to do in terms of the Vertex object with regards to copyWith Zone.
gary
This depends on if Vertex is mutable or not.
If Vertex is immutable:
Since the inVertex can not have any of its data changed, the current way you have it is fine. The vertexList will retain inVertex when you add it and will release it when you remove it. Since the properties of inVertex can not be changed there is no difference in storing it versus a copy.
If Vertex is mutable:
You should store a copy so that the changes to inVertex do not affect the vertex stored in the list. Note however that you have a possible memory leak in the way you have it right now. When you copy an object that copy's retain count is set to 1, then when you store it in the vertexList the retain count becomes 2. When you remove it from the list it will have a retain count of 1, causing a memory leak unless you remember to release it. The best place to release it would be in the addVertex method after it is added to the vertexList to keep its retain count at 1, since only one thing has a reference to it.
- (void) addVertex:(Vertex *) inVertex {
Vertex * copy = [inVertex copy];
[vertexList addObject:copy];
[copy release];
}
Note that Vertex must implement the NSCopying protocol for this to work.
I suggest using the immutable approach unless you have a real valid reason to make vertex mutable other than convenience.
Edit: Regarding copyWithZone:
To implement object copying you have to implement the NSCopying protcol that defines the copyWithZone: method. The only consideration you need to make with the zone is that instead of calling the alloc method on the Vertex class you call the allocWithZone: method instead.
- (id) copyWithZone:(NSZone *) zone {
Vertex * copy = [[Vertex allocWithZone:zone] initWithxPos:x yPos:y zPos:z];
// Any other copying that needs to be done
return copy;
}