Does throwing a asynchronous function make it synchronous in Swift? - swift

This is a code example taken from Perfect Swift PostgresSTORM library.
do{
//Create a user object
let obj = User()
obj.name = "someUser"
//Save it to db
try obj.save({ id in
print(2..)
obj.id = id as! Int
})
print("1..")
}catch{
print("Something went wrong.")
}
//Go to next page
print("3..")
I expected to see the print logs to be
1..
3..
2..
but, The logs looked like this.
2..
1..
3..
It's highly unlikely for "2.." to print before "1..". Is it the "try" that's making it to run as a synchronous function?

It's totally up to PostgresSTORM implementation of save. I'm not familiar with it so I can't say whether it's actually asynchronous or not but I can offer you two dummy implementations, one asynchronous and one synchronous of a method with a similar signature.
Asynchronous:
func save(callback: #escaping (Int) -> Void) throws {
OperationQueue.main.addOperation {
callback(0)
}
}
Synchronous:
func save(callback: (Int) -> Void) throws {
callback(0)
}
(Note that it doesn't throw any exception in this example just for simplicity's sake).
try is required by Swift compiler when you call a function that might throw an exception and has no impact in (a)synchronous execution of the method. In fact try is just required to ensure that when we use the method we are well aware about it possibly throwing an exception.
I might be wrong but if this is SwiftORM's implementation of save method then callback is always synchronously called.

Related

Throwing an error from a stored throwing function

I have an array of throwing completion block handlers:
typealias CompletionBlock = (MyType) throws -> Void
private var handlers: [CompletionBlock] = []
I want to be able to recall them later on and throw an error like this:
for handler in handlers {
handler(throw myError)
}
However, that doesn't work because it's expecting me to pass MyType to the handler, not an error. How can I do this?
Your question is a bit vague, but I'm going to assume you mean that you want the handlers to accept the result of a throwing function. That's possible, and I'll show how, but it's not quite what you want.
"The result of a throwing function" can be expressed by calling a function that calls a throwing function:
typealias CompletionBlock = (() throws -> MyType) -> Void
Throwing an error in the way you've described like this:
let result: () throws -> MyType = { throw MyError() }
for handler in handlers {
handler(result)
}
A handler would then look like:
func h(result: () throws -> MyType) {
do {
let myType = try result()
// ...
} catch {
// ...
}
}
But this is not a great design. Most importantly it executes result many times. Instead you want to pass the Result of a throwing function:
typealias CompletionBlock = (Result<MyType, Error>) -> Void
let f: () throws -> MyType = { throw MyError() }
let result = Result(catching: f)
for handler in handlers {
handler(result)
}
The more common look for this is along these lines:
let result = Result {
// Do various operations that might throw
return value
}
You can also convert back from Result to a throws using try result.get(). So you can easily move between Result and throws wherever you like. Result is best for storing. Throws is best for calling. You can have both.
(And of course, you should also be looking to the future and exploring replacing completion handlers with async/await, but there are many reasons you might not do that right away, so Result will be an important tool for quite some time.)
You are trying to change the behaviour of a stored closure by forcing it to throw an error regardless of its input. You cannot do that.
The fact that you marked CompletionBlock as throws only means that the closure itself might throw. However, you cannot force it to throw, since you can only pass an input argument of type MyType to it.
Moreover, there's no point in enforcing a closure to throw, since you need to handle the error thrown from the call-site anyways. So you can just simply throw the error from the call site.
Alternatively, if you need the closure itself to handle an error, you should change the closure to accept a Result and in case .failure in injected, the closure itself could throw.
typealias CompletionBlock = (Result<MyType, MyError>) throws -> Void
private var handlers: [CompletionBlock] = []
for handler in handlers {
handler(.failure(myError))
}

Is realm.write asynchronous

