When using NSURLConnection, you had the option to schedule the connection using NSRunLoop:
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
Passing NSDefaultRunLoopMode would effectively cause the connection to pause if the user scrolled, which was great for performance as the user experience was never impacted by the download.
Is there a way to get similar behaviour for NSURLSession? I have read through the docs and tried various ways of configuring the session with no success.
NSURLSession works at an "upper" level, and is made to be more simple for the developer than using NSURLConnection.
I made some tests and I think that there is no possibility to control the runloop and the mode of NSURLSession, because they seems to be managed by an external daemon and not by your App (I tested only with NSURLSessionDownloadTask).
Do this simple test:
download and execute this Github project
start a download
open the "downloads" controller to see the state of your download
pause the app
wait a moment
unpause the app
you will see that the download has been continued while your app was paused, so when you start a NSURLSession the control is passed to the system, outside your app: it means that the main part of the work doesn't take place in an internal runLoop.
The only thing you have control on, is the serial queue on which the delegate calls are dispatched (passed back to your application). The delegate calls are queued for execution (on the main or on a background thread, you can choose it), since NSOperationQueue use Grand Central Dispatch to queue the calls, I'm not sure about the runloop mode used for this, but I think this is a good starting point to continue your researches.
EDIT:
If I remember correctly, dispatch calls made on background threads are made on threads where a runloop is not running. In fact, if you add this line
NSLog(#"%#", [[NSRunLoop currentRunLoop] currentMode]);
on one of the delegate methods of the FLDownloader class in the previous project, you will see that there is no run mode (nil), this happens when the runloop is not running.
If anyone ever runs into this problem again. You can't schedule the download task into a different run loop but you can handle the response in a different run loop mode which still greatly improves the performance while scrolling.
[self performSelectorOnMainThread:#selector(requestDidFinishLoadingWithData:)
withObject:data
waitUntilDone:YES
modes:#[NSDefaultRunLoopMode]];
Related
Because I am a programmer from Android to iOS. I am quite familiar with Handler Looper MessageQueue etc. in Android. But I am a little confused with iOS when I find a code block [NSURLConnection start] called in main thread. Luckily I read NSDefaultRunLoopMode vs NSRunLoopCommonModes and Does NSURLConnection block the main thread?. I learned network communication can go on in main thread for iOS with the help of modes concept.
Now I have a another question. As we know UITracking mode have a high priority, but if the current moment is doing network job in default mode, then UI event happens and network job is not finished, the corresponding code or method execution is not over, how the execution state be processed, saved and restored for OS and how the modes switch well?
And using NSURLConnection asynchronously actually does create another thread, an internal one or not ?
Thanks!
Is is possible in an iOS app to do the following:
Pause execution of a method running on the main thread.
Allow the main thread to continue, completing one loop of the main run loop (or continuing for a specified time period)
Resume execution of the previous paused method
?
I've searched and can't find anything that allows me to do this, but I have a feeling I've seen it done in the past by a programmer I previously worked with.
The motivation for this is the following:
I'm writing a test of a message routing class
The test (a) sends a message and then (b) analyses the outcome to determine if the test passed
The send message is sent using performSelectorOnMainThread:withObject:waitUntilDone:NO
There are 2 ways I can think to resolve this:
Split my test up and use performSelector:withObject:afterDelay:
Specify YES for waitUntilDone when sending the message
Both of these solutions are ok, but 1. complicates the test quite a lot, and 2. changes the messaging system I'm writing tests for, so will have to be carefully considered.
Considering what performSelectorOnMainThread:withObject:waitUntilDone:YES does, it seems like the functionality I'm asking for should be possible (as it's similar in many ways), but is it possible?
This might be what you're looking for:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
This will execute one iteration of the main run loop and then continue on from there. This is kind of abusing the way the run loop is supposed to work though, so you might want to consider a different design - it's almost never a good idea to pause the main thread. Maybe do what you need to do in a separate thread and have that thread call back to a delegate method when it's done, or use a notification.
Use multithreading for this purpose. SENDER on the mainThread, WATCHER on a secondThread, what allows you to start WATCHING before SENDING.
I'm updating some content in my app and I want that to finish up when the user switches out of the app. It seems like I have to stop my currently-running update and start another one in the applicationDidEnterBackground method. It would be much more convenient if I could mark some work as something I want to run in the background before that method is called.
Here's the scenario:
I'm trying to update content and start running a SQL update that takes a bit of time. (More than the five seconds you have to return from applicationDidEnterBackground.)
The user leaves the app. The current update is suspended, but I really want it to finish.
I can start a new update which picks up where the other left off, but if the user switches back into the app I have SQL-level concurrency issues.
Is the only option to break down the SQL queries to smaller batches so I can switch over cleanly in the applicationDidEnterBackground callback? It almost doubles the execution time. (I'm not worried about the OS killing my background task, resume is handled fine.)
Ideally I'd be able to have the existing work continue seamlessly in the background (at the pleasure of the OS), is that possible? Are there better options?
(I've read the Programming Guide's section on executing background tasks.)
You can continue to run your current threads. You don't have to stop any of them and start new one.
The only thing which you need to do, if to use beginBackgroundTaskWithExpirationHandler (as proxi mentioned) when you entering background and use endBackgroundTask when you are done. This method gives your application up to 10 minutes of execution. UI of your application won't be accessible (since a user switched to another app), but all threads of your app will continue to run. System will pause all threads when your will do endBackgroundTask or 10 minutes will expire.
I would organize it like this
Have you processing threads running
In applicationDidEnterBackground call beginBackgroundTaskWithExpirationHandler.
Save UIBackgroundTaskIdentifier somewhere accessbile.
At the end of your processing thread, check whether UIBackgroundTaskIdentifier isn't 0 and if it's not, call endBackgroundTask. Set UIBackgroundTaskIdentifier to zero.
If I understand right, you just have to wrap your long-running operation into beginBackgroundTaskWithExpirationHandler block. See the method's documentation for details on how to use it.
I am trying to understand multi-threading on iOS in more detail. I went through some of the class references like NSThread, NSRunLoop, NSTask..
First of all as indicated on the following link:
use of runloop
Runloop runs within a Thread.
So why do we need to define our own Runloop in our app? In the case of NSThread it is useful because some of time-consuming processes can run in a separate thread so that the app will still be responsive on the main thread.
Interacting with the thread's run loop may be useful if you have a thread whose work you want to continue periodically. That is, a run loop would do some work, and then when it is finished with that work, it would put the thread to rest for some time, then resume work at a later time -- effectively preventing the thread from exiting. You won't need to interact with them or configure/create them yourself regularly (only a small percentage of apps would qualify, if you are using high level abstractions such as Foundation because Foundation would set them up on your behalf in most scenarios).
If your secondary thread just does a specified task and does not need to wait for some external event (e.g. a download to finish), you would (typically) not need to interact with the run loop.
You might consider looking at using NSOperationQueues, NSOperations and NSBlockOperations instead as these will manage themselves, will allow for cancellation of tasks and can be scheduled on main and background threads.
I am working on an app, which uploads native contacts to server then get responses(JSON, a contact list that already installed the app). When native contacts are large enough, server response will be slow and unstable. And user cannot do other things. so I put network request into background thread. every time I will upload 100 contacts, do some tasks , then next 100 contacts until loop finish.
But in running, the result is not as expected. background thread is running, it keeps to request server. UI thread is blocked, I still cannot do anything.
is this cause a long loop in background thread? Although I have 2 thread, but they will compete CPU resources(test device is iPod, 1 core. And I think this may not related core numbers)?
Could anyone tell me hints on how to handle this kind of scenario? Thanks in advance!
Update:
I have found the root cause. A global variable in App delegate is set to wrong value, therefore UI behavior is weird. I found this by comment all network request method. So this problem is not related with multiple threading. Sorry for the bother.
I think there needs to be some clarification as to how you are performing the network operations.
1st, NSOperatiomQueue deals with NSOperations, so you are presumably wrapping your network code in an NSOperation subclass.
2nd, are you using NSURLConnections for your networking code?
3rd, is the blocking part the NSURLConnection or you delegate callback for NSURLConnection?
1 thing to note is that plain ol' NSURLConnections are implemented under the hood multithreaded. The object is placed into your main threads run loop by default (when run from the main thread), but the object is just a wrapper that handles callbacks to the delegate from the lower level networking code (BSD sockets) which happens on another thread.
You really shouldn't be able to block your UI with NSURLConnections on the main thread, unless A) you are blocking the thread with expensive code in the delegate callback methods or B) you are overwhelming your run loop with too many simultaneous URL connections (which is where NSOperationQueue's setMaxConcurrentOperationsCount: comes into play)