waiting on a task in iOS - ios

I have what sounded like a really simple issue at first, and I think still is, but I'm missing the answer.
When my app logs in, I start a background task to download some data from the server. If that data isn't downloaded by the time the user taps one of two buttons, I want to put up a spinner view and wait until that task is finished. Once finished, remove the spinner view and then continue to push the view controller.
What I seem to be missing is how to do this and wait so that the main thread isn't blocked.
Once the data is downloaded, I can set a global flag, or send out a notification, but the view controller has to have a way to wait on that condition to either be set, or to know that there was a download error.
Any thoughts?

Stack overflow is not meant to be used in the way you're using it, so you're going to get downvoted.
But, what you are looking for is dispatch_async. You use it to create queues.
If you need more information, this page has a good explanation, as well as this page.

Related

Swift: Gui is not updated / much too late

I am currently programming a Quiz-App for iphone using swift and xcode. Since a week I a stuck with a problem, i wasn't able so solve up to today.
Little Background Information:
All Questions are stored online on a server and are downloaded just in time. The Questions always contain images, so it takes some time to load them. This is why I am pre-loading the next question in background while the user is still thinking about the current question. Therefore I use the class QuestionLoader which extends Thread.
The Problem Situation:
When the user answers a question and presses the continue-button, the function goToNextQuestion() is triggered which shows the next question to the user. Either the pre-loading of the next question is finished and it can be shown instantly or the next question is still loading and we have to wait.
The Problem:
Now if we have to wait, I want to show a Loading-Label and -Indicator. Both are in the gui (placed with interface-builder) and hidden at the time. I do it like this:
func goToNextQuestion()
{
//check if next question is already ready
if (nextQuestionLoader.isFinished==false)
{
//not ready, so wait and show waiting indication
labelLoading.isHidden = false
loadingIndicator.startAnimating()
while(nextQuestionLoader.isFinished == false) {}
//question i now ready, hide waiting indication
labelLoading.isHidden = true
loadingIndicator.stopAnimating()
}
//so next question is ready and all good
}
What I want to happen is that when the user pressed continue and the next question is not ready yet, that the loadingIndicator and the labelLoading are shown until the question is ready.
Now what happens is that continue is pressed the there is nothing shown and the gui is blocked util the question is ready, then it shows the next question and the same thing happens with the following question.
Screenshots of the area of my gui that is concerned for better visualitzation
(Sorry for the different number of points gained in the screenshots, does not have to do with the problem, simply 2 different screenshots)
So the gui update does not happen before the question is loaded completely and does not happen immediately as it should!
Additional Information
I tried the showing of the loading indicators at other points of the code and they show correctly, so there is no problem with this.
When i put print-command before and after the while-loop and follow the output in the terminal, I see that the print-commands are correctly executed before and after the while-loop. But the gui-update does only happen afterwards
The function goToNextQuestion is completely executed in the main-thread!
So how can i achieve that the loading-indication is shown directly and after it is shown, the while-loop waits for the finish of the other thread?
I am really desperate because i have no idea at all how to solve this.
I would really really appreciate some help from you!
Thank you very much in advance for your time!!
There are several problems with your approach.
First, you should not use NSThread/Thread. The system class URLSession handles async downloading for you. (And creating threads is expensive and error-prone.) Use URLSession instead. You should be able to find lots of examples online, including here.
Second, you can't block the main thread like you're doing with your
while(nextQuestionLoader.isFinished == false) {}
Code. Since your download is running on a background thread it will keep running, but by blocking the main thread everything will freeze until the download completes. Your UI changes don't get a chance to be drawn on the screen until the download is complete.
What you should do is use URLSession using either a delegate method or a completion closure. It supports both approaches.
I have a project called Async_demo on Github that includes working code that shows how to use URLSession. It's a simplified example, but it should get you started.

Where to put code that gets checked frequently?

I have some code that needs to get called frequently, such as check what day it is, if it's the next day then move the day strings in the tableView.
Now I thought that the viewDidLoad would get called all the time and so it would be 'fine' to put it in there. However, I've left the simulator overnight, and I've pressed the home button and clicked again, changed VCs etc. and viewDidLoad hasn't been hit.
What are my options for doing sporadic checks such as, is it a new day? As x happened etc.
In this specific case, you can subscribe to NSCalendarDayChangedNotification to be notified when the date changes and respond accordingly in your view controller. In general, didBecomeActive or viewDidAppear would likely work.
What are my options for doing sporadic checks such as, is it a new day
It depends what the meaning of "is" is! In particular, "is" when? You say "sporadic", but that's just fluff. When do you need to know this? To what stimulus do you want to respond? When the user opens your app? Then put it in applicationDidBecomeActive. Every day at noon? Then run an NSTimer. Really, the problem here is that you don't seem to know, yourself, just when you need to perform these checks.
Whilst in your app, its quite easy to continually check for something. You simply create a background thread. However, what you describe is a thread that persists from outside the app's lifecycle.
Have a read on this documentation provided by Apple itself. You have to have good excuse to put a background thread. The scope of such thread is limited to only certain scenarios such as downloading background stuff, playing sounds etc.
For your scenario, I'd look at applicationDidBecomeActive(_:) found in your Application Delegate. There you can mimic such continual check. Beware however, don't put heavy word load on start up or your app might be killed automatically if it fails to become active in reasonable amount of time.

