Why would you need a DispatchQueue when showing Alerts on Swift? - ios

I am new to Swift, and trying to examine a finished project. But there is something i couldn't understand.
After a network request is completed, the app show an alert under a condition.
func makeNetworkRequest() {
//newtork result...
DispatchQueue.main.async {
self.showAlert(versionMessage: "Error")
}
}
func showAlert(versionMessage: String) {
let alert = UIAlertView(title: "", message: versionMessage, delegate: self)
alert.show()
}
However, it is done with a DispatchQueue. Why would anyone need to use DispatchQueue in this situation.

It’s a conscious design decision from Apple’s side to not have UIKit
be thread-safe. Making it thread-safe wouldn’t buy you much in terms
of performance; it would in fact make many things slower. And the fact
that UIKit is tied to the main thread makes it very easy to write
concurrent programs and use UIKit. All you have to do is make sure
that calls into UIKit are always made on the main thread. So
according to this the fact that UIKit objects must be accessed on
the main thread is a design decision by apple to favor performance.
for more detailed information you can go through this article
https://www.objc.io/issues/2-concurrency/thread-safe-class-design/
In your case , You are showing alert from another thread so you have to write code under the MainThread so , you can get the main thread using below code
DispatchQueue.main.async {
// Your UI Updation here
}
Reason
In Cocoa Touch, the UIApplication i.e. the instance of your application is attached to the main thread because this thread is created by UIApplicatioMain(), the entry point function of Cocoa Touch. It sets up main event loop, including the application’s run loop, and begins processing events. Application's main event loop receives all the UI events i.e. touch, gestures etc.

You´ll for sure notice that the alert will lag if you don´t show the alert on the main thread, that´s because your UI code does always have to be done on your main thread.
So if you're on a background thread and want to execute code on the main thread, you need to call async(). That´s way you call DispatchQueue.main, which is the main thread.

Related

UI not updating (in Swift) during intensive function on main thread

I wondered if anyone could provide advice on how I can ‘force’ the UI to update during a particularly intensive function (on the main thread) in Swift.
To explain: I am trying to add an ‘import’ feature to my app, which would allow a user to import items from a backup file (could be anything from 1 - 1,000,000 records, say, depending on the size of their backup) which get saved to the app’s CodeData database. This function uses a ‘for in’ loop (to cycle through each record in the backup file), and with each ‘for’ in that loop, the function sends a message to a delegate (a ViewController) to update its UIProgressBar with the progress so the user can see the live progress on the screen. I would normally try to send this intensive function to a background thread, and separately update the UI on the main thread… but this isn't an option because creating those items in the CoreData context has to be done on the main thread (according to Swift’s errors/crashes when I initially tried to do it on a background thread), and I think this therefore is causing the UI to ‘freeze’ and not update live on screen.
A simplified version of the code would be:
class CoreDataManager {
var delegate: ProgressProtocol?
// (dummy) backup file array for purpose of this example, which could contain 100,000's of items
let backUp = [BackUpItem]()
// intensive function containing 'for in' loop
func processBackUpAndSaveData() {
let totalItems: Float = Float(backUp.count)
var step: Float = 0
for backUpItem in backUp {
// calculate Progress and tell delegate to update the UIProgressView
step += 1
let calculatedProgress = step / totalItems
delegate?.updateProgressBar(progress: calculatedProgress)
// Create the item in CoreData context (which must be done on main thread)
let savedItem = (context: context)
}
// loop is complete, so save the CoreData context
try! context.save()
}
}
// Meanwhile... in the delegate (ViewController) which updates the UIProgressView
class ViewController: UIViewController, ProgressProtocol {
let progressBar = UIProgressView()
// Delegate function which updates the progress bar
func updateProgressBar(progress: Float) {
// Print statement, which shows up correctly in the console during the intensive task
print("Progress being updated to \(progress)")
// Update to the progressBar is instructed, but isn't reflected on the simulator
progressBar.setProgress(progress, animated: false)
}
}
One important thing to note: the print statement in the above code runs fine / as expected, i.e. throughout the long ‘for in’ loop (which could take a minute or two), the console continuously shows all the print statements (showing the increasing progress values), so I know that the delegate ‘updateProgressBar’ function is definitely firing correctly, but the Progress Bar on the screen itself simply isn’t updating / doesn’t change… and I’m assuming it’s because the UI is frozen and hasn’t got ‘time’ (for want of a better word) to reflect the updated progress given the intensity of the main function running.
I am relatively new to coding, so apologies in advance if I ask for clarification on any responses as much of this is new to me. In case it is relevant, I am using Storyboards (as opposed to SwiftUI).
Just really looking for any advice / tips on whether there are any (relatively easy) routes to resolve this and essentially 'force' the UI to update during this intensive task.
You say "...Just really looking for any advice / tips on whether there are any (relatively easy) routes to resolve this and essentially 'force' the UI to update during this intensive task."
No. If you do time-consuming work synchronously on the main thread, you block the main thread, and UI updates will not take effect until your code returns.
You need to figure out how to run your code on a background thread. I haven't worked with CoreData in quite a while. I know it's possible to do CoreData queries on a background thread, but I no longer remember the details. That's what you're going to need to do.
As to your comment about print statements, that makes sense. The Xcode console is separate from your app's run loop, and is able to display output even if your code doesn't return. The app UI can't do that however.

