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.
Related
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.
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 3 UITableViews each when asked for a cell, async loads an image in a NSOperation. This is working perfectly until I insert cells, at which point cellForRow gets called and the operation is created and added to the queue.
The operations in the queue however are not processed for another few seconds, sometimes several seconds. I have another queue that is doing some image processing and saving the image to the disk; I suspect both these queues are competing for disk I/O, but I'm not sure. Ideally, when a image is processed, it should be loaded by the cell next. But it seems like the processing is done first and cells never get a chance to load, even though I experimented with the queue/thread priority. Can anyone with experience give me guidance?
A UIViewController takes about half a second to load its contents and appear on screen. How can I make them all load in the background and appear when they're ready?
There is a LazyTableImages sample on the Apple developer site.
It shows how to perform the heavy lifting in a background thread and update the UI on the main thread.
PerformSelectorInBackground:withObject: is a possible solution, although a more modern method would be to use asynchronous blocks. You can run code on the main thread from within these blocks to update the UI Safely.
The Concurrency Programming Guide is a good place to find more information and examples of this.
A Background Thread cant update the UI,you can perform all the processing logic in background thread and call the main thread for UI update
Example to load a tableView with Data ,use the background thread to process everything and load the Data, call [tableView reloadData] using the main thread, see Grand central Dispatching to know how to Work with Threads in IOS..
Hope it Helps
Create a GCD queue to process your work in a background thread (read the docs, because my "create" label and options may not be what you want).
You send it to the queue asynchronously, meaning that the call to dispatch_async will make appropriate arrangements for the block of code you give it to run in another thread and it will then return back to you "immediately."
All the work in the block you give it will be executed on a separate thread. Note, at the end, you do another call, this time with the well know main queue. This arranges for that code to run in the main thread, which is mandatory for any UI work.
Also, you really should read the documentation on GCD and especially blocks, because there are memory and cycle considerations. Good luck.
dispatch_queue_t workQ = dispatch_queue_create("bgWorkQ", 0);
dispatch_async(workQ, ^{
// This code is now running in a background thread.
// Do all your loading here...
// When ready to touch the UI, you must do that in the main thread...
disptach_async(dispatch_get_main_queue(), ^{
// Now, this code is running in the main thread.
// Update your UI...
});
});
dispatch_release(workQ);
The easiest way is to use NSObject's - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg You just pass it a selector and it will happy in the background and not block your UI. Be aware however that there are rules to background threads.
You shouldn't update your UI from the background thread. And you need to make sure the methods you're calling are thread safe.