I am trying to add CustomUserData to my MongoDB Realm Database and then refresh the user data.
But as it seems is realm.write asynchronous, whereas I thought it would be synchronous (Swift Realm Write method is Sync or Async Thread). So my question is what's really the case and when its synchronous why does the .refreshCustomData() function is only able to fetch the new data after a short delay.
try! realm.write {
realm.add(member)
}
app.currentUser!.refreshCustomData()
// -> after the refresh the customUserData is still empty
// only if you call it with a short delay the result contains the new written data
No, it is not asynchronous.
Have a look at the implementation (removed everything that is not important to understand).
A write transaction is simply started and committed if no exception has occurred, otherwise the transaction is rolled back.
public func write<Result>(_ block: (() throws -> Result)) throws -> Result {
beginWrite()
var ret: Result!
do {
ret = try block()
} catch let error {
if isInWriteTransaction { cancelWrite() }
throw error
}
if isInWriteTransaction { try commitWrite(withoutNotifying: tokens) }
return ret
}
But: If you query the data just written in another thread, that is another story.
New objects just wont be visible on another thread for some time.
My solutions to this is: stay on the same thread for write and read operations.
See also: Saving data and query it in different threads with Realm

How do you escape a completionBlock in Swift to return to normal application flow?

One of the things that's throwing me in Swift is the never-ending chain of completionBlocks in my program; and I'm not sure how to get Swift to say "Ok, the completion block is now done -- come back to the main program."
In my project I am writing a simple board/card game which loads its data from a very small plist.
I wrote a fairly simple Plist loader, it loads the plist and kicks back Data via a completionBlock. It doesn't do any parsing; it doesn't care about how you want to parse it, it simply returns NSData (Data) or errors.
I have a Parser which fires the Plist loader, gets the Data and then parses it using the new Swift Codable protocol.
// A static function to try and find the plist file on bundle, load it and pass back data
static func loadBoard(completionHandler: #escaping (Board?, Error?) -> Void) {
PListFileLoader.getDataFrom(filename: "data.plist") { (data, error) in
if (error != nil) {
print ("errors found")
completionHandler(nil, error)
}
else {
guard let hasData = data else {
print ("no data found")
completionHandler(nil, error)
return
}
do {
print ("found board")
let board = try decodeBoard(from: hasData) // Call a function that will use the decoder protocol
completionHandler(board, nil)
} catch {
print ("some other board error occured")
completionHandler(nil, error)
}
}
}
}
and then returns this parsed data to the main program, or whatever called it -- for example; an XCTest
My XCTest:
func testBoardDidLoad() -> Board? { // The return bit will show an error; but its fine without the return part
BoardParsePlist.loadBoard { (board, error) in
XCTAssertNotNil(board, "Board is nil")
XCTAssertNotNil(error, error.debugDescription)
// How do I now escape this and return flow to the normal application?
// Can I wrap this in a try-catch?
}
}
From a hierarchical view it sort of looks like this.
XCTest
... Calls the parser (completionBlock ...)
.... Parser calls the PListLoader (completionHandler: ...)
Now it feels like I'm stuck in the block for the rest of the app
BoardParsePlist.loadBoard { (board, error) in
// ... rest of the app now lives here?
})
It seems I'm in a never ending loop of completionBlocks.
How do you "escape" or break out of the completion block and return flow back to the main app?
I'm not sure if I explained it correctly, but would appreciate any assistance on this.
Thank you for your time.
not sure how to get Swift to say "Ok, the completion block is now done -- come back to the main program."
There's no way and no need to say that - it happens automatically.
static func loadBoard(completionHandler: #escaping (Board?, Error?) -> Void) {
PListFileLoader.getDataFrom(filename: "data.plist") { (data, error) in
// completion handler code here
}
// "back to main"
}
In your example, execution will get "back to main" in one of two ways, depending on whether PListFileLoader.getDataFrom is running asynchronously or not.
If it's running synchronously, the execution order will be:
your app calls loadBoard
this calls getDataFrom
which in turn calls its completion handler parameter
the completion handler runs
"back to main" runs
OTOH, if getDataFrom is asynchronous (e.g. because it does a network request), the order will be:
your app calls loadBoard
this calls getDataFrom
which starts its asynchronous work and returns
"back to main" runs
at some later point in time, the work started by getDataFrom is done, and it calls the completion handler parameter
the completion handler runs
Either way, you get back to main without special effort.

How to test asynchronous method results?

