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.
Related
I ran this code here, and it worked perfectly.
However, I’m facing a problem when trying to add it to my project.
After the class YouTubeHelper call showAuthenticationViewController:
– (void)showAuthenticationViewController:(UIViewController *)authView {
[self.navigationController presentViewController:authView animated:NO completion:nil];
}
Inside GTMOAuth2ViewControllerTouch.m I’m getting an exc_bad_access, inside loadView, on [super loadView], as below:
– (void)loadView {
NSString *nibPath = nil;
NSBundle *nibBundle = [self nibBundle];
if (nibBundle == nil) {
nibBundle = [NSBundle mainBundle];
}
NSString *nibName = self.nibName;
if (nibName != nil) {
nibPath = [nibBundle pathForResource:nibName ofType:#”nib”];
}
if (nibPath != nil && [[NSFileManager defaultManager] fileExistsAtPath:nibPath]) {
[super loadView]; <<<< exc_bad_access here!
} else {
// One of the requirements of loadView is that a valid view object is set to
// self.view upon completion. Otherwise, subclasses that attempt to
// access self.view after calling [super loadView] will enter an infinite
// loop due to the fact that UIViewController's -view accessor calls
// loadView when self.view is nil.
self.view = [[[UIView alloc] init] autorelease];
#if DEBUG
NSLog(#"missing %#.nib", nibName);
#endif
}
}
Any idea why this is happening and how to fix it?
Thanks!
You should never call [super loadView]; in your subclass. Remove that line from your implementation.
You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view
hierarchy to the view property. The views you create should be unique
instances and should not be shared with any other view controller
object. Your custom implementation of this method should not call
super.
Reference: UIViewController Class Reference
Thanks Midhun and EridB.
As the libraries are a little bit old, I did my own implementation to upload videos to YouTube.
Here some links that helped on it:
https://developers.google.com/youtube/v3/guides/implementation/videos
https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
Specially this one was very useful to try and check request/response:
https://developers.google.com/oauthplayground/?code=4/1c_ILMzPMhgPWpCI6L7M-LeBe5jL-BY1Xa1sS0oWQJ0
I hope it helps others with YouTube API implementation.
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.
I clear out the table view delegate and data source methods directly in dealloc as below:
- (void)dealloc
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
But looking at some online examples of dealloc, I see that everybody is checking whether the view is loaded before clearing out the delegate and data source like below:
- (void)dealloc
{
if ([self isViewLoaded])
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
}
Curious to know is it just to check if the memory is allocated to the view, if yes then clear else not. Or is there any specific reason for adding a check here?
If your controller is a table view controller then calling self.tableView when the view isn't loaded will cause it to load. If you're about to get deallocated then there is no point going to the effort of loading the view. So checking isViewLoaded is a cheap way of preventing that from happening.
What #Wain mentions is right. However, as per the iOS Memory Management Guidelines you should
NEVER use self to refer to an ivar inside init or dealloc precisely for situations like the one he described.
The correct way to do it would be:
- (void)dealloc
{
_tableView.delegate = nil;
_tableView.dataSource = nil;
}
Hope this helps!
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 :)
I have a method that will dismiss the current view in the navigation controller and replace it with another view. The code looks like this
-(void)didTransferRequest:(NSString *)_transferComments {
AddRequestViewController *ar = [[AddRequestViewController alloc]
initAsTransferForRequestID:requestID
withClosingComments: _transferComments]];
UINavigationController *currentNav = self.navigationController;
[[self retain] autorelease];
[currentNav popViewControllerAnimated:NO];
[currentNav pushViewController:ar animated:NO];
[ar release];
}
[AddRequestViewController.m]
-(AddRequestViewController *)initAsTransferForRequestID:(int)requestID
withClosingComments:(NSString *)closingComments{
self = [self initWithStyle: UITableViewStyleGrouped];
if (self) {
_requestID = requestID;
_closingComments = closingComments;
}
return self;
}
The problem is that once the new view is pushed onto the nav stack, it crashes when the view attempts to access the contents passed in by _transferComments. The pointer is pointing to something else which would make sense since the view gets popped.
I was successful in using withTransferComments: [_transferComments copy] but the Analyzer identified a memory leak with this.
Is using copy safe and should I ignore the leak message or is there a better way to send the string over?
Your AddRequestViewController isn't taking ownership of _transferComments.
You need to read Cocoa Core Competencies - Memory Management and Basic Memory Management Rules.
In the code snippet you posted (without the copy), I deduce that AddRequestViewController doesn't send retain to _transferComments. If it wants to make sure _transferComments stays around, it needs to send it the retain message to take ownership of the string. When AddRequestViewController is done with the string, it needs to send it release to relinquish ownership. You would probably do this in -[AddRequestViewController dealloc].
Basically, your initAsTransferForRequestID:withClosingComments: method should look something like this:
- (id)initAsTransferForRequestID:(int)requestId withClosingComments:(NSString *)transferComments {
if (!(self = [super init]))
return nil;
_requestId = requestId;
_transferComments = [transferComments retain];
return self;
}
(Note that I'm using the common convention of naming instance variables with a leading underscore.) Your dealloc method should look like this:
- (void)dealloc {
[_transferComments release];
[super dealloc];
}
When you changed your code to copy _transferRequest, you did create a memory leak. The copy message creates an "owning" reference to the copy, and something needs to take responsibility for relinquishing that ownership. You didn't change either of your objects to do that.