Swift threading multiple tasks? - ios

I have this label that is supposed to display a username. Now, I have done quite a bit of IOS developing, however threading is still a bit unclear to me. How would I make sure this code finishes:
User(name: "", email: "", _id: "").getCurrentUser(userId: userId)
Before this gets excecuted?:
self.nameLabel.text = currentUser.name
I have been fumbling with DispatchQueue but I can't seem to figure it out...
Thx in advance!

You can use DispatchGroups to do this, as one solution. Here is an example:
// create a dispatch group
let group = DispatchGroup()
// go "into that group" starting it
group.enter()
// setup what happens when the group is done
group.notify(queue: .main) {
self.nameLabel.text = currentUser.name
}
// go to the async main queue and do primatry work.
DispatchQueue.main.async {
User(name: "", email: "", _id: "").getCurrentUser(userId: userId)
group.leave()
}

Just send a notification in your getCurrentUser() method and add an observer in your UIViewController to update the label.
public extension Notification.Name {
static let userLoaded = Notification.Name("NameSpace.userLoaded")
}
let notification = Notification(name: .userLoaded, object: user, userInfo: nil)
NotificationCenter.default.post(notification)
And in your UIViewController:
NotificationCenter.default.addObserver(
self,
selector: #selector(self.showUser(_:)),
name: .userLoaded,
object: nil)
func showUser(_ notification: NSNotification) {
guard let user = notification.object as? User,
notification.name == .userLoaded else {
return
}
currentUser = user
DispatchQueue.main.async {
self.nameLabel.text = self.currentUser.name
}
}

You have to differentiate synchronous from asynchronous tasks.
Typically, a synchronous task is a task blocking the execution of the program. The next task will not be executed until the previous one finishes.
An asynchronous task is the opposite. Once it is started, the execution passes to the next instruction and you get typically the results from this task with delegating or blocks.
So without more indication, we can't know what exactly getCurrentUser(:) do...
According to Apple :
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system.
It is not necessarily executing work items on background threads. It is just a structure allowing you to execute synchronously or asynchronously work items on queues (it could be the main queue or another one).

Related

Nested dispatch queues not firing inner dispatch queue

I have a pair of nested dispatch queues. For some reason the code inside the second one never gets fired:
dispatchGroupOne.notify(queue: .main) { [weak self] in
// Gets here
self?.dispatchGroupTwo.notify(queue: .main) {
// Doesn't get here
}
}
Even if I don't enter/leave dispatchGroupTwo, it just never will get fired. Why is this? It works when it's not nested:
dispatchGroupOne.notify(queue: .main) {
// Gets here
}
dispatchGroupTwo.notify(queue: .main) {
// Gets here
}
However, I want to specifically perform code only after both of the dispatch groups have been fired.
Without nesting you register the listen separately so every group will act according to it's submitted tasks ,while when you nest them , then the notify of the inner group will depend on the outer one plus whether or not the it's (the inner group) tasks ended/working when it's notify is added upon triggering the outer notify
let dispatchGroupOne = DispatchGroup()
let dispatchGroupTwo = DispatchGroup()
let dispatchGroupThird = DispatchGroup()
dispatchGroupThird.enter()
dispatchGroupOne.notify(queue: .main) {
// Gets here
dispatchGroupThird.leave()
}
dispatchGroupThird.enter()
dispatchGroupTwo.notify(queue: .main) {
// Gets here
dispatchGroupThird.leave()
}
dispatchGroupThird.notify(queue: .main) {
// All groups are done
}

Adding dependency of one BlockOperation on another is not working properly in swift