When we get table view datasource, we will ask a network request. It is asynchronous. I have no idea to test the result operation. There is a method to get the points.
func loadPoints() {
API().retrievePoints{ [weak self](pointsSet, error) in
DispatchQueue.main.async(execute: {
// Make sure the call succeeded; return an error if it didn't
guard error == nil else {
self?.showErrorMessage()
Device.debugLog(item:"Error loading Points: \(String(describing: error))")
return
}
self?.pointsSet = pointsSet
self?.tableView.reloadData()
})
}
}
I have known that if we want to test the retrievePoints method, we can test like bellow
//points
func testRetrievePoints() {
let expectation = self.expectation(description: "RetrievePoints")
API().retrievePoints{ (pointsSet, error) -> Void in
XCTAssertNil(pointsSet)
XCTAssertNotNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 15.0, handler: nil)
}
Now I want to test the codes
self?.pointsSet = pointsSet
self?.tableView.reloadData()
self?.showErrorMessage()
For now I just use sleep(15) to wait the method. But it is inaccurate.
Could you please help me? Thanks in advance.
Just as what you have said, it's asynchronous. So it will take time before finish. That is to say that you need to wait before it can success.
Also note that it's just timeout value. All your task must finish within this value. Or it will be treated as failure.
You want to test your data source — not your web service.
there for you should mock the api call.
To achieve this you could use a mocking framework. But I'd rather go another route:
create a protocol that declares the public interface of API, let API conform to that protocol
pass the API as a dependency into the data source. either as an init parameter or via a property. Passing objects is easier that classes, I'd change retrievePoints to be an instance method.
For your test write an APIMock that implements the protocol. Let retrievePoints' callback return prepared points.
Now the points will be returned immediately, no need for timeouts. If you want to defer that your mock can use a DispatchQueue.main.asyncAfter call.

Is there a way to throw errors from asynchronous closures in Swift 3?

I’m executing some functions in a test asynchronously using a DispatchQueue like this:
let queue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
let group: DispatchGroup = DispatchGroup()
func execute(argument: someArg) throws {
group.enter()
queue.async {
do {
// Do stuff here
group.leave()
} catch {
Log.info(“Something went wrong")
}
}
group.wait()
}
Sometimes the code inside the do block can throw errors, that I have to catch later on. Since I’m developing a test, I want it to fail, if the code inside the do block throws an error.
Is there a way to throw an error, without catching it inside the queue.async call?
You cannot throw an error, but you can return an error:
First, you need to make your calling function asynchronous as well:
func execute(argument: someArg, completion: #escaping (Value?, Error?)->()) {
queue.async {
do {
// compute value here:
...
completion(value, nil)
} catch {
completion(nil, error)
}
}
}
The completion handler takes a parameter which we could say is a "Result" containing either the value or an error. Here, what we have is a tuple (Value?, Error?), where Value is the type which is calculated by the task. But instead, you could leverage a more handy Swift Enum for this, e.g. Result<T> or Try<T> (you might want to the search the web).
Then, you use it as follows:
execute(argument: "Some string") { value, error in
guard error == nil else {
// handle error case
}
guard let value = value else {
fatalError("value is nil") // should never happen!
}
// do something with the value
...
}
Some rules that might help:
If a function calls an asynchronous function internally, it inevitable becomes an asynchronous function as well. *)
An asynchronous function should have a completion handler (otherwise, it's some sort of "fire and forget").
The completion handler must be called, no matter what.
The completion handler must be called asynchronously (with respect the the caller)
The completion handler should be called on a private execution context (aka dispatch queue) unless the function has a parameter specifying where to execute the completion handler. Never use the main thread or main dispatch queue - unless you explicitly state that fact in the docs or you intentionally want to risk dead-locks.
*) You can force it to make it synchronous using semaphores which block the calling thread. But this is inefficient and really rarely needed.
Well, you might conclude, that this looks somewhat cumbersome. Fortunately, there's help - you might look for Future or Promise which can nicely wrap this and make the code more concise and more comprehensible.
Note: In Unit Test, you would use expectations to handle asynchronous calls (see XCTest framework).
Refactor your code to use queue.sync and then throw your error from there. (Since your execute function is actually synchronous, given the group.wait() call at the last line, it shouldn't really matter.)
For instance, use this method from DispatchQueue:
func sync<T>(execute work: () throws -> T) rethrows -> T
By the way, a good idiom for leaving a DispatchGroup is:
defer { group.leave() }
as the first line of your sync/async block, which guarantees you won't accidentally deadlock when an error happens.