MPMoviePlayerController deallocated when view is expanded to FullScreen - ios

How can I check when an instance of MPMoviePlayerController is deallocated? Note I already have NSZombieEnabled set to YES and checked.
The code in question works great under some circumstances in my app. In another, when the view with the moviePlayer controls is expanded, the MPMoviePlayerDidEnterFullscreenNotification callback method is executed and shortly after the screen goes black and the app from user viewpoint freezes. (The difference is this one has an extra viewController on the navigation stack). It does not crash the app. It is still running but can not return back to a usable UI.
In the debugger, I check the last known time when the instance was "alive" to get its address, to check and find it is deallocated:
2012-10-01 18:35:46.032 MyApp[930:907] -[MyMoviePlayerViewController moviePlayerDidEnterFullscreenNotification:](203): : moviePlayer = <MPMoviePlayerController: 0x1dba9420> loadState = 3
2012-10-01 18:36:05.280 MyApp[930:907] [MPAVController] Autoplay: Enabling autoplay
Black screen occurs here, so I check the debugger:
(lldb) po 0x1dba9420
2012-10-01 18:37:38.656 MyApp[930:907] *** -[MPMoviePlayerController respondsToSelector:]: message sent to deallocated instance 0x1dba9420
(int) $0 = 498766880 [no Objective-C description available]
If I check other known instance of other classes, they are alive and well as expected:
(lldb) po 0x1da12110
(int) $1 = 497099024 <MyDetailViewControllerWithTableView: 0x1da12110>
(lldb) po 0x1da1af70
(int) $2 = 497135472 <MyMasterTableViewController: 0x1da1af70>
(lldb) po 0x1da19be0
(int) $3 = 497130464 <NSFetchedResultsController: 0x1da19be0>
So, to reiterate, how does one check when and by what an instance gets deallocated or destroyed? Is there an Instruments type that can be used.
Allocations shows that instances was allocated and now not alive....
I suppose I can do a fine step by step trace but that is tedious...
Would be great to be able to set a condition when the event occurs and trigger
the Instruments (or debugger) to stop.

I fixed this. It appears that when the user expands the moviePlayer, the event lifecycle causes the view (MyAddDetailViewController) (that holds the subview (MyMoviePlayerController)) to execute viewWillDisappear & viewDidDisappear.
I mentioned that the code path created an additional viewController on the navigation stack, and that VC was making a brief appearance (evidenced by a viewWillAppear message) then going back into the background with viewWillDisappear. It still is not clear why that would cause the autoreleasePool to free up MyAddDetailViewController thus deallocating MyMoviePlayerController, but it is now clear that there was no strong reference to MyAddDetailViewController.
To fix, I created a property in the VC that pushes MyAddDetailViewController onto the navigation Stack to hold a strong reference to it.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// ... snip ...
self.myAddDetailViewControllerReference = [segue destinationViewController];
}
//
- (void)viewDidUnload {
[self setMediaAddDetailViewControllerReference:nil];
}
That takes care of the black screen/freeze.

Related

How to remove SFSafariViewController as a child view controller correctly?

I am using the technique provided by this SO answer to preload some URL in SFSafariViewController like this:
addChildViewController(svc)
svc.didMoveToParentViewController(self)
view.addSubview(svc.view)
And I try to remove the Safari View controller with following code:
svc.willMoveToParentViewController(nil)
svc.view.removeFromSuperview()
svc.removeFromParentViewController()
Now I can preload the URL and show the Safari View without problem. However, after I repeat the process (preload/show/remove) several times (probably 30+ times) , the app will crash due to some memory issue because the log shows Memory level is not normal or this app was killed by jetsam when the app crashes.
Before crash, I saw some logs about possible-leak warnings:
<Warning>: notify name "UIKeyboardSpringBoardKeyboardShow" has been registered 20 times - this may be a leak
<Warning>: notify name "com.apple.SafariViewService-com.apple.uikit.viewService.connectionRequest" has been registered 20 times - this may be a leak
Am I doing it correctly when removing the Safari View controller? Am I missing something? Or any suggestion to work around this issue?
If your adding child view controller code is as you have specified above then I think its order should be a bit different as per the documentation.
addChildViewController(svc)
view.addSubview(svc.view)
svc.didMoveToParentViewController(self)
You should first add the child view and then call didMoveToParentViewController. Try this and see if it works.
Listing 5-1Adding a child view controller to a container
(void) displayContentController: (UIViewController*) content { [self addChildViewController:content]; content.view.frame = [self
frameForContentController]; [self.view
addSubview:self.currentClientView]; [content
didMoveToParentViewController:self]; }
In the preceding example, notice that you call only the
didMoveToParentViewController: method of the child. That is because
the addChildViewController: method calls the child’s
willMoveToParentViewController: method for you. The reason that you
must call the didMoveToParentViewController: method yourself is that
the method cannot be called until after you embed the child’s view
into your container’s view hierarchy.
you are probably leaking svc. nil it out after removing it.
svc.willMoveToParentViewController(nil)
svc.view.removeFromSuperview()
svc.removeFromParentViewController()
svc = nil
if this doesn't solve it, try enabling zombies or use the leaks instrument

