Am I missing something with ARC? - ios

I'm developing a large scale application for iOS 5 using ARC in xcode. The system seems to work well except for when I'm trying to deallocate one of my interfaces. I'm using a framework called WhirlyGlobe to create a 3D interactive globe in the first view controller.
When I switch view controllers (between the 4 I have), I notice that the memory being used for the view controller with the globe isn't being released. All the other view controllers (only using simple views and images) release their memory fine - But the globe stays resident, or so it seems. When navigating back to the globe, I get almost a 10mb jump in memory due to 1mb allocations in "glsmLoadTextureLevelBuffer".
To get on with my question - Is there anything more I can do, with ARC active, to help release my objects? I've noticed my viewDidUnload and dealloc methods are not being called at all, and that the only way I can get anything to fire is using viewDidDisappear (which is not ideal obviously) - See below:
- (void)clear
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (self.layerThread)
{
[self.layerThread cancel];
while (!self.layerThread.isFinished)
[NSThread sleepForTimeInterval:0.001];
}
self.glView = nil;
self.sceneRenderer = nil;
if (theScene)
{
delete theScene;
theScene = NULL;
}
self.theView = nil;
self.texGroup = nil;
self.layerThread = nil;
self.earthLayer = nil;
self.vectorLayer = nil;
self.labelLayer = nil;
self.interactLayer = nil;
self.pinchDelegate = nil;
self.panDelegate = nil;
self.tapDelegate = nil;
self.longPressDelegate = nil;
self.rotateDelegate = nil;
}
- (void)viewDidDisappear:(BOOL)animated {
NSLog(#"dealloc - viewDidDisappear");
[self clear];
}
I'm setting everything I no longer need to nil. Is this the best practise?
The globe setup code:
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// Set up an OpenGL ES view and renderer
EAGLView *ev = [[EAGLView alloc] initWithFrame:CGRectMake(0, 0, 824, self.view.frame.size.height)];
self.glView = ev;
self.sceneRenderer = [[SceneRendererES1 alloc] init];
UIColor *whiteC = [UIColor whiteColor];
[sceneRenderer setClearColor:whiteC];
glView.renderer = sceneRenderer;
glView.frameInterval = 2; // 60 fps (2)
[self.view addSubview:glView];
self.view.backgroundColor = [UIColor blackColor];
self.view.opaque = YES;
self.view.autoresizesSubviews = YES;
//glView.frame = self.view.bounds;
glView.frame = CGRectMake(275, GLOBE_HEIGHT_FIX, 768, SCREEN_HEIGHT+STATUS_BAR_HEIGHT); // was 260 x
glView.backgroundColor = [UIColor whiteColor];
self.view.backgroundColor = [UIColor whiteColor]; // red for debug
// Create the textures and geometry, but in the right GL context
[sceneRenderer useContext];
self.texGroup = [[TextureGroup alloc] initWithInfo:[[NSBundle mainBundle] pathForResource:#"bdGlobe_info" ofType:#"plist"]];
// Need an empty scene and view
theScene = new WhirlyGlobe::GlobeScene(4*texGroup.numX,4*texGroup.numY);
self.theView = [[WhirlyGlobeView alloc] init];
[theView setFarPlane:5.0];
[theView setHeightAboveGlobe:GLOBE_HEIGHT_VIEW];
if (globeShouldAnimate) glView.alpha = 1.0;
// Need a layer thread to manage the layers
self.layerThread = [[WhirlyGlobeLayerThread alloc] initWithScene:theScene];
// Earth layer on the bottom
self.earthLayer = [[SphericalEarthLayer alloc] initWithTexGroup:texGroup];
[self.layerThread addLayer:earthLayer];
// Set up the vector layer where all our outlines will go
self.vectorLayer = [[VectorLayer alloc] init];
[self.layerThread addLayer:vectorLayer];
// General purpose label layer.
self.labelLayer = [[LabelLayer alloc] init];
[self.layerThread addLayer:labelLayer];
self.interactLayer = [[InteractionLayer alloc] initWithVectorLayer:self.vectorLayer labelLayer:labelLayer globeView:self.theView
countryShape:[[NSBundle mainBundle] pathForResource:#"10m_admin_0_map_subunits" ofType:#"shp"]
oceanShape:[[NSBundle mainBundle] pathForResource:#"10m_geography_marine_polys" ofType:#"shp"]
regionShape:[[NSBundle mainBundle] pathForResource:#"10m_admin_1_states_provinces_shp" ofType:#"shp"]];
self.interactLayer.maxEdgeLen = [self.earthLayer smallestTesselation]/10.0;
[self.layerThread addLayer:interactLayer];
// Give the renderer what it needs
sceneRenderer.scene = theScene;
sceneRenderer.view = theView;
// Wire up the gesture recognizers
self.panDelegate = [PanDelegateFixed panDelegateForView:glView globeView:theView];
self.tapDelegate = [WhirlyGlobeTapDelegate tapDelegateForView:glView globeView:theView];
self.longPressDelegate = [WhirlyGlobeLongPressDelegate longPressDelegateForView:glView globeView:theView];
// Kick off the layer thread
// This will start loading things
[self.layerThread start];

You can use the allocations instrument for this. Using heap shot, you can mark the heap at various points in the lifetime of your application and compare the object graph that constitutes the current allocations in memory at the point of each snapshot. That should help you narrow down what's being retained and by whom.

This is anecdotal, but COULD be a similar situation.
I just had a situation where my objects were not getting released ever, in ARC, even though they were accessed in a static way (e.g., [SingletonThing instance].thing) because the autorelease pool wasn't draining. The reason for this was a bizarre endless loop that runs on its own thread. Putting a separate #autorelease block for the enclosing code.
On the other hand, even if one of your (or the libs) object has the UIView as a subview, I think your UIViewController will never viewDidUnload and therefore never dealloc. I have to check this empirically.

make sure your -(void)viewDidDisappear:(BOOL)animated is invoked.
if you use
[self.view addSubview:yourViewController.view];
and
[yourViewController.view removeFromSuperview];
then viewDidDisappear: and viewDidAppear: will not be invoked
these callback will only be invoked
when you use presentViewController: in IOS 5
or presentModalViewController:
and dismissViewControllerAnimated: in IOS 5
or dismissModalViewControllerAnimated:
or use UINavigationController to present and dismiss your viewController

I found the problem after using heap shots (thanks to Mark Adams) - Turns out I wasn't making a couple of delegates weak entities, so they weren't being released when changing the view controller. Default strong delegates stay resident.
Thanks to all the suggestions, they all helped point me in the right direction :)

Related

Xcode Memory Debugger showing instances of already removed objects - iOS

Please see attached image for reference. All of the viewControllers have been already removed from the app but Memory Debugger shows its instances as well as all its properties. When i click the Show Only Leaked blocks filter of Memory Debugger, the viewControllers and other instances do not appear in it. Does it mean there are no leaks?
How do I solve the problem. What does it mean?
I do have the PKYStepper block in the CartViewController's cellForRowAtIndexPath (Stepper is a UIControl in my TableViewCell) method as follows :
PKYStepper *qtyStepper = [cell viewWithTag:993];
qtyStepper.tappedCallback = ^(PKYStepper *stepper) {
NSLog(#"Tapped!");
rowSelected = indexPath;
if (((Dish*)((MenuSubSection*)_section.subSections[0]).dishesArray[indexPath.row]).disheOptions.count)
{
UIWindow *window = [UIApplication sharedApplication].keyWindow;
NSBundle* bun = [NSBundle bundleWithIdentifier:#"com.test.test"];
DishItemOption *dishOptions = [[bun loadNibNamed:#"DishItemOption" owner:self options:nil] objectAtIndex:0];
dishOptions.frame = CGRectMake(0, 0, window.frame.size.width, window.frame.size.height);
dishOptions.dish = [[Dish alloc] initWithDishObject:((Dish*)((MenuSubSection*)_section.subSections[0]).dishesArray[indexPath.row])];
dishOptions.delegate = self;
[window addSubview:dishOptions];
}
};
How to make it reference the Weak Self?
It looks like you've likely captured the view controller in a callback block of some sort.
Specifically, PKYStepper seems to have a callback block that is strongly referencing the view controller. Either make sure said reference is weak or make sure the block is properly destroyed when the view controller is torn down.
Found the solution. Updated my callbacks to the following :
__weak typeof(self) weakSelf = self;
qtyStepper.incrementCallback = ^(PKYStepper *stepper, float newValue) {
CartViewController *sSelf = weakSelf;
[sSelf updateTotalCharges]; //Had to use WEAKSELF in the callback!
};

iOS: What happens between init and loadView?

I have an Objective-C app with a programmatically created view for which I'm seeing strange (to me) behavior. Specifically, there's a long pause (up to 30 seconds) between the end of the init method and the beginning of the loadView method. I'm unable to see anything out of the ordinary when setting breakpoints at both those places, so I'm wondering what the system is doing between the init and the loadView, and whether there's anything I can do to optimize the work being done at that point.
Thanks.
Edit: adding some code below (abbreviated for clarity)
The app plays and records video. This view controller reviews a video that's just been recorded but not yet saved / posted.
// called from the view controller that pushes this onto the navigation controller stack
- (id)initWithURL:(NSURL *)fileURL
{
self = [super init];
if (self) {
self.localFileURL = fileURL;
sessionManager = [[sessionManager alloc] initWithURL:fileURL];
}
NSLog(#"return self");
return self;
}
- (void)loadView
{
NSLog(#"begin loadView");
self.playView = [[PlayView alloc] initWithFrame:screenRect];
self.view = self.playView;
}
The pause occurs between the two logging statements.

UIImage passing between controllers causes memory warnings

I am working on an app that takes an image from the photo library using UIPickerController and then loads it into an UIImageView in the first controller. Then i pass the UIImage via property to the second controller of navigation controller in order to edit the image here. When i pop this EditController tapping on Back and then retake the process passing one more time the image to the EditController, the memory encreases every time and after 5 cycles i receive memory warnings. Is there a solution to this issue?
//Getting the image from the PickerController
_imageToEdit = info[UIImagePickerControllerOriginalImage];
_imageToEdit=[self fixOrientation:_imageToEdit];
[_imageView setImage:_imageToEdit];
//Passing image to EditController
if ([segue.identifier isEqualToString:#"editImage"]) {
EditImageViewController *destViewController=segue.destinationViewController;
destViewController.newlyImage=_imageToEdit;
destViewController.optVC=self;
destViewController.numberReceived=_firstTimeInEdit;
}
I made a new method where I release all the objects I don't need. I don't receive memory warnings anymore.
- (void) backAction {
[_editBaseController removeAllButtons];
_editBaseController = nil;
_undoImage = nil;
copyImage = nil;
_modifiedImage = nil;
_newlyImage = nil;
aCIImage = nil;
context = nil;
free(imageRawData);
free(filterChannelRowData);
_scrollingViewOne = nil;
_scrollingViewTwo = nil;
[self.navigationController popViewControllerAnimated:YES];}

Warning after didreceivememorywarning

During a memory warning I do the following:
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if ([self isViewLoaded] && [self.view window] == nil) {
self.restaurantsTableView = nil;
self.restaurantFecther = nil;
self.bgImageView = nil;
self.menuFetcher = nil;
self.searchBar = nil;
self.searchBarDisplayContr = nil;
self.feed = nil;
self.searchResults = nil;
self.locationManager = nil;
self.restaurantsMap = nil;
self.myImage = nil;
self.rDetailsVC = nil;
self.introVC = nil;
self.SubclassVC = nil;
self.view = nil;
}
}
This seems to work fine, except that when I return to that view which all it's properties has been set to nil, plus the view, I get the following warnings:
The top layout guide length constraint unexpectedly lost its container. Did the application remove all constraints from the view controller's view (which would be an app error)?
The bottom layout guide length constraint unexpectedly lost its container. Did the application remove all constraints from the view controller's view (which would be an app error)?
How can I fix this, so I don't get those warnings and is the code above, the right way to deal with memory warnings?
You should not destroy everything in didReceiveMemoryWarning like you have here. You should only generate non-essential bits and pieces. You especially don't want to set anything you don't explicitly create to nil, such as self.view.
The best way to handle this is to make each of your programmatically created views lazy-loaded, and only set them to nil if not visible on screen (IE the user is currently viewing a another view controller).
However, I doubt that your views are the memory problem here. I would worry about the data collections you have.
Only set to nil those item that can be re-created. Then you will need to check them for nil and re-create as necessary.
Also call [super didReceiveMemoryWarning] after you have released your memory.

Objective-C memory management issues

I am new to iPhone programming and Objective-C.
I am building a View-based application.
The problem is that none of the UIViewController's dealloc functions are ever called.
I have decided to unload all my retained objects programmaticaly, right before presenting the next UIViewController class.
I have resolved all the leaks detected by Xcode, tested the application with Leaks and Allocations Tools and everything seams OK, but the memory used builds up to around 180 MB and the app crashes.
The self.retainCount before presenting the next UIViewController is 1, the objects owned are released and the pointers are nil, but still the memory builds up.
Can you give me a clue? If needed I will post some code samples. Or can you send me some references to something like Objective-C memory management 101?
This is my interface:
#interface GameScreen : UIViewController
{
IBOutlet UILabel *timeLabel;
IBOutlet UIView *gameView;
IBOutlet UIImageView *gameViewFrame;
IBOutlet UIButton *showOutlineButton;
UIImage *puzzleImage;
UIView *optionsView;
UIView *blockView;
NSTimer *timer;
UIImageView *viewOriginalPicture;
SHKActivityIndicator *activityIndicator;
BOOL originalPictureShown;
BOOL outLineShown;
}
#property (nonatomic, retain) IBOutlet UILabel *timeLabel;
#property (nonatomic, retain) IBOutlet UIView *gameView;
#property (nonatomic, retain) IBOutlet UIImageView *gameViewFrame;
#property (nonatomic, retain) IBOutlet UIButton *showOutlineButton;
#property (nonatomic, retain) UIImage *puzzleImage;
And here is the implementation:
- (id) initWithPuzzleImage: (UIImage *) img
{
if((self = [super init]))
{
puzzleImage = [[UIImage alloc] init];
puzzleImage = img;
}
return self;
}
This is the function called when the user taps the exit button:
- (void) onExit
{
[timer invalidate];
CurrentMinuts = 0;
CurrentSeconds = 0;
//remove piece configurations
[pieceConfigMatrix removeAllObjects];
[pieceFramesMatrix removeAllObjects];
PuzzleViewController *modalView = [[PuzzleViewController alloc] init];
[self unloadObjects];
[self presentModalViewController:modalView animated:YES];
[modalView release];
}
And the unloadObjects function:
- (void) unloadObjects
{
[self resignFirstResponder];
[viewOriginalPicture release];
viewOriginalPicture = nil;
[timeLabel release];
timeLabel = nil;
[gameView release];
gameView = nil;
[originalImage release];
originalImage = nil;
[gameViewFrame release];
gameViewFrame = nil;
[timer release];
[showOutlineButton release];
showOutlineButton = nil;
}
I have a lead of what I do wrong, but I am not sure. Let me explain. I am adding the puzzle pieces to the 'gameView' property. For this, I have a 'SplitImage' object. The following function is called in - (void) viewDidLoad:
- (void) generatePuzzleWithImage:(UIImage *) image
{
SplitImage *splitSystem = [[SplitImage alloc] initWithImage:image andPuzzleSize:gPuzzleSize];
[splitSystem splitImageAndAddToView:self.gameView];
[splitSystem release];
}
Next, initialization function for the SplitImage class and the splitImageAndAddToView function:
- (id) initWithImage: (UIImage *) image andPuzzleSize: (int) pSize
{
if((self = [super init]))
{
UIImage *aux = [[[UIImage alloc] init] autorelease];
pieceCenterSize = [SplitImage puzzlePieceSizeForNumberOfPieces:pSize];
UIImage *outSideBallSample = [UIImage imageNamed:#"sampleHorizontal.jpg"]; //convexity size for puzzle size
outSideBallSample = [outSideBallSample resizedImageWithContentMode:UIViewContentModeScaleAspectFit bounds:CGSizeMake(pieceCenterSize, pieceCenterSize) interpolationQuality:kCGInterpolationHigh];
outSideBallSize = roundf(outSideBallSample.size.height);
puzzleSize = pieceCenterSize * pSize;
pieceNumber = pSize;
if(image.size.height < puzzleSize || image.size.height > puzzleSize || image.size.width < puzzleSize || image.size.width > puzzleSize)
{
aux = [SplitImage resizeImageForPuzzle:image withSize:puzzleSize];
aux = [SplitImage cropImageForPuzzle:aux withSize:puzzleSize];
}
aux = [aux imageWithAlpha];
originalImage = [[UIImage imageWithCGImage:aux.CGImage] retain];
mainImage = aux.CGImage;
imageSize = CGSizeMake(aux.size.width, aux.size.height);
NSLog(#"%#", NSStringFromCGSize(imageSize));
splitImageSize = CGSizeMake(pieceCenterSize + 2*outSideBallSize, pieceCenterSize+2*outSideBallSize);
}
return self;
}
- (void) splitImageAndAddToView: (UIView *) view
{
for (int i = 0; i < pieceNumber; i++)
for(int j = 0; j < pieceNumber; j++)
//some code
UIImage *mask;
mask = [self randomlyRetriveMaskWithPrefix:1 forPieceAtI:i andJ:j];
CGImageRef split = CGImageCreateWithImageInRect(mainImage, cuttingRect);
PuzzlePiece *splitView = [[PuzzlePiece alloc] initWithImage:[UIImage imageWithCGImage:split] andMask:mask centerSize:pieceCenterSize objectMatrixPosition:i*pieceNumber+j outSideBallSize:outSideBallSize pieceType:pieceType pieceFrame:cuttingRect];
[pieceFramesMatrix addObject:[NSValue valueWithCGRect:cuttingRect]];
[splitView setTag:(i+1)*100+j];
[view addSubview:splitView];
CGImageRelease(split);
[splitView release];
}
Thank you,
Andrei
Objective-C memory management 101 is here:
https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
Ignore the stuff on garbage collection, it isn't available for iOS.
It's not normal to release objects before presenting the next UIViewController.
Assuming you have a NIB, its contents will be loaded the first time that you (or the system) accesses the view member. Your view controller will get a call to loadView, then subsequently to viewDidLoad. If you have any views you want to add programmatically, you can do that in loadView or viewDidLoad, if you want to interrogate objects loaded from the NIB then you can do that in viewDidLoad.
Once loaded, the view will remain in memory unless and until a low memory warning occurs. At that point it'll be released. You'll get viewDidUnload. You should release anything view related that you've still got an owning reference to in there, and set to nil any weak references you may have.
Any attempt to access the view property subsequently will cause the NIB to be reloaded, etc.
So, when presenting a new UIViewController, just present it. If you create objects that are used only for display of that controller then do so on viewDidLoad, and release them on viewDidUnload.
That all being said, the Leaks tool in Instruments should be able to tell you which types of object are leaking and where you first allocated them, so it makes finding leaks really quite easy. The only thing to watch out for is that if one object handles its properties/members entirely correctly but is itself leaked then anything it creates will generally also leak. So when something leaks, check the thing that created it isn't also leaking before tearing your hair out over why you can't find a problem.
First, retainCount is useless.
Activity Monitor is pretty close to just as useless. As well, the behavior in the simulator can be quite different on the memory use front. Better to focus debugging of a problem like this on the device.
Next, this:
The problem is that none of the
UIViewController's dealloc functions
are ever called. I have decided to
unload all my retained objects
programmaticaly, right before
presenting the next UIViewController
class.
Any time you find yourself programmatically working around incorrect behavior, you are just creating more bugs.
Step back from your custom hack and figure out why the dealloc isn't being called. Something somewhere is over-retaining the object. The allocations instrument with retain tracking turned on will show you exactly where all retains and releases are sent to the errant objects.
Leaks likely won't show anything if whatever is retaining the objects is still reachable from a global or the stack (i.e. leaks are objects that can never be used by your program again -- but there are many more ways to explode memory use without it being truly a leak).
You should also "Build and Analyze", then fix any problems it identifies.
Next, if you are seeing memory accretion of repeated operations on the user's part, then Heapshot analysis is extremely effective at figure out exactly what has gone wrong.
Some specific comments:
puzzleImage = [[UIImage alloc] init];
puzzleImage = img;
2 bugs; you are leaking a UIImage and not retaining img. That your app doesn't crash in light of the above code indicates that there is likely an over-retain elsewhere.
retainCount is not a reliable debugging tool.
You should never pay attention to or rely on retainCount. Just because you released it does not mean that some part of the program does not still have a reference to it. retainCount has no value.
Generally as a rule of thumb. If you use 'alloc' you must 'release' at some point.
Unless ofcourse you have put it into an autorelease pool.
Leaks should be able to point you to the objects that are leaking, using that, narrow down to where those objects are added, stored etc.
Post examples of your code on how you instantiate objects and where you release them, you maybe doing something wrong early on.
edit: apologies, i put 'init' not 'alloc' previously, thank you dreamlax, early morning mistake.

Resources