How to trigger a delegate method after a certain period of time? - iphone

I'm using a class which downloads a file asynchronously .. works a bit like this
// in AViewController.m
DataGetter *blueFile = [[DataGetter alloc] init];
blueFile.delegate = self;
[blueFile getData:#"http://example.com/blue-file"];
It has a delegate method which does stuff to the file once downloaded
- (void) dataGetterFinished:(DataGetter *)dataGetter
{
// code
}
This works OK in ideal conditions, but as we're dealing with a mobile device, connections are not always reliable. The connection might break off half way thru, or it might be unusably slow.
So I'm wondering how I would set up a delegate method which triggers after, say, 10 seconds, which then displays an error and stops the operation. Would I have to use NSTimer, or NSNotification , or some combination?

Quinn "The Eskimo!" from Apple did a two talks on network programming for iPhone at WWDC 2010. It's session 207 and 208, you can download them here: http://developer.apple.com/videos/wwdc/2010/
The simple recepie for network success is:
Use NSURLConnection asynchronously.
Do not set a manual time-out using timers or any other means, the defaults are sane.
Instead be prepared to handle for a connection:didFailWithError:, that will be sent for time-outs.
If needed you can manually cancel a connection using -[NSURLConnection cancel], in response to user action for example.

Related

corebluetooth : didDisconnectPeripheral reduce timeout

I am working with core bluetooth framework . I have implemented the didDisconnectPeripheral method to detect the disconnected peripheral . Right now it is calling around 20 seconds after peripheral has been disconnected . I want to reduce that timeout period . I have searched a lot in the document but not able to get the answer . is it really possible ?
I've been using that method, and it calls (nearly) immediately upon disconnection for me, so it definitely should be possible to reduce that delay period. Are you sure you're not confusing that with the didFailToConnectToPeripheral method? The didFail method normally takes a long time to be called, because I believe I recall reading that Bluetooth LE doesn't have a timeout period, so it will try to connect for a long time.
It is possible that you have the delegate working on a background thread, which could explain the delay.
Check your initialization to see if it is going on the a different thread. If your queue value is something other than nil, that is likely the case.
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
Also, in your didDisconnectPeripheral method, you can place this to determine if the thread changed when it was called.
NSLog(#"Current thread: %#",[NSThread currentThread]);
If the thread has changed, then you can modify the initialization to make sure it goes on the main.

iPhone/iOS: Best practices to shorten launch time of an app?

I found it took 4-5 seconds on an iPhone4/iOS6.0 device, and I want to know what is the system doing in that time, are there any best practices to short the launch time of an app?
many thanks.
EDIT:
I log it and it took about 1.5s in applicationDidFinishLaunchingWithoptions:
,and it is like 3.5-4s(I used a clock to get a preciser time) from I tap the app icon to its launch screen disappear.That is to say, about 2-2.5s to launch the app before calling applicationDidFinishLaunchingWithoptions
Simply watch Session 225 Up and Running: Making a Great Impression with Every Launch from WWDC 2012, it will take you through the process of profiling your app launch and give tips on most often made mistakes, it's great.
There is a very very good WWDC 2012 video with a long sequence devoted to exactly this topic: Session 235 (Responsiveness). I strongly recommend that you watch it. It gives you lots of hints and tricks for speeding up launch (don't link to too many libraries, etc.), tells you how to measure and how to read your measurements in Instruments, etc.
The system isn't what's causing your app to take 4-5 seconds to launch. It's probably your code in applicationDidFinishLaunching, or the init, viewDidLoad, or other setup methods of your root view controller.
Profile your app on a real device with the 'Time Profiler' Instrument. Only launch your app, don't press any buttons on the UI once it's launched. We only want to see what's going on during launch.
I would suggest checking the 'Separate by Thread,' 'Invert Call Tree,' 'Hide System Libraries,' and 'Show Obj-C Only' options under 'Call Tree' on the lower left side of the window.
Once you've checked your chosen options, go down the list of method calls. Start with the ones taking the most time (for obvious reasons). You can double click on symbol names to get more detailed information on where time was spent. Good luck!
We had the same problem.
Ultimately, we sped up launch time significantly by preloading the video in viewDidAppear. By the time the user taps the play button to launch the movie, enough content has loaded so that it can begin playback almost immediately.
e.g.:
- (void)viewDidAppear:(BOOL)animated {
NSURL *movieURL = [NSURL URLWithString:#"http://static.example.com/movie.mov"];
MPMoviePlayerViewController *vc = [[MPMoviePlayerViewController alloc] initWithContentURL:movieURL];
vc.moviePlayer.shouldAutoplay = NO;
[vc.moviePlayer prepareToPlay];
self.moviePlayerViewController = vc;
}
Full details in this blog post: http://blog.tetherpad.com/blog/2013/3/22/improving-tutorial-movie-load-times-on-ios-for-fun-and-profit
In my opinion, the launch time mainly cost in function,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
If you do a bunch of stuff which blocks process, it will take long for launch.
So you can optimise your applicationDidFinishLaunching by moving out some unnecessary code which you can do later. Or you can make your code non-block, for example, you should use asynchronous network requests in applicationDidFinishLaunching instead of synchronous ones.

How to handle existing data when force-closing app on iPhone?

I have loaded some values into local documents in my application. When the application is running app writes, say, 50 records.
Now, when the user force-closes the app and starts it again: The files only write the new records 50+existing 50.
How to handle this issue?
If your application is force-quit completely, you do not have an opportunity to write out additional data. No more of your code is run.
If you are talking about the force quit that happens when your app is in the background and the OS wants to free up memory, you must implement a callback in your application delegate (you can use notifications too but let's keep it simple) and observe when the OS places your app in the background. At that point, write your data to disk:
#implementaton MyAppDelegate
- (void)applicationDidEnterBackground: (UIApplication *)application {
[myDatabase commit];
[myFiles save];
[myWhatever doWhatever];
// etc.
}
#end

NSFileManager: Continue writing to disk in background?

in my iPhone app, I am using the default NSFileManager to copy and move some data around. The amount of data may be some MB, so it may take several seconds to finish copying. If the user now starts a copy process and quits the application, can I continue writing to disk in background mode? If so, how? I do not want to start a new copy process, but only finish the running one.
I am currently using two kinds of method calls:
[imageData writeToFile:path atomically:YES];
and
[fm copyItemAtPath:sourcePath toPath:destinationPath error:&error];
Both calls are wrapped in another method and are performed in the background via -performSelectorInBackground:withObject:.
Another question is if I can get any information on how far the writing operation has progressed or at least if it has already finished?
EDIT: Some more information:
Currently, I did not implement any background tasks at all. What happens to running disk operations when the user presses the home button? Are they just cut off and the file is incomplete? Can I continue writing on the next start? Are they canceled and the incomplete file is removed?
To sum it all up: I am writing data to disk and I want it to stay consistent when the user presses the home button during a writing operation. How do I achieve this?
By default you app does not really finishes when the user press the home button. Hence it should finish the that task as long is does not take too long. If it takes long then please take a look at this question: How to implement Task completion
One thing: I think you are confused about what performSelectorInBackground:withObject: really does.
The background used in "... I continue writing to disk in background mode" and the background in "performSelectorIn Background :withObject: " are not the same background
The former background:
Is when you app becomes invisible to the user, but is still running, at least for a while. (When the user presses twice the home button and change to another app)
The latter background:
Refers to a background thread, which is the opposite to the main thread.
In this case, if you use or not performSelectorInBackground:withObject: it will have no effect in whether you app can do it background mode or not. These are completely different things
You can set BOOL finished = YES right after [fm copyItemAtPath:ToPath:error:]; and save it in NSUserDefaults and check that flag when your app comes to the foreground again ;)
Hope it helps ;)
You may want to look into NSOperationQueue, the documentation page is here.
Also there are two existing Stack Overflow questions that address using NSOperationQueue that are here and here. Both have accepted answers.
As for figuring out whether a file write has completed, I had this issue as well when I was writing an OS X application that created a very large file. I wanted the user to be able to track the progress of the write. I ended up using an NSTimer along with a UIProgressBar
Essentially, you will want to determine the (expected) total size of the file. Then, as you write the file, you should have another method (in the code below I have checkFileWritingProgress) that you can call at periodic intervals using an NSTimer. Check the current progress against the total expected file size and update the UIProgressBar accordingly.
I have provided some code to get you started.
- (void)checkFileWritingProgress:(NSTimer *)someTimer {
fileAttributes = [fileManager attributesOfItemAtPath:[NSString stringWithFormat:#"%#.data",saveLocation] error:nil];
currentFileSize = [fileAttributes fileSize]; // instance variable
if(currentFileSize < maxFileSize) { // maxFileSize is instance variable
[progressBar setDoubleValue:(((double)currentFileSize/(double)maxFileSize)*100)];
// progressWindows OS X only... not on iOS
//[progressWindow setTitle:[NSString stringWithFormat:#"Writing... | %.0f%%",(((double)currentFileSize/(double)maxFileSize)*100)]];
}
else {
[progressBar setDoubleValue:100.0];
}
}
And the timer...
NSTimer *timer = [[NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(checkFileWritingProgress:)
userInfo:nil
repeats:YES] retain];
//(linked to timer... this is the
// CORRECT way to use a determinate progress bar)
[progressBar setIndeterminate:NO];
[progressBar setDoubleValue:0.0];
[progressBar displayIfNeeded];
I hope this code helps. Let me know if anything needs to be clarified.

What happens to an iPhone app when iPhone goes into stand-by mode?

My app uses NSTimer and it appears that NSTimer doesn't fire when the iPhone goes into the stand-by mode (either by pressing the hardware button or by the idle timer).
When I activate the iPhone again, my app is still in the foreground. What happens to third party apps when the iPhone is the stand-by mode?
Although it's not evident here, I believe the original poster did find an answer to his question by starting a thread (available here) in the iPhone Developer Forums (which I eventually had to find myself because the information wasn't shared here).
In case someone else has the same question and finds the page in the future, here's a helpful response that was posted by someone on the Apple forum called "eskimo1" (which I have edited slightly such that it is easier to read without having the context provided by the entire original thread):
Regarding iPhone app status terminology, "active" does not mean "awake", it means "attached to the GUI". Think of it being analogous to "frontmost" in Mac OS X. When you lock the device your app deactivates but the device may or may not go to sleep
iPhone OS rarely sleeps if the device is connected to main power (i.e., via USB). It can sleep if running on battery, however.
A short time after the screen is locked (20 seconds according to Oliver Drobnik), the device sleeps. This is like closing the lid on your laptop; all activity on the main CPU halts.
This does not happen if the device is playing audio in the right audio session. See DTS Q&A QA1626 "Audio Session - Ensuring audio playback continues when screen is locked" for details.
Note that the idleTimerDisabled property (which can be turned on to prevent the screen from turning off while the app is running) is about locking the screen after user inactivity. It's not directly related to system sleep (it's indirectly related in that the system may sleep shortly after it's locked).
See Application Interruptions in the iPhone OS Programming Guide, especially the applicationWillResignActive and applicationDidBecomeActive events. (The whole guide is certainly worth reading.) When You ignore the events, the timer seems to go on for a while and then stops. Sounds logical, the application could easily drain the battery if kept running. And what exactly happens to the application? I guess it simply does not get any CPU time – it freezes and only thaws when You turn the machine back β€œon.”
My first advice is do not disable the idle timer, that is just a hack. If you want to keep a timer alive during UI events run the timer on the current run loop using NSCommonModes:
// create timer and add it to the current run loop using common modes
self.timer = [NSTimer timerWithTimeInterval:.1 target:self selector:#selector(handleTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
I used the information on this post for a small sample that I was building. This is the code that I used when I initiated the playback to prevent the audio from stopping:
AudioSession.Category = AudioSessionCategory.MediaPlayback;
And when the application is done with the playback to reset to the original value:
AudioSession.Category = AudioSessionCategory.SoloAmbientSound;
The full sample is here:
http://github.com/migueldeicaza/monotouch-samples/tree/master/StreamingAudio/
I was faced with this issue recently in an app I am working on that uses several timers and plays some audio prompts and made two relatively simple changes:
In the AppDelegate I implemented the following methods and there mere presence allows the app to continue when the screen is locked
// this receives the notification when the device is locked
- (void)applicationWillResignActive:(UIApplication *)application
{
}
// this receives the notification that the application is about to become active again
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
}
references: UIApplicationDelegate Protocol Reference & NSApplication Class Reference in the API doc (accessible via Xcode, just search for applicationWillBecomeActive).
Made the main viewcontroller class an AVAudioPlayerDelegate and used this code from Apple's "AddMusic" sample to make the audio alerts the app played mix nicely into the iPod audio etc...
I just dropped this code into a method that is called during viewDidLoad. If this interests you, you fall into the "who should read this doc" category for this: Audio Session Programming Guide
// Registers this class as the delegate of the audio session.
[[AVAudioSession sharedInstance] setDelegate: self];
// The AmbientSound category allows application audio to mix with Media Player
// audio. The category also indicates that application audio should stop playing
// if the Ring/Siilent switch is set to "silent" or the screen locks.
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error: nil];
// Activates the audio session.
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
I believe your application should run normally when suspended. (think Pandora Radio)
Check to see if your timer is being deallocated due to your view being hidden or some other event occurring.