I was wondering how someone would load multiple UI elements asynchronously. In my case, I have a UIViewController that has a UISegmentControl. Each segment has different UI aspects to load.
For example seg1 shows one image, seg2 shows and image and some text, etc.
If I load all the UI elements before showing the UIViewController then there is a noticeable lag. So I would want to load seg2...n asynchronously to make the UI feel more responsive.
Would I need to load everything via
DispatchQueue.main.async {
// load UI
}
For each seg I want to load in the background? Or can I load these elements on another thread and not take up the main thread? I know you're not supposed to update the UI on background threads... But will using the main thread still block the UI if I use async code?
You can't make UI changes on a background thread. Nearly all UIKit calls must be made on the main thread. Therefore you're likely out of luck.
Instead what you should do is do the time consuming number-crunching (downloading and parsing data for example) on a background thread and then use DispatchQueue.main.async() to install the data into your views on the main thread once the time consuming work is done.
If it's the UI setup that is introducing the lag and it's not possible to speed it up then you may be out of luck.
Related
I wonder how the process associated with NSOperation mainQueue doesn’t affect UITableView's scrolling.
The main reason of the sluggish scroll in my code is that each cell loading is slow, because each cell has to fetch images from the server.
That’s why I used to create another thread using GCD to handle the fetching process given by each cell. Meanwhile, the main thread handles exclusively tableview’s scroll, which is what I've understood to ensure fast scrolling.
But, in recent days, I’ve noticed a sample code is using NSOperation.
I imitated the part so that the tableview cell is loaded using NSOperation mainQueue.
I expected it would tax the tableview’s scroll performance, because the main thread handles both cell loading and tableview scrolling as I understand.
But, surprisingly, it doesn’t. The performance of both cell loading and tableview’s scrolling is perfect.
I still wonder how this can be.
I guess I’m either mistaken or I miss something.
Would you let me know what it is?
All iOS UI animations (including scrolling) are performed on separate UI thread. So as long as UITableView has all required data (i.e. you provide empty cell until image is loaded) it won't be blocked by any sort of main thread activity.
Execution of the animations occurs on a secondary thread so as to
avoid blocking the current thread or your application’s main thread.
https://developer.apple.com/.../AnimatingViews.html
The iOS application's main thread consists of two elements:
• The Run Loop
• The Main Queue
The main dispatch queue is a globally available serial queue that
executes tasks on the application’s main thread. This queue works with
the application’s run loop (if one is present) to interleave the
execution of queued tasks with the execution of other event sources
attached to the run loop. Because it runs on your application’s main
thread, the main queue is often used as a key synchronization point
for an application.
Source
The code you have in your View Controllers is ran on the run loop (not sure about this one). That's why you're not allowed to execute any long lasting tasks.
You can though, delegate these tasks to the Main Queue (and that's what you're doing). When you do that, the OS interleaves the execution between the run loop and the main queue (loading the images). That's why scrolling the view is still pretty smooth.
Why don't we do all async tasks on the main queue then? you might ask. Because the main queue has to share thread processor time with the run loop, operations will be executed slower than on a background thread. It's not something that you can observe in that example, though.
How would I constantly refresh a background image of my UIImageView, while also still listening for touches and input? What I am looking for is the iOS Objective C equivalent (in java) of creating a new thread apart from the main thread, and having that thread be devoted to updating the background picture as fast as possible. Thanks!
It depends on where the new images are coming from. If they are a cycle of known existing images in your app bundle, just make this an animated image view (or an animated image) and the cycle will happen automatically.
If you need to run code e.g. to get out to the network periodically, then just start a repeating NSTimer. It calls you on the main thread, but your main thread code will be very brief indeed, and networking takes place asynchronously (unless you mess that up deliberately). Just make sure that when you actually set the image, you step out to the main thread, as you must never touch the interface in any way except on the main thread.
I have an iOS application that has an NSTimer which fires every 5 seconds. This then posts a notification, telling several controllers that they must now recalculate some data and then update their UI.
When this all happens on the main thread, scrollviews can become jittery as the data is processed.
Using GCD, I have wrapped the code called when a notification is posted:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Code here
dispatch_async(dispatch_get_main_queue(), ^(){
// Reload tableviews and UI
};
};
Unfortunately, this leads in certain cases to several threads accessing the same instance variables. This causes my application to crash as sometimes it ends up mutating an array being enumerated in another thread.
Wrapping the code in a synchronised block prevents this.
When the user scrolls the tableview, several methods are called to calculate the height etc, on the main thread. The code within the background thread is working on the code needed by the main thread. As such, this can usually further cause crashes
I have tried setting the variables to 'atomic', but this doesn't solve the issue. Ideally, I don't want to have the main thread waiting on the background thread, but I am not sure how to best resolve this issue given that they need the same data?
This is pretty classic multithreaded programming issues. There are a number of ways to solve it with basic locks (#synchronized blocks), reader/writer locks, etc but the problem is often that you can't control when the user is going to scroll or take other action. If you #synchronize, you have to do it anywhere that data is touched, including your UITableView data source methods. That can lead to stuttering if the background processing happens to be in the middle of something.**
Personally, I would use an immutable snapshot mechanism.
Have the background thread produce the results, then include just the data the UI needs to display in the notification data as an immutable snapshot (copy). That way the background thread never modifies the data the UI is currently reading for display. How you would implement this is highly dependent on how much data you are talking about and the form it takes, but the safe way would be to have copies of your classes with readonly properties. Alternatively, you can use a "frozen" flag. Make a copy, then set frozen = YES on the copy. The UI thread will only ever see "frozen" or readonly objects coming from the background thread.
The benefit is the UI never causes the background thread to stall and there are no locks required. The downside is increased memory usage, though if the amount of data is large you can use copy-on-write mechanisms to allow the background thread and UI thread to share the data, even though logically the UI thread has a completely separate copy.
** Note: In most applications, you don't have this sort of continuous background processing going on so those apps can use simpler mechanisms. This is typically a form of message passing where the background thread finishes its task and "passes the message" to the UI thread (passes the results). At that point the background work is finished so there is no concurrent modification happening.
I have a UITextField, which checks a password and then my app loads data from a remote server. While this is happening I would like a progress view to display the progress of the download. My issue is that the UITextField seems to lock up the display until it returns and therefore I cannot update the progress view on the main thread. I also would like an answer without GCD or any other kind of threading as I am using core data and that would probably overcomplicate the app. Is there a way to have the UITextField not lock up the view so that I can update my progressView on the main thread?
If your app is loading data from a remote server, then you will have to use multi-threading(GCD, etc). Otherwise it just locks up the main thread till the download is finished which makes your app unresponsive.
To keep it simple, use GCD to fetch data(raw NSData) and give it to the main thread. Do all your processing on the main thread(core data, etc) as usual.
EDIT: One more thing, it is not the textfield locking up your UI, it is the download. So I don't think you can do anything other than multi-threading to help you here.
I am doing one application in which i got one HTML string in background thread. I want to load the webview using that HTML string in background.
If I load that web view on background, the app crashes. I don't want to load webview using main thread because on that i don't want to disturb the main thread. And i did the R&D in internet i got one possibility using GCD.I think that one also involved in main thread. SO please let me know how to update the UI in background.
You can not. UI must always be updated from the main thread. Whatever your reason for not wanting to do it from the main thread, that reason is invalid.
No. You cant update UI in other thread then main.