Completion handler being called on background Thread instead of main UI thread in iOS

I have a networking class that does my fetching of data from the server. In the completion handler of that class, it looks something like this:
func fetchData(url: URL, completion: #escaping (Result<Data, MyError>) -> Void) {
let request = URLRequest(url: url)
fetch(request: request) { (result: Result<Data, MyError>) in
switch result {
case .success(let response):
DispatchQueue.main.async {
completion(.success(response))
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
If I call this fetchData method from my ViewController, I get the callback on the main thread and I don't have to reload my collection view on the main thread. I then tried adding a view model for my view controller. So the flow looks more like:
ViewController -> ViewModel (fetchData) -> Networking (fetchData)
where basically each class just calls a method that looks exactly like the above fetchData method, passing the completion upwards. In ViewController, do I need to check again that I'm on the main thread. Could iOS switch threads during these calls? I ask because I did get a warning about updating the UI was not called on the main thread one time. But I'm not sure if that was a false negative from this call since I have other networking calls to fetch images, and maybe I messed something else up elsewhere. But basically, I'm just asking if I don't do any other GCD type tasks, but only use completion handlers and bubble up the completion from the single networking call that calls back on the main thread, do I need to check again somewhere up the chain (like in the ViewController).
You haven't provided the code fo "these calls", so it isn't possible to say whether code will be dispatched on another queue, however, the system doesn't arbitrarily switch to another queue while executing code. You need to explicitly or implicitly dispatch onto another queue. Your code above contains an explicit dispatch onto the main queue and an implicit dispatch onto another queue when you call fetch (Somewhere in that code will be an implicit dispatch onto another queue, perhaps in code where you can't see the source).
As a simple answer to your question, if you dispatch onto the main queue in the completion handler shown and none of the other code called "further up" performs asynchronous work or explicitly dispatches onto a queue other than the main queue you can be certain that execution will continue on the main queue.
Also, you can simplify your code by simply calling the upstream completion handler directly:
func fetchData(url: URL, completion: #escaping (Result<Data, MyError>) -> Void) {
let request = URLRequest(url: url)
fetch(request: request) { (result: Result<Data, MyError>) in
DispatchQueue.main.async {
completion(response))
}
}
}
When designing your code you should adopt one of two approaches and stick to it:
Dispatch onto the main queue early. This approach is often taken by frameworks that may well be consumed by someone else; For example AFNetworking explicitly documents that completion handlers are dispatched onto the main queue so you don't need to worry about it. The disadvantage of this approach is that programmers may not read the documentation and may dispatch onto the main queue defensively, leading to double asynchronous dispatch or they may not be updating the UI and don't need main thread execution. This is an overhead but unlikely to be a major issue.
Never dispatch onto the main queue and rely on the calling code to dispatch if it needs to do so. This approach may be more common where all of the code is part of one solution and the programmer "knows" that they ultimately need to dispatch onto the main queue. The advantage of this approach is that you defer (and potentially avoid entirely if it isn't required) dispatching work to the main queue. The disadvantage is that if you forget to do it you will get warnings and main thread violations
if you're talking about this:
func fetchData(url: URL) { result in
print(result) // <-- This on main thread and should not cause any warnings
}
If you're certain that's what's happening then it's a false positive. But I highly doubt it. I've never seen it malfunction. You can easily use the Main Thread Checker and detect mistakes.
Aside from that normally functions shouldn't dictate the completionHandler's thread. ie it's on the caller to dispatch the thread. I mean if you ever wanted to dispatch this to another thread, then you'd be dispatching it twice which isn't ideal.

How do I ensure my DispatchQueue executes some code on the main thread specifically?

I have a singleton that manages an array. This singleton can be accessed from multiple threads, so it has its own internal DispatchQueue to manage read/write access across threads. For simplicity we'll say it's a serial queue.
There comes a time where the singleton will be reading from the array and updating the UI. How do I handle this?
Which thread my internal dispatch queue is not known, right? It's just an implementation detail I'm to not worry about? In most cases this seems fine, but in this one specific function I need to be sure it uses the main thread.
Is it okay to do something along the lines of:
myDispatchQueue.sync { // Synchronize with internal queue to ensure no writes/reads happen at the same time
DispatchQueue.main.async { // Ensure that it's executed on the main thread
for item in internalArray {
// Pretend internalArray is an array of strings
someLabel.text = item
}
}
}
So my questions are:
Is that okay? It seems weird/wrong to be nesting dispatch queues. Is there a better way? Maybe something like myDispatchQueue.sync(forceMainThread: true) { ... }?
If I DID NOT use DispatchQueue.main.async { ... }, and I called the function from the main thread, could I be sure that my internal dispatch queue will execute it on the same (main) thread as what called it? Or is that also an "implementation detail" where it could be, but it could also be called on a background thread?
Basically I'm confused that threads seem like an implementation detail you're not supposed to worry about with queues, but what happens on the odd chance when you DO need to worry?
Simple example code:
class LabelUpdater {
static let shared = LabelUpdater()
var strings: [String] = []
private let dispatchQueue: dispatchQueue
private init {
dispatchQueue = DispatchQueue(label: "com.sample.me.LabelUpdaterQueue")
super.init()
}
func add(string: String) {
dispatchQueue.sync {
strings.append(string)
}
}
// Assume for sake of example that `labels` is always same array length as `strings`
func updateLabels(_ labels: [UILabel]) {
// Execute in the queue so that no read/write can occur at the same time.
dispatchQueue.sync {
// How do I know this will be on the main thread? Can I ensure it?
for (index, label) in labels.enumerated() {
label.text = strings[index]
}
}
}
}
Yes, you can nest a dispatch to one queue inside a dispatch to another queue. We frequently do so.
But be very careful. Just wrapping an asynchronous dispatch to the main queue with a dispatch from your synchronizing queue is insufficient. Your first example is not thread safe. That array that you are accessing from the main thread might be mutating from your synchronization queue:
This is a race condition because you potentially have multiple threads (your synchronization queue’s thread and the main thread) interacting with the same collection. Rather than having your dispatched block to the main queue just interact objects directly, you should make a copy of of it, and that’s what you reference inside the dispatch to the main queue.
For example, you might want to do the following:
func process(completion: #escaping (String) -> Void) {
syncQueue.sync {
let result = ... // note, this runs on thread associated with `syncQueue` ...
DispatchQueue.main.async {
completion(result) // ... but this runs on the main thread
}
}
}
That ensures that the main queue is not interacting with any internal properties of this class, but rather just the result that was created in this closure passed to syncQueue.
Note, all of this is unrelated to it being a singleton. But since you brought up the topic, I’d advise against singletons for model data. It’s fine for sinks, stateless controllers, and the like, but not generally advised for model data.
I’d definitely discourage the practice of initiating UI controls updates directly from the singleton. I’d be inclined to provide these methods completion handler closures, and let the caller take care of the resulting UI updates. Sure, if you want to dispatch the closure to the main queue (as a convenience, common in many third party API), that’s fine. But the singleton shouldn’t be reaching in and update UI controls itself.
I’m assuming you did all of this just for illustrative purposes, but I added this word of caution to future readers who might not appreciate these concerns.
Try using OperationQueues(Operations) as they do have states:
isReady: It’s prepared to start
isExecuting: The task is currently running
isFinished: Once the process is completed
isCancelled: The task canceled
Operation Queues benefits:
Determining Execution Order
observe their states
Canceling Operations
Operations can be paused, resumed, and cancelled. Once you dispatch a
task using Grand Central Dispatch, you no longer have control or
insight into the execution of that task. The NSOperation API is more
flexible in that respect, giving the developer control over the
operation’s life cycle
https://developer.apple.com/documentation/foundation/operationqueue
https://medium.com/#aliakhtar_16369/concurrency-in-swift-operations-and-operation-queue-part-3-a108fbe27d61

Completion block thread

I have this piece of code:
[[FBController sharedController] getUserDetailsWithCompletionBlock:^(NSDictionary *details)
{
// Updating UI elements
}];
I don't understand a thing: when the block is fired, the secondary thread is still running. Isn't it more correct that the completion of a block should be executed on main thread automatically?
I know that I am wrong with something and I need a couple of explanations.
The Facebook SDK documentation should give you more details, but in general a well-behaved SDK would call completion blocks on the same thread that the SDK was called from. Any long-running or asynchronous operations that the SDK may perform should operate on a separate thread, usually only visible to the SDK. Whether or not that separate thread is still running or not, is an implementation detail of the SDK - and you shouldn't care about it from the client code perspective.
You can visualise it like this:
Client Code (Main Thread) : [Request]--[Response]-[Continue Thread]-------[Completion Block]
v ^ ^
SDK Code (Main Thread) : [Immediate Operations] |
v |
SDK Code (Private Thread) : [Long Running / Asynchronous Operations]----[Finished]
In the specific example you posted, there's no 'Response' from the getUserDetailsWithCompletionBlock method, so the thread carries on as usual.
The missing piece to the jigsaw puzzle might be - "How does my completion block get executed on the main thread". Essentially this comes down to the Runloop system. Your main thread isn't actually owned and operated by your code, it's behind the scenes. There's a Main Runloop which periodically looks for things to do. When there's something to do, it operates those somethings on the main thread sequentially. When those somethings have finished, it goes back to looking for something else to do. The SDK basically adds your completion block to the main runloop, so the next time it fires, your block is there waiting to be executed.
Other things that the runloop might be doing are:
UI Updates
Delegate callbacks from UI code
Handling Timers
Touch Handling
etc... etc...

iOS: Is [UIApplication schedulelocalnotification] and related local notification manipulating methods thread safe?

My App sometimes need to schedule almost 64 local notifications, which will block my main thread for almost 1 seconde on iPhone4.
I want to do this on a separated thread, is these local notification manipulating methods of UIApplcation thread safe?
dont think so as the docs dont explicitly state it and UIKit in general in large parts isnt thread safe
but it would be worth a try :D the main thread is only a dispatch_async away ;)
--- maybe it would be an option to schedule them individually and run the main loop in between
There are two things in play, thread safety and calling UIKit from background threads. Some UIKit code doesn’t like to be called from a background thread at all and will throw an exception if you attempt to do so (like setting a new content for a UITextView). In other words, there’s something like this in the code:
NSParameterAssert([NSThread isMainThread],
#"This method must be called from the main thread.");
Then comes the thread safety, ie. if the code can be called from a background thread, it might still be written in a way that may result in a bug when you do so:
- (void) doA {
for (id item in allItemsArray) {
// do something
}
}
- (void) doB {
[allItemsArray addObject:#"foo"];
}
Now if one thread calls -doA and another thread calls -doB in the meantime, your app would crash with an exception because you changed the allItemsArray while enumerating it.
So the first question is if the notification methods can be called on a background thread. I’d say they can. In that case you can simply schedule all your notification from a background queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i=0; i<64; i++) {
// schedule notification
}
});
You don’t need to care about thread safety, unless there’s another part of your app scheduling other local notifications in the meantime. If there is, you can either create a separate queue to serialize all the notification calling code, or you have to be sure that the methods are thread-safe indeed (in which case I have no authoritative resource to offer).

Resources