Parse query on Main Thread

My iOS app relies heavy on server side data, and just for the launch of it, I need a little bit of information from Parse to get the job done on the app delegate... the issue is I'm making this query on the main thread because otherwise I would use a block or a queue, and immediately after the start of the app, the launch image shows up, then the query starts and the screen goes blank, then the query arrives and the app screen refreshes and is ready to go, but this looks very odd for the user experience and I don't want it to happen..
With the query on the main thread the launch image stays until the data arrives, and it looks much better and the loading time is about 2-3 seconds...
It feels like a bad practice, but...
Any advices?
Regards,
Miguel Rojas Cortés
Don't block the main thread when the app launches. If the network request isn't fast enough, the watchdog will terminate your app and your users will give you 1 star reviews.
Just display your UI with as much info as you have, and show some visual indication that more data is loading. Then update the views when the data arrives.
Also remember to handle the case that the user launches your app with no connectivity. The user should get an appropriate error and an option to retry.
You just need to do a bit more work here
Create a separate nib / view controller for the launch screen instead of the using the default iOS one
When the launch view controller loaded, starts the request, and don't do any transition just yet. Maybe show some kind of loading indicator there.
When all data has arrived, do a transition to the first screen (either fading smoothly or abruptly, IDK).
Doing query on main thread in this case may work 90% of the time, but the other 10%, eg when network is flaky, it's not a nice experience. The app will just hang there, and you got no chance to handle returned errors, since the main thread is blocked.

Is it possible for your APP network status indicator to be turned on by something other than your own App?

I have couple method that depend on the network status indicator being hidden or not based on my App. I was wondering if it is possible something outside of your App control is able to turn it off or on. Any clarification will be appreciated. I downloaded an App in background and received an email in the background the indicator didn't show.
If you're referring to the networkActivityIndicatorVisible property of UIApplication, I don't believe any outside application will manipulate that property, since it is specific to the running application.
I assume you're asking because of this question, and I would recommend, like others have, not to use this to determine whether a network call has completed or not. I would put the code you want to execute at completion in a callback or delegate, depending on how the call is made. Attaching it to the networkActivityIndicatorVisible property can lead to problems if you have code in the future that shows and hides this, but you don't want this method to execute anymore.

grand central dispatch, operation queues, async, view will and did disappear

Hopefully these questions will seem helpful to people out there. I've been learning objective c, mostly from this book, which I found to be amazing and helpful even for a noob. My questions all have to deal with this:
What happens to the queue when the user changes the view? I can't seem to find a good explanation anywhere.
From my understanding, using the NSOperation and its queue, you can always cancel it using the "cancel"...but what if you don't want it to cancel? What if, say a user selects multiple images to upload to the server, and you create a queue with the order, and the user switches to a new view controller? This might be time consuming, especially on a slow mobile network. I remember reading somewhere that iOS gives around 20 seconds extra time for a method to finish its work, but I think that's only when the app enters to the background.
For the GCD, there is no cancel method...so what happens in the background if you use async? I guess if you don't have a response to the queue, I mean you don't update the UI in any way, shouldn't the queue finish since it's sent to another thread?
I'm sure there is much more that I don't understand as far as threading goes, but I hope I made my question clear. And please please don't tell me to use the AFNetworking stuff...I tried using all those keychain wrappers out there and it all failed. Thanks to the book, the straight Apple code from the book did everything easily. I would rather learn the basics first before using the easier way out.
I would really appreciate if someone took the time to talk about this. Thanks in advance!
Your concern about only having a set amount of time to finish tasks only applies to when you switch away from your app to another app. And in that case you can use the beginBackgroundTaskWithExpirationHandler method so your app can request time to finish those tasks if your app happens to go into background. See the Executing a Finite-Length Task in the Background section of the App States and Multitasking section of the iOS App Programming Guide for more information.
But if you're still within your app (whether you transitioned to another view controller or not), anything you've added to your operation queue will continue to run until you cancel those operations (or the app is suspended or terminated). Likewise, anything you've added to a GCD queues will continue to run until the app is suspended or terminated.
In both of these scenarios, the above beginBackgroundTaskWithExpirationHandler will give you a few minutes to finish your queued tasks/operations after your app goes into the background.

Resources