I have multiple api's in a controller and after successful response I have to reload the UITableView.
For now I started with two api having second api dependency on first one using BlockOperation and DispatchGroup in it.
First in viewDidLoad:
getDataFromAllApis {
self.tableView.reloadData()
}
Then I added the method:
func getDataFromAllApis(completion: #escaping (() -> Void)) {
let queue = OperationQueue()
let getFirstDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getFirstDataFromApi {
group.leave()
}
group.wait()
}
queue.addOperation(getFirstDataOperation)
let getSecondDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getSecondDataFromApi {
group.leave()
}
group.notify(queue: .main) {
completion()
}
}
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation)
}
The problem that I am facing here is getSecondDataOperation executes first and returns to the tableview reload part.
Am I missing something here or there can be a different approach for it? Any help will be appreciated.
I have tried going through this post :
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
You are way overthinking this. Just call the second API from the completion handler of the first API. No operations, no dispatch groups, no nothing.
self.getFirstDataFromApi {
self.getSecondDataFromApi {
// call the completion handler
}
}
As for why your code didn't work, it's because you didn't do what the linked answer said to do!
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
It said to do this:
getSecondDataOperation.addDependency(getFirstDataOperation)
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
That isn't what you did. You did this:
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation) // too late
(However, that post, while ingenious, is not what I would do in this situation. If I wanted to sequentialize download operations, I would use the technique described here: https://fluffy.es/download-files-sequentially/. Or, in iOS 13, I'd use the Combine framework, as I describe here: https://stackoverflow.com/a/59889993/341994.)

How to get cancellation state for multiple DispatchWorkItems

Background
I'm implementing a search. Each search query results in one DispatchWorkItem which is then queued for execution. As the user can trigger a new search faster than the previous one can be completed, I'd like to cancel the previous one as soon as I receive a new one.
This is my current setup:
var currentSearchJob: DispatchWorkItem?
let searchJobQueue = DispatchQueue(label: QUEUE_KEY)
func updateSearchResults(for searchController: UISearchController) {
let queryString = searchController.searchBar.text?.lowercased() ?? ""
// if there is already an (older) search job running, cancel it
currentSearchJob?.cancel()
// create a new search job
currentSearchJob = DispatchWorkItem() {
self.filter(queryString: queryString)
}
// start the new job
searchJobQueue.async(execute: currentSearchJob!)
}
Problem
I understand that dispatchWorkItem.cancel() doesn't kill the running task immediately. Instead, I need to check for dispatchWorkItem.isCancelled manually. But how do I get the right dispatchWorkItemobject in this case?
If I were setting currentSearchJob only once, I could simply access that attribute like done in this case. However, this isn't applicable here, because the attribute will be overriden before the filter() method will be finished. How do I know which instance is actually running the code in which I want to check for dispatchWorkItem.isCancelled?
Ideally, I'd like to provide the newly-created DispatchWorkItem as an additional parameter to the filter() method. But that's not possible, because I'll get a Variable used within its own initial value error.
I'm new to Swift, so I hope I'm just missing something. Any help is appreciated very much!
The trick is how to have a dispatched task check if it has been canceled. I'd actually suggest consider OperationQueue approach, rather than using dispatch queues directly.
There are at least two approaches:
Most elegant, IMHO, is to just subclass Operation, passing whatever you want to it in the init method, and performing the work in the main method:
class SearchOperation: Operation {
private var queryString: String
init(queryString: String) {
self.queryString = queryString
super.init()
}
override func main() {
// do something synchronous, periodically checking `isCancelled`
// e.g., for illustrative purposes
print("starting \(queryString)")
for i in 0 ... 10 {
if isCancelled { print("canceled \(queryString)"); return }
print(" \(queryString): \(i)")
heavyWork()
}
print("finished \(queryString)")
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
}
Because that's in an Operation subclass, isCancelled is implicitly referencing itself rather than some ivar, avoiding any confusion about what it's checking. And your "start a new query" code can just say "cancel anything currently on the the relevant operation queue and add a new operation onto that queue":
private var searchQueue: OperationQueue = {
let queue = OperationQueue()
// queue.maxConcurrentOperationCount = 1 // make it serial if you want
queue.name = Bundle.main.bundleIdentifier! + ".backgroundQueue"
return queue
}()
func performSearch(for queryString: String) {
searchQueue.cancelAllOperations()
let operation = SearchOperation(queryString: queryString)
searchQueue.addOperation(operation)
}
I recommend this approach as you end up with a small cohesive object, the operation, that nicely encapsulates a block of work that you want to do, in the spirit of the Single Responsibility Principle.
While the following is less elegant, technically you can also use BlockOperation, which is block-based, but for which which you can decouple the creation of the operation, and the adding of the closure to the operation. Using this technique, you can actually pass a reference to the operation to its own closure:
private weak var lastOperation: Operation?
func performSearch(for queryString: String) {
lastOperation?.cancel()
let operation = BlockOperation()
operation.addExecutionBlock { [weak operation, weak self] in
print("starting \(identifier)")
for i in 0 ... 10 {
if operation?.isCancelled ?? true { print("canceled \(identifier)"); return }
print(" \(identifier): \(i)")
self?.heavyWork()
}
print("finished \(identifier)")
}
searchQueue.addOperation(operation)
lastOperation = operation
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
I only mention this for the sake of completeness. I think the Operation subclass approach is frequently a better design. I'll use BlockOperation for one-off sort of stuff, but as soon as I want more sophisticated cancelation logic, I think the Operation subclass approach is better.
I should also mention that, in addition to more elegant cancelation capabilities, Operation objects offer all sorts of other sophisticated capabilities (e.g. asynchronously manage queue of tasks that are, themselves, asynchronous; constrain degree of concurrency; etc.). This is all beyond the scope of this question.
you wrote
Ideally, I'd like to provide the newly-created DispatchWorkItem as an
additional parameter
you are wrong, to be able to cancel running task, you need a reference to it, not to the next which is ready to dispatch.
cancel() doesn't cancel running task, it only set internal "isCancel" flag by the thread-safe way, or remove the task from the queue before execution. Once executed, checking isCancel give you a chance to finish the job (early return).
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .background)
let prq = DispatchQueue(label: "print.queue")
var task: DispatchWorkItem?
func work(task: DispatchWorkItem?) {
sleep(1)
var d = Date()
if task?.isCancelled ?? true {
prq.async {
print("cancelled", d)
}
return
}
sleep(3)
d = Date()
prq.async {
print("finished", d)
}
}
for _ in 0..<3 {
task?.cancel()
let item = DispatchWorkItem {
work(task: task)
}
item.notify(queue: prq) {
print("done")
}
queue.asyncAfter(deadline: .now() + 0.5, execute: item)
task = item
sleep(1) // comment this line
}
in this example, only the very last job is really fully executed
cancelled 2018-12-17 23:49:13 +0000
done
cancelled 2018-12-17 23:49:14 +0000
done
finished 2018-12-17 23:49:18 +0000
done
try to comment the last line and it prints
done
done
finished 2018-12-18 00:07:28 +0000
done
the difference is, that first two execution never happened. (were removed from the dispatch queue before execution)

Notifying main UI thread from background thread in Swift 4

I'm doing some communication work on a background thread, which I start like so:
self.thread = Thread(target: self, selector: #selector(threadMain), object: nil)
self.thread?.start()
...
func threadMain() {
...
}
threadMain is invoked correctly and the processing rolls as it should. The last code sequence on threadMain is the notification of the main thread via externally provided callback "onComplete", which I wanted to do like so:
print("leaving thread")
DispatchQueue.main.async {
self.onComplete?(CommunicationCallbackParams(errorCode:
self.errorCode, commState: self.commState, redirectUrl: self.redirectUrl))
}
However, the code inside the closure is never called. If I remove the "DispatchQueue.main.async" wrap it works, but I notify on non-UI-thread level. What might go wrong here?
The same principle is working fine in Objective C...
The most obvious thing is that you're calling self.onComplete with optional chaining. If onComplete is nil, that code won't get called. However, you say that it does get called if you don't wrap it in a call to DispatchQueue.main.async.
Oh, found the solution. I was calling the class from XCTest using a semaphore in order to wait for the result:
let semaphore = DispatchSemaphore.init(value:0)
let _ = Communication(url: "<some url>") { (result) in
print("Current thread is main thread: \(Thread.isMainThread)")
print(result)
semaphore.signal()
}
semaphore.wait()
That semaphore pattern must have blocked the notification. Once I changed it to expectations, it worked fine.
let exp = expectation(description: "\(#function)\(#line)")
let _ = Communication(url: "<some url>") { (result) in
print("Current thread is main thread: \(Thread.isMainThread)")
print(result)
exp.fulfill()
}
waitForExpectations(timeout: 60, handler: nil)

API calls blocks UI thread Swift

I need to sync web database in my coredata, for which I perform service api calls. I am using Alamofire with Swift 3. There are 23 api calls, giving nearly 24k rows in different coredata entities.
My problem: These api calls blocks UI for a minute, which is a long time for a user to wait.
I tried using DispatchQueue and performing the task in background thread, though nothing worked. This is how I tried :
let dataQueue = DispatchQueue.init(label: "com.app.dataSyncQueue")
dataQueue.async {
DataSyncController().performStateSyncAPICall()
DataSyncController().performRegionSyncAPICall()
DataSyncController().performStateRegionSyncAPICall()
DataSyncController().performBuildingRegionSyncAPICall()
PriceSyncController().performBasicPriceSyncAPICall()
PriceSyncController().performHeightCostSyncAPICall()
// Apis which will be used in later screens are called in background
self.performSelector(inBackground: #selector(self.performBackgroundTask), with: nil)
}
An API call from DataSyncController:
func performStateSyncAPICall() -> Void {
DataSyncRequestManager.fetchStatesDataWithCompletionBlock {
success, response, error in
self.apiManager.didStatesApiComplete = true
}
}
DataSyncRequestManager Code:
static func fetchStatesDataWithCompletionBlock(block:#escaping requestCompletionBlock) {
if appDelegate.isNetworkAvailable {
Util.setAPIStatus(key: kStateApiStatus, with: kInProgress)
DataSyncingInterface().performStateSyncingWith(request:DataSyncRequest().createStateSyncingRequest() , withCompletionBlock: block)
} else {
//TODO: show network failure error
}
}
DataSyncingInterface Code:
func performStateSyncingWith(request:Request, withCompletionBlock block:#escaping requestCompletionBlock)
{
self.interfaceBlock = block
let apiurl = NetworkHttpClient.getBaseUrl() + request.urlPath!
Alamofire.request(apiurl, parameters: request.getParams(), encoding: URLEncoding.default).responseJSON { response in
guard response.result.isSuccess else {
block(false, "error", nil )
return
}
guard let responseValue = response.result.value else {
block (false, "error", nil)
return
}
block(true, responseValue, nil)
}
}
I know many similar questions have been already posted on Stackoverflow and mostly it is suggested to use GCD or Operation Queue, though trying DispatchQueues didn't work for me.
Am I doing something wrong?
How can I not block UI and perform the api calls simultaneously?
You can do this to run on a background thread:
DispatchQueue.global(qos: .background).async {
// Do any processing you want.
DispatchQueue.main.async {
// Go back to the main thread to update the UI.
}
}
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system.
I usually use NSOperationQueue with Alamofire, but the concepts are similar. When you set up an async queue, you allow work to be performed independently of the main (UI) thread, so that your app doesn't freeze (refuse user input). The work will still take however long it takes, but your program doesn't block while waiting to finish.
You really have only put one item into the queue.
You are adding to the queue only once, so all those "perform" calls wait for the previous one to finish. If it is safe to run them concurrently, you need to add each of them to the queue separately. There's more than one way to do this, but the bottom line is each time you call .async {} you are adding one item to the queue.
dataQueue.async {
DataSyncController().performStateSyncAPICall()
}
dataQueue.async {
DataSyncController(). performRegionSyncAPICall l()
}

Resources