I have a dataTask making a webservice API call, and in the completion block for that dataTask I use the returned info to build a UIImageView and add it as a subview of the parent UIViewController. I have tried it with two API calls and I would like to know why one causes the UIImageView to be displayed so much slower than the other. Inside of the completion block, the fast call, which takes less than 1 second, is:
dispatch_async(dispatch_get_main_queue(), {
(self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
});
while the slower call, which takes roughly 13 seconds, is:
(self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
The first clearly runs on the main queue, but the second one I'm not so sure, since it's running directly in the completion block of the dataTask.
Can someone explain in detail why these two calls of the same function have such markedly different run lengths?
As these calls update the UI ,your first call is on main thread which updates your imageView immediately.But your second call directly within dataTask completion handler which generally runs on secondary thread run loops so this call
(self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
run on secondary thread runloop so it will not able to update UI. While some other call or some other event reloads the UIImageView which display the your computed image to display on UIImageView.
So as dataTask fetch data on secondary thread if your call is directly within completion handler than it is updating your imageView on secondary thread which should not be done as all UI must be update on main thread.Put your imageView updation on main thread.
Related
When I present a view controller or perform a segue in swift should I call it in DispatchQueue.main.async {} or is that a problem. My problem is that should I run it in the background thread or the main thread. If I load data from the database should I also present the view in DispatchQueue.main.async {} or should I run it in the background thread.
You should call all the UI related transition, changes and updates on the main thread. But where should you use DispatchQueue.main.async {} ?
It is to be used when the call is made from a background thread. Example,if you are downloading data and parsing from an API, you usually do that in a background thread, once that is completed, maybe you want a UI transition or update, So that update will take place in the main thread, and since currently you are on background thread, you require DispatchQueue.main.async {} to make the changes on UI.
All UI work should happen on the main thread, so unless you're on a background queue there is no reason to dispatch to the main queue. In the common case the segue is triggered from user interaction (e.g. a button press) which would already be on the main thread.
If loading the data for the view that is being presented takes a long time, then you can dispatch asynchronously to a background queue to load it and dispatch back to the main queue when the data has finished loading.
Since this is asynchronous, it will mean that the view will be without data from the time where it is presented to the time where the data has finished loading. This is something that you have to handle in your UI. Depending on your application, one example could be to displayed a loading indicator while the data is loading. Another could be to fetch or pass a minimal amount of data to display and load the larger amount of data asynchronously.
I'm calling a method which belongs to custom delegate class on viewDidLoad but it starts from [sampleProtocol startSampleProcess], starts from sleep(5), before it show me view controller and label1.
CustomDelegate *sampleProtocol = [[CustomDelegate alloc]init];
sampleProtocol.delegate = self;
[self.label1 setText:#"Processing..."];
[sampleProtocol startSampleProcess];
startSampleProcess method is below;
-(void)startSampleProcess{
sleep(5);
[self.delegate processCompleted];
}
processCompleted method is also below;
-(void)processCompleted{
[self.label1 setText:#"Process Completed"];
}
It just set a label on viewcontroller, go to another class and do something simple (etc: sleep) and come back to view controller and set label again. I didn't try custom delegate before so it would be great if you help me on what I'm missing.
The problem is that you are calling sleep on the main thread.
Here's how an iOS app works:
Wait until something interesting happens.
Process it.
Go back to step 1.
The app has something called a runloop that receives messages from the system about touches, timers, etc. Every time it gets a message, it runs some code, often provided by you. When you call the sleep function, it suspends the current thread. When the thread is suspended, the run loop can't process new events until the sleep is done.
When you change something onscreen, you add an event to the run loop that says the screen needs to be redrawn. So, this is what is happening in your application:
You change the label text. A redraw event is now added to the runloop.
You sleep for 5 seconds, meaning the runloop can't process new events.
5 seconds later, the thread wakes up and changes the label's text.
Control finally gets back to the run loop.
The run loop processes the redraw event, changing the label's text.
If the task needs to be a long-running task, you can do it in a background thread:
-(void)startSampleProcess {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0) ^{ //run this code in the background so it doesn't block the runloop
sleep(5);
dispatch_async(dispatch_get_main_thread(), ^{ //after the task is done, call the delegate function back on the main thread since UI updates need to be done on the main thread
[self.delegate processCompleted];
});
});
}
How do I get the UIActivityIndicatorView to display first, then execute other code?
I've experimented with using sleep, and it works but it doesn't "feel" right and adds an extra second to processing a bunch of core data stuff. I've also tried dispatching it to the main thread which only works some of the time. (I'm guessing when the rest of the block is executed outside of the main thread).
Ideally as soon as a user touches the button the instance of the UIActivityIndicatorView would display (which seems to happen where I've used it in other apps by itself or with other minimal processing).
Details: I have an IBAction connected to a button that executes a bunch of core data stuff, sometimes including images, that takes between 1 - 3 seconds to finish. When it finishes it dismisses the view controller. The view controller where this is executed is presented as a modal over current context.
Here is a code snippet:
// get the background queue
let bg_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(bg_queue, {
// long running code here...
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicator.stopAnimating()
})
})
I have a seemingly simple problem that I cannot for the life of me seem to figure out. In my iOS App, I have a UICollectionView that triggers network operation upon tapping it that can take a few seconds to complete. While the information is being downloaded, I want to display a UIView that fills the cell with a UIActivityIndicatorView that sits in the square until the loading is done, and the segue triggered. The problem is that it never appears. Right now my code looks like:
myLoadView.hidden = NO;
//Network Operation
myLoadView.hidden = YES;
The App simply stops for a couple seconds, and then moves on the the next view. I'd imagine Grand Central Dispatch has somthing to do with the solution, however please keep in mind that this code takes place in prepareForSegue, and the network info needs to be passed to the next View. For this reason not finishing the download before switching scenes has an obvious problem. Any help would be VASTLY appreciated. Thanks!
iOS commits changes in the interfaces after working out a routine. Hence you should perform your network operation in a background thread and then get back back on the main and perform the "show my view now thing". Have a look the below code for reference.
myLoadView.hidden = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Network Operation
dispatch_async(dispatch_get_main_queue(), ^{
myLoadView.hidden = YES;
});
});
Your network operation seems to be carried out on the main thread, aka UI thread. This blocks all further UI calls, including the call to unhide a view, until completion.
To resolve this, make your call asynchronous.
You should read this in full, if you haven't already.
As mentioned by other answers, the problem is that the UIView change doesn't happen until the current method finishes running, which is where you are blocking. Before GCD was available I would split methods in two and use performSelector:withObject:afterDelay (to run the second part also on the UI loop) or performSelectorInBackground:withObject: at the end of the first method. This would commit all the waiting animaations first, then do the actual tasks in the second method.
Well the better option for this type of indication is by using the custom HUD libraries like SVProgressHUD or MBProgressHUD
I have a table view, and when the user selects a row, i push them to a new ViewController. At first, I initialized all my view objects in the ViewDidLoad method (involving web service calls) but I saw that it made the transition from my tableview to my new viewcontroller very long.
Instead, I moved most of my UI initialization in the ViewDidAppear method, and I like that it sped up my transition from tableview to new viewcontroller.
However, I cannot press any buttons in my NavigationBar at the top of the screen (like the back button) until my ViewDidAppear method completes and the UI is loaded.
What's the solution for this? Is there another way for me to load my UI without it preventing the user from interacting with the buttons in my NavigationBar?
Thanks!!
you do too much on the main thread. off load your longer operations like IO or longer computations BUT take care to not mess with the UI in the background thread.
Only touch the UI on the main thread. (Note sometimes it might seem safe, but in the long run it always end up producing weird issues)
one easy way is to use GCD:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//insert web service requests / computations / IO here
dispatch_async(dispatch_get_main_queue(),^{
//back to the main thread for UI Work
});
});
You could use grand central dispatch to make your web service calls asynchronously, which will keep the UI on the main thread responsive.
//create new queue
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.siteName.projectName.bgqueue", NULL);
//run requests in background on new queue
dispatch_async(backgroundQueue, ^{
//insert web service requests here
});
Here's a more in-depth tutorial:
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
Try to initialize your UI in the background by using the following method
[self performSelectorInBackground:#selector(initYourUI) withObject:yourObj];
You can call this in the ViewDidLoad