Dealloc called without viewDidLoad being called (crash on removing KVO observer)

I am using a UITabBarController, and my 3rd tab observes an array on a singleton data store (implemented in viewDidLoad).
Currently if I just log out (and change root view controller from App Delegate), the app will crash when dealloc is called on that 3rd tab with the message "cannot remove observer for the key path "X" because it is not registered as an observer.
Using breakpoints, I see that viewDidLoad is never called on this 3rd tab, however dealloc is being called when I sign out. What is going on? I assume the UITabBarController is holding a reference to the 3rd tab when I enter the storyboard, but does not "load" that tab. Yet iOS calls dealloc on it when I release the tab bar controller.
Should I use a boolean to track viewDidLoad execution, or try to remove the observer with a #try statement? Is there an overall better design for this?
Do not use #try. Exceptions in Objective-C should always be considered programmer error, and should be fatal.
As you say, use a boolean ivar set in -viewDidLoad to avoid this.
The view has not been loaded because views are only loaded when they are required for display.
Raw KVO can be dangerous and unwieldy. While not required to answer this question, ReactiveCocoa significantly improves the KVO experience.
viewDidLoad is called before the view appears for the first time. UITabBarController is creating the relevant UIViewController, but the view is not loaded during creation. It is loaded on-demand, when a user visits the tab for the first time.
KVO removal is problematic, I don't think you can avoid using #try in dealloc. I would suggest to use KVOController: it's fairly easy to use and it would also handle all the edge cases for you.
May have found an even better solution. I add the observer in the method initWithCoder:(NSCoder *)aDecoder, which is called when the parent UITabController is loaded. I am using the storyboard which may be why I need to call override this method instead of regular init. Doing this now without the need for a BOOL flag or #try and no crashing.
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[anObject addObserver:self forKeyPath:aKeyPath options:0 context:NULL];
}
return self;
}
Use a flag to set whether or not KVO has been set up. Using #try can create memory management issues depending on the state of the app.

Losing Reference of currently opened View

When Push Notifications has been received in app delegate did receive, what i am doing is taking the last object from navigation stack and calling one of the function of that class.
if([[self.navigationController.viewControllers lastObject] isKindOfClass:[JFFriendsListViewController class]]){
JFFriendsListViewController *friendlist=[self.navigationController.viewControllers lastObject];
[friendlist RefreshRequiredOnSameView];
}
Please help How to get the scrollview from xib??
And When i touch any view... It again regain the Iboutlets references from xib.
Run your app and go to the JFFriendsListViewController page and add a debugger inside viewDidLoad and check the memory address of this class.
Again when you receive push notes then here
JFFriendsListViewController *friendlist=[self.navigationController.viewControllers lastObject];
again check the memory address of friendlist.
Are they same???
I think wen you are receiving push notes you are using diff JFFriendsListViewController instance and your subviews are not loading in main thread yet.

Detecting if view still exists or active in completionHandler block

In my app I'm polling a web service for status updates, using a completionHandler block and making changes to the current view based on returned results when the callback executes.
- (void) tickTimer
{
[MyWebService myWebMethod:param1 completionHandler:^(NSString *result) {
// does view still exist?
[self myUpdateMethod];
// does property still exist?
self.theResult = result;
// does child view still exist?
_txtUpdate.text = result;
}];
}
But in the interim, it's possible that the view may have been unloaded as the user navigates elsewhere.
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to the background? I imagine it gets garbage collected at some point, but how do I tell if it's still safe to access by any of the references above, and what would happen if it's not?
If the view does still exist, how do I tell if it is also still the foreground view?
So, blocks create strong references to all objects pointers that are referred to in their closure. Due to this, your block is going to force [self] to stay in memory until the block is destroyed. If this isn't the behavior you want you should create a weak pointer to self and refer to it inside of the block:
__weak typeof(self) weakSelf = self;
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to
the background? I imagine it gets garbage collected at some point, but
how do I tell if it's still safe to access by any of the references
above, and what would happen if it's not?
If your view stays in the view hierarchy, it will stay in memory. Once there are no more references to the view it will be dealloced.
If you use a weak pointer like outlined above, then [weakSelf] will be nil if the view has been dealloced
If the view does still exist, how do I tell if it is also still the
foreground view?
I'm not sure what you mean by foreground view, but if you want to see if it's still in the view hierarchy then you can check the property -(UIView *)superview. If superview is nil, then it's not on the screen
If you use ARC right, it will not let you use deallocated viewcontroller.
You can use viewDidAppear and viewDidDisappear methods to know visible yours viewcontroller or not.

Receive memory warning and memory leak

I'm using ARC (automatic reference counting).
Is it ok if I set the IBOutlets to nil in the viewDidDisappear instead of viewDidUnload?
Such as these:
[self setTheImage:nil];
[self setBTNplay:nil];
[self setBTNstop:nil];
I'm writing a navigation based app which includes pageViewController, I tested my app in Instruments to see the memory leaks and I keep getting receive memory warning message.
I've even put a log code in the viewDidUnload method. But it doesn't seem to get called when I even pop to rootViewController!
One more thing: If each page has an audioPlayer, where should I set a #property (nonatomic, strong) AVAudioPlayer *audioPlayer; to nil?
Or how do I set it to weak instead of strong? Because it gives me a 'warning' then in this code line:
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:poemURL error:nil];
it says: Assigning retained object to weak variable
You don't need to nil out those values in viewDidUnload. Make sure you're using weak properties instead of strong or assign for IBOutlets. Received memory warning doesn't necessarily mean you're leaking. Received memory warning means that your app is consuming too much memory. Run Instruments and edit your question with how much memory your app uses.
The fact that you're using AVAudioPlayer makes me thing that maybe you're pulling down some massive audio files into memory.
Also by the way, initWithContentsOfURL:error: will get you rejected from the App Store because you're blocking the main thread. Try testing your app on an iPhone with only cellular enabled and go into a part of your office/house that has a bad internet connection. Also try with your phone switched to airplane mode. Your app will undoubtedly either freeze for a long time before the connection fails or it will simply crash.
Instead, you should be using grand central dispatch or downloading it via NSURLConnection's block or delegate methods.
First, do not set to nil your properties in viewDidDisappear cause your view is still loaded. You must always set them to nil in viewDidUnload. It's invoked in low memory situations and here you must clean-up all stuff that breaks system memory.
Apple's UIViewController reference for viewDidUnload
When a low-memory condition occurs and the current view controller’s
views are not needed, the system may opt to remove those views from
memory. This method is called after the view controller’s view has
been released and is your chance to perform any final cleanup.
Second , take a look at this tutorial where is explained very well ARC
Are you calling [[NSNotificationCenter defaultCenter] removeObserver:self]; from one of your view controller subclasses? If so, that would explain why you're not getting viewDidUnload called.
If that's the problem, you should remove yourself from specific notifications when needed rather than all notifications as above. (It's OK to call removeObserver:self from dealloc, though.)
Is it ok if I set the IBOutlets to nil in the viewDidDisappear instead
of viewDidUnload?
There are many things wrong from this statement.
First of all, you do not set IBOutlets to nil in viewDidDisappear. viewDidDisappear is called when the view "disappears" (e.g. when it's in a tab bar controller, and you switch to another tab; or it's on a navigation controller, and you push something on top of it); the view can then "appear" again without loading again. If you set IBOutlets to nil, they will not be set again when you appear. (They are only set when the view is loaded.)
Second, if you have a leak, and setting stuff to nil "fixes it", that means you are not releasing the instance variables. You must always release retained instance variables in dealloc.
I've even put a log code in the viewDidUnload method. But it doesn't
seem to get called when I even pop to rootViewController!
Yes, viewDidUnload is only called in low memory situations. It is not called normally in most situations. If you were depending for it to be called, you were using the wrong method.

Resources