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)
Related
In Xcode 9 / Swift 4 using Google APIs Client Library for Objective-C for REST: why does service.executeQuery return thread completion notification before the thread completes?
I have been trying various ways but I am stuck with the following code where the notification is returned before the thread completes. See below the code, the actual output and what I would expect to see (notification comes once the thread has complete).
What am I doing wrong?
Thanks
func myFunctionTest () {
let workItem = DispatchWorkItem {
self.service.executeQuery(query,
delegate: self,
didFinish: #selector(self.displayResultWithTicket2b(ticket:finishedWithObject:error:))
)
}
let group = DispatchGroup()
group.enter()
group.notify(queue: service.callbackQueue) {
print("************************** NOTIFY MAIN THREAD *************************************")
}
service.callbackQueue.async(group: group) {
workItem.perform()
}
group.leave()
}
#objc func displayResultWithTicket2b(ticket : GTLRServiceTicket,
finishedWithObject messagesResponse : GTLRGmail_ListMessagesResponse,
error : NSError?) {
//some code to run here
print("************************** 02.displayResultWithTicket2b ***************************")
}
Output
************************** NOTIFY MAIN THREAD *************************************
************************** 02.displayResultWithTicket2b ***************************
What I would expect = Thread notification comes when thread has completed
************************** 02.displayResultWithTicket2b ***************************
************************** NOTIFY MAIN THREAD *************************************
The problem is that you're dealing with an asynchronous API and you're calling leave when you're done submitting the request. The leave() call has to be inside the completion handler or selector method of your executeQuery call. If you're going to stick with this selector based approach, you're going to have to save the dispatch group in some property and then have displayResultWithTicket2b call leave.
It would be much easier if you used the block/closure completion handler based rendition of the executeQuery API, instead of the selector-based API. Then you could just move the leave into the block/closure completion handler and you'd be done. If you use the block based implementation, not only does it eliminate the need to save the dispatch group in some property, but it probably eliminates the need for the group at all.
Also, the callback queue presumably isn't designed for you to add your own tasks. It's a queue that the library will use the for its callbacks (the queue on which completion blocks and/or delegate methods will be run). Just call executeQuery and the library takes care of running the callbacks on that queue. And no DispatchWorkItem is needed:
session.executeQuery(query) { ticket, object, error in
// do whatever you need here; this runs on the callback queue
DispatchQueue.main.async {
// when you need to update model/UI, do that on the main queue
}
}
The only time I'd use a dispatch group would be if I was performing a series of queries and needed to know when they were all done:
let group = DispatchGroup()
for query in queries {
group.enter()
session.executeQuery(query) { ticket, object, error in
defer { group.leave() }
// do whatever you need here; this runs on the callback queue
}
}
group.notify(queue: .main) {
// do something when done; this runs on the main queue
}
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).
How can I prevent a block of code to be repeatedly accessed from the same thread?
Suppose, I have the next code:
func sendAnalytics() {
// some synchronous work
asyncTask() { _ in
completion()
}
}
I want to prevent any thread from accessing "// some synchronous work", before completion was called.
objc_sync_enter(self)
objc_sync_exit(self)
seem to only prevent accessing this code from multiple threads and don't save me from accessing this code from the single thread. Is there a way to do this correctly, without using custom solutions?
My repeatedly accessing, I mean calling this sendAnalytics from one thread multiple times. Suppose, I have a for, like this:
for i in 0...10 {
sendAnalytics()
}
Every next call won't be waiting for completion inside sendAnalytics get called (obvious). Is there a way to make the next calls wait, before completion fires? Or the whole way of thinking is wrong and I have to solve this problem higher, at the for body?
You can use a DispatchSemaphore to ensure that one call completes before the next can start
let semaphore = DispatchSemaphore(value:1)
func sendAnalytics() {
self.semaphore.wait()
// some synchronous work
asyncTask() { _ in
completion()
self.semaphore.signal()
}
}
The second call to sendAnalytics will block until the first asyncTask is complete. You should be careful not to block the main queue as that will cause your app to become non-responsive. It is probably safer to dispatch the sendAnalytics call onto its own serial dispatch queue to eliminate this risk:
let semaphore = DispatchSemaphore(value:1)
let analyticsQueue = DispatchQueue(label:"analyticsQueue")
func sendAnalytics() {
analyticsQueue.async {
self.semaphore.wait()
// some synchronous work
asyncTask() { _ in
completion()
self.semaphore.signal()
}
}
}
I am self taught Swift user and trying to do something simple but it's got me pretty stumped. I have a simple registration form. After submitting the items for registration, I want to move the page to a "how it works" page via a segue, but ONLY when my restful API returns success. Here's what I have so far; feel free to send me a better way to do this as well. All criticisms are welcome.
let myUrl = NSURL(string:"http://www.example.com/scripts/Register.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "email=\(email)&password=\(pass)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){
data, response, error in
if (error != nil) {
println("Error: \(error)")
return
}
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
var showTutorial : Bool = false
if let parseJSON = json {
var returnValue = parseJSON["status"] as? String
println("Status: \(returnValue)")
var isUserRegistered: Bool = false
if (returnValue == "Success") {
showTutorial = true
} else if (returnValue == "Error") {
// handle error
}
}
// if successful registration, show how it works page
if (showTutorial) {
self.performSegueWithIdentifier("howItWorksSegue", sender: self)
}
}
task.resume()
I have a segue named howItWorksSegue attached to this view controller going to the HowItWorksViewController. I'm receiving this error from Xcode:
2015-10-12 21:22:43.261 ZiftDine[11396:2307755] Assertion failure in -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished], /SourceCache/UIKit_Sim/UIKit-3347.44.2/Keyboard/UIKeyboardTaskQueue.m:374
2015-10-12 21:22:43.391 ZiftDine[11396:2307755] Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIKeyboardTaskQueue waitUntilAllTasksAreFinished] may only be called from the main thread.'
Anything done with UI should be done on the main thread, try wrapping you performSegue call like this:
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier("howItWorksSegue", sender: self)
})
#Swinny89 gave the solution to your problem but some explanation is in order.
If you read the description of dataTaskWithRequest:completionHandler:, which is the method you are using (although your Swift code uses trailing closure syntax to drop the completionHandler label and put the closure outside the parentheses) it says:
completionHandler: The completion handler to call when the load
request is complete. This handler is executed on the delegate queue.
Then if you read the description of the init method sessionWithConfiguration:delegate:delegateQueue: it says:
queue: A queue for scheduling the delegate calls and completion
handlers. If nil, the session creates a serial operation queue for
performing all delegate method calls and completion handler calls.
Serial operation queues run on a different thread.
So, taking all of those pieces of information together, it means that your completion closure is going to be executed on a thread other than the main thread.
A cardinal rule of iOS/Mac development is that you must do all UI calls from the main thread. If a call changes anything on the screen, it's a UI call.
Your code is invoking performSegueWithIdentifier: from a background thread. It changes what's displayed on the screen, so it must be a UI call. Therefore it needs to be run on the main thread.
The GCD function dispatch_async(), with a queue of dispatch_get_main_queue(), submits a closure to be run on the main dispatch queue, a queue that runs on the main thread.
So Swinny's solution fixes your problem.
The take-away here:
Any time you are running code in a closure, stop and think: "Am I positive that this closure will always be run on the main thread?" If the answer is no, enclose the code in a call to dispatch_async(dispatch_get_main_queue(), like Swinny's answer.
The answers by #Duncan C and #Swinny89 are good. For anyone coming in from Google, the syntax in Swift 3 has changed a little:
DispatchQueue.main.async(execute: {
self.performSegueWithIdentifier("howItWorksSegue", sender: self)
})
In Swift (that's Swift) there are a number of ways to handle asynchronous,
Say you have a loop like this - it's calling a parse cloud code call which goes to background anyway.
public func runImages()
{
print("test begins...")
for i in 1...3
{
print("Tick tock tick tock ..\(i)")
PFCloud.callFunctionInBackground("blah", withParameters:["bla":i,"bla":"bla] )
{
(response: AnyObject?, error: NSError?) -> Void in
print(".. done! Now go again...")
if let rr = response as? String { print(rr) }
if let err = error { print(err.domain) }
}
}
}
How to make that wait for the end of each PFCloud call?
Really is just an ordinary flag best, or? (Note that (a) I can't get a flag to work in Swift and (b) as Paul points out you're blocking the UI!!)
What is the "Swift way" in the context you see here? I feel it would be very inelegant to use a recursive pattern here, for example.
If you want the three calls to execute serially then you can use a serial dispatch queue and a dispatch_barrier_async to do something when all three are finished -
let dispatchQueue=dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL)
for i in 1...3 {
dispatch_async(dispatchQueue, { () -> Void in
print("i=\(i)")
let result = PFCloud.callFunction("blah", withParameters:["bla":i,"bla":"bla] )
})
}
dispatch_barrier_async(dispatchQueue, { () -> Void in
print("really done")
})
print(" done")
In order for this to work with your Parse function you need to use the synchronous cloud code call, not an asynchronous. And if you update UI in the dispatch_barrier_async closure you would need to dispatch that on the main queue.