How to get resumeData at UIApplicationWillTerminateNotification in NSURLSessionDownloadTask - ios

I want to save data unfinished downloads when the app is closed.
Tried so, but always empty resumeData:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
- (void)appWillTerminate
{
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
if (resumeData)
[self saveData:resumeData];
else
NSLog(#"Not exist");
}];
}

In my experience, downloading small files does not create resumeData. Try with a larger file (> 20 mb).

Related

Memory Leak: AVplayerViewController

I am facing memory leaks when we play a video and return back to the parent window. See the screenshot below of readings from allocations tool. Every time, when I pop the view controller (showing video) there are some objects related to AVFoundation holding the memory.
Interestingly the responsible library of all these objects is AVFoundation. None of the increase in memory is due to objects created in the APP. It is very highly unlikely that there is some problem with such a popular framework. I saw a few examples of AVPlayerViewController over the web but they seem to have the same problem.
Does anyone have any idea what/where is the problem? If anyone wants to replicate this then he can download any of the 2 projects given above. You have to make minor changes in the storyboard for creating root view controller with the navigation controller.
http://www.modejong.com/blog/post13_iOS8_SunSpot/index.html
https://github.com/coolioxlr/PageView-AVPlayer
This is how I am clearing the memory:
-(void) dealloc{
[self clearCurrentVideo];
}
-(void)clearCurrentVideo {
[_playerItem removeObserver:self forKeyPath:#"status"];
[_currentVideoPlayerViewController.player removeObserver:self forKeyPath:#"rate"];
[_currentVideoPlayerViewController.player pause];
_currentVideoPlayerViewController.delegate=nil;
_currentVideoPlayerViewController.player=nil;
_playerItem=nil;
_currentVideoPlayerViewController = nil;
}
This is how I load the asset for videos:
-(void)playtheAsset:(AVAsset *)asset{
[asset loadValuesAsynchronouslyForKeys:#[#"playable"] completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
[self loadTheAsset:asset withKeys:#[#"playable"]];
});
}];
}
- (void)loadTheAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys{
/* Make sure that the value of each key has loaded successfully. */
for (NSString *thisKey in requestedKeys)
{
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed)
{
//[self assetFailedToPrepareForPlayback:error];
if([thisKey isEqualToString:#"playable"]){
[self showNetworkErrorLabel];
}
return;
} else if ((keyStatus == AVKeyValueStatusLoaded) || ( keyStatus == AVKeyValueStatusLoading )){
[self removeNetworkLabel ];
}
}
/* Use the AVAsset playable property to detect whether the asset can be played. */
if (!asset.playable)
{
/* Generate an error describing the failure. */
NSString *localizedDescription = NSLocalizedString(#"Item cannot be played", #"Item cannot be played description");
NSString *localizedFailureReason = NSLocalizedString(#"The assets tracks were loaded, but could not be made playable.", #"Item cannot be played failure reason");
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
localizedDescription, NSLocalizedDescriptionKey,
localizedFailureReason, NSLocalizedFailureReasonErrorKey,
nil];
NSError *assetCannotBePlayedError = [NSError errorWithDomain:#"StitchedStreamPlayer" code:0 userInfo:errorDict];
NSLog(#"%#",assetCannotBePlayedError);
[self showNetworkErrorLabel];
/* Display the error to the user. */
[self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
return;
}
/* At this point we're ready to set up for playback of the asset. */
/* Stop observing our prior AVPlayerItem, if we have one. */
if (_playerItem)
{
/* Remove existing player item key value observers and notifications. */
[_playerItem removeObserver:self forKeyPath:#"status"];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:_playerItem];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemPlaybackStalledNotification
object:_playerItem];
}
/* Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. */
_playerItem = [AVPlayerItem playerItemWithAsset:asset];
/* Observe the player item "status" key to determine when it is ready to play. */
[_playerItem addObserver:self
forKeyPath:#"status"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:AVPlayerDemoPlaybackViewControllerStatusObservationContext];
/* When the player item has played to its end time we'll toggle
the movie controller Pause button to be the Play button */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:_playerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerItemFailedToPlayToEndTime:) name:AVPlayerItemPlaybackStalledNotification object:_playerItem];
// Remove the movie player view controller from the "playback did finish" notification observers
// Observe ourselves so we can get it to use the crossfade transition
[[NSNotificationCenter defaultCenter] removeObserver:_currentVideoPlayerViewController
name:kPlayerViewDismissedNotification
object:_currentVideoPlayerViewController.player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(videoFinishedCallback:)
name:kPlayerViewDismissedNotification
object:_currentVideoPlayerViewController.player];
/* Create new player, if we don't already have one. */
if (!_currentVideoPlayerViewController.player)
{
/* Get a new AVPlayer initialized to play the specified player item. */
_currentVideoPlayerViewController.player=[AVPlayer playerWithPlayerItem:self->_playerItem];
[_currentVideoPlayerViewController.player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew
context:AVPlayerDemoPlaybackViewControllerRateObservationContext];
}
}
I couldn't figure out the reason behind this. I tried using AVPlayer instead and created my own UI using (reference Apple AVPlayer Demo app.) and I couldn't find any leak there. It just worked.
If someone gets stuck with similar problem just give a try to reference code from AVPlayer Demo app.
And if someone knows the answer for the issue I on this thread. Please let me know.

ios Magical Record save on UIApplicationWillTerminateNotification

I have app that stores system events to Core Data Database. To perform saving I use MagicalRecord.
So, in my logger class in init I have:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDidFinishLaunchingNotification) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleWillTerminateNotification) name:UIApplicationWillTerminateNotification object:nil];
In handle functions I store simple entity with text and timestamp property:
- (void)handleDidFinishLaunchingNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Did finish launching";
dbMessage.timestamp = [NSDate date];
}];
}
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
When I open and close (without crash) app few times, in my DB I can see "Did finish launching" entries (more that one, so I'm sure app was closed, not only moved to BG), but none of "Will terminate".
I would be less surprised if the launching event were missed, because I could expect that init method will be called after notification is posted.
What I can do to store terminate events?
I have an answer:
It looks like when application gets terminated, wont't let me to save in new background thread. But saving in current thread works fine. So all I had to do was change saving method to save in current thread, by calling saveWithBlockAndWait: instead of saveWithBlock:
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
Now events are succesfully saved on DB.

The difference between these two scheduled method calls - iOS

I have seen this around a few times but can't seem to find what is the difference between the two ...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewFetchedUserInfo:)
name:FBSDKProfileDidChangeNotification
object:nil];
- (void)loginViewFetchedUserInfo:(NSNotification *)notification
and
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewFetchedUserInfo)
name:FBSDKProfileDidChangeNotification
object:nil];
- (void)loginViewFetchedUserInfo
I know that (void)methodname:(TYPE *)newName can pass in a value to the method but I don't know what the difference is in the two above and why you would do the first one (which is used in the Facebook SDK example) over the second one.
The first method passes the NSNotification object to the method. This way allows you to access information about the notification.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewFetchedUserInfo:)
name:FBSDKProfileDidChangeNotification
nil];
For example, if the notification was posted with a userInfo dictionary
NSDictionary *userInfo = #{#"Blah" : #"foo"};
[[NSNotificationCenter defaultCenter] postNotificationName:FBSDKProfileDidChangeNotification object:self userInfo:userInfo];
and you wanted to access userInfo in the method. You can also access the sender of the notification, which would be the notification's object.
- (void)loginViewFetchedUserInfo:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
NSObject *sender = notification.object;
}

How to get a non null userInfo after the first time to my #selector(method)

I have a code like the following:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemPlayEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:nil userInfo:userInfo];
In the selector method:
- (void)itemPlayEnded:(NSNotification *)notification
{
NSLog(#"Entered itemPlayEnded");
AVPlayerItem *p = [notification object];
NSLog(#"userinfo description %#",[[notification userInfo] description]);
}
The first time the itemPlayEnded is accessed the userInfo is non-null. After the first time the value is null.
Can anybody tell me why this is happening? Why null value for userInfo after the first time?
EDIT:
I need to clarify what is happening. I also updated my Notification code to use the lastItem which is a AVPlayerItem.
queuePlayer = [[AVQueuePlayer alloc] init];
NSDictionary *userInfo = #{#"tones": copyoftones};
for (NSString *playThis in listOfTonesToBePlayed) {
NSString *soundPath =[[NSBundle mainBundle] pathForResource:playThis ofType:#"mp3"];
NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
AVPlayerItem *thePlayerItemA = [[AVPlayerItem alloc] initWithURL:soundURL];
lastItem = thePlayerItemA;
[queuePlayer insertItem:thePlayerItemA afterItem:nil];
}
queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemPlayEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:lastItem];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:lastItem userInfo:userInfo];
[queuePlayer play];
What happens.
before the queuePlayer is able to play a tone the itemPlayEnded is entered along with the Dictionary non nil
Next the list of tones are played
The itemPlayEnded is re-entered with the Dictionary nil.
I wanted to use code to reset the Notification inside of the itemPlayEnded method with something like the following (p is a AVPlayerItem which is the lastItem from the code with the Notification):
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:p];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemPlayEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:p];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:p userInfo:userInfo];
[queuePlayer play];
But then the itemPlayEnded method is re-entered in a never-ending loop without playing the player. A race condition.
Any suggestions?
EDIT 2:
I have determined that the offending code is:
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:lastItem userInfo:userInfo];
some how I need to set the object to use the correct sender. Not sure yet on what to put there.
EDIT 3:
What I really wanted to do was be able to loop thru the complete sequence of sounds or mp3s. I finally figured that what I was trying to do here was not going to work like I wanted it. So, I ended up using the class from https://github.com/dgiovann/AVQueuePlayerPrevious and this worked out great for me!
You are subscribing to the AVPlayerItemDidPlayToEndTimeNotification which is being posted first by yourself (with some user info) and then posted again, this time NOT by you and therefore may or may not contain any user info. Refer to the documentation to see when and why Apple will post this notification and what information you can expect with it.
https://developer.apple.com/library/mac/documentation/AVFoundation/Reference/AVPlayerItem_Class/Reference/Reference.html

Method getting called multiple times

I am using MPMoviePlayer in my project. I have registered for the movie player finish notifications and it is working good. I am displaying an error alert whenever a notification is received for movie player error. But the problem is that the error alert displays multiple times. It happens because more than one notifications are received for same error and that too at the same time. I have tried using boolean variables to control the alert display but since the notifications are received at the same time, it is not working. What approach should I apply, please suggest.
My code for notification method:
MPMovieFinishReason reason = [[[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
NSError *errorMsg = [[notification userInfo] valueForKey:#"error"];
NSString *errmsg = [errorMsg localizedDescription];
if (reason == 1 && !errorReceived){
NSError *errorMsg = [[notification userInfo] valueForKey:#"error"];
NSString *errmsg = [errorMsg localizedDescription];
[self showErrorAlert];
}
For registering notification:
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(moviePlayerDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.player];
for removing observer, in viewWillDisappear
[[NSNotificationCenter defaultCenter]removeObserver:self];
Remove the observer once you get the error
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:player];
and add the observer once you click to play .

Resources