Does a Completion Handler end the function? - swift

Perhaps I do not understand the concept of a completion handler, but I have a function like so;
func someFunction(completion: #escaping (Bool, LoginError?) -> Void) {
self.checkDevice() { allowed, error in
if let e = error {
completion(false, e)
}
completion(true, nil)
}
}
While being light on what checkDevice() does, the basic premise is that it performs an asynchronous network call, and returns either true with no error (nil), or returns false with an error.
When I run this code, I am finding that the completion handler is being called twice. It sends a completion as a tuple (as false, error) and also as (true, nil). I've done some debugging and there seems to be no manner in which someFunction() is called twice.
I was of the belief that once a completion is sent, the function would end. In my test case, I am forcing an error from checkDevice(), which should result in me sending the completion as (false, error), but I am seeing both (false, error) and (true, nil). Does a completion not immediately end the function?

I was of the belief that once a completion is sent, the function would end.
No, why would that be? When you call this function it’s like calling any other function. The name has no magic power.
Rewrite it like this:
func someFunction(completion: #escaping (Bool, LoginError?) -> Void) {
self.checkDevice() { allowed, error in
if let e = error {
completion(false, e)
return // *
}
completion(true, nil)
}
}
Actually, I should mention that this is a poor way to write your completion handler. Instead of taking two parameters, a Bool and an Optional LoginError to be used only if the Bool is false, it should take one parameter — a Result, which carries both whether we succeeded or failed and, if we failed, what the error was:
func someFunction(completion: #escaping (Result<Void, Error>) -> Void) {
self.checkDevice() { allowed, error in
completion( Result {
if let e = error { throw e }
})
}
}
As you can see, using a Result as your parameter allows you to respond much more elegantly to what happened.
Indeed, checkDevice itself could pass a Result into its completion handler, and then things would be even more elegant (and simpler).

A completion handler does not end a function. The typical way to end a function is to insert a return.
See comment re: your specific case

Consider including parameter names (for reference). And the completion should only be called once. You can do that using return or by using a full if-else conditional.
func someFunction(completion: #escaping (_ done: Bool, _ error: LoginError?) -> Void) {
checkDevice() { (allowed, error) in
if let e = error {
completion(false, e)
} else {
completion(true, nil)
}
}
}
func someFunction(completion: #escaping (_ done: Bool, _ error: LoginError?) -> Void) {
checkDevice() { (allowed, error) in
if let e = error {
completion(false, e)
return
}
completion(true, nil)
}
}
By giving parameters names, when calling the function, you can now reference the signature to denote the meanings of its parameters:
someFunction { (done, error) in
if let error = error {
...
} else if done {
...
}
}

Related

ReactiveSwift pipeline flatMap body transform not executed

I have the following pipeline setup, and for some reason I can't understand, the second flatMap is skipped:
func letsDoThis() -> SignalProducer<(), MyError> {
let logError: (MyError) -> Void = { error in
print("Error: \(error); \((error as NSError).userInfo)")
}
return upload(uploads) // returns: SignalProducer<Signal<(), MyError>.Event, Never>
.collect() // SignalProducer<[Signal<(), MyError>.Event], Never>
.flatMap(.merge, { [uploadContext] values -> SignalProducer<[Signal<(), MyError>.Event], MyError> in
return context.saveSignal() // SignalProducer<(), NSError>
.map { values } // SignalProducer<[Signal<(), MyError>.Event], NSError>
.mapError { MyError.saveFailed(error: $0) } // SignalProducer<[Signal<(), MyError>.Event], MyError>
})
.flatMap(.merge, { values -> SignalProducer<(), MyError> in
if let error = values.first(where: { $0.error != nil })?.error {
return SignalProducer(error: error)
} else {
return SignalProducer(value: ())
}
})
.on(failed: logError)
}
See the transformations/signatures starting with the upload method.
When I say skipped I mean even if I add breakpoints or log statements, they are not executed.
Any idea how to debug this or how to fix?
Thanks.
EDIT: it is most likely has something to do with the map withing the first flatMap, but not sure how to fix it yet.
See this link.
EDIT 2: versions
- ReactiveCocoa (10.1.0):
- ReactiveObjC (3.1.1)
- ReactiveObjCBridge (6.0.0):
- ReactiveSwift (6.1.0)
EDIT 3: I found the problem which was due to my method saveSignal sending sendCompleted.
extension NSManagedObjectContext {
func saveSignal() -> SignalProducer<(), NSError> {
return SignalProducer { observer, disposable in
self.perform {
do {
try self.save()
observer.sendCompleted()
}
catch {
observer.send(error: error as NSError)
}
}
}
}
Sending completed make sense, so I can't change that. Any way to change the flatMap to still do what I intended to do?
I think the reason your second flatMap is never executed is that saveSignal never sends a value; it just finishes with a completed event or an error event. That means map will never be called, and no values will ever be passed to your second flatMap. You can fix it by doing something like this:
context.saveSignal()
.mapError { MyError.saveFailed(error: $0) }
.then(SignalProducer(value: values))
Instead of using map (which does nothing because there are no values to map), you just create a new producer that sends the values after saveSignal completes successfully.

Variable outside DispatchQueue.main.async is empty

I tried to resolve this error since days but I dont understand why am I getting this error in the first place.
Please help...
func createData(request:Crudpb_CreateRequest) -> String {
DispatchQueue.main.async {
self.response = try! self.client.create(request)
print("This is response 1: " + self.response.result) // <-------- This is priting the right response
}
print("This is response outside DispatchQueue: " + self.response.result) // <------- This is not printing anyvalue
return self.response.result // <------ This is not
}
You are dispatching whatever work you are doing in your create request method asynchronously, therefor your create data function will not wait for this work to be done to continue its execution, it just calls it and keeps its execution and thats why your value is not modified when you reach your "This is response outside DispatchQueue: " statement.
It will be modified in the capture block that you have created, thats why you need to create an #escaping completion block like they mentioned before, to only return your value when the work you did to obtain it is finished.
func createData(request:Crudpb_CreateRequest, with completion: #escaping (String) -> Void) {
DispatchQueue.main.async {
self.response = try! self.client.create(request)
completion(self.response)
}
}
This is the way I use #escaping closures: You don't need a return value in the function, given that the execution of the calling function may end before the closure is finished. You also need to specify a dataType in the #escaping parameter (Bool in this case)... also, Function types cannot have argument labels, so you must use "_"
self.fetchStuff(onCompletion: { (success) in
if success {
// Do something
}
})
func fetchStuff(onCompletion: #escaping (_ success: Bool) -> Void) {
// Do some asynch stuff
onCompletion(true)
}

Chaining closures and completion handlers Swift 3

I'm having a hard time understanding how chaining completion handlers with closures works.
The example I'm working with is the following :
typealias CompletionHandler = (_ result: AnyObject?, _ error: NSError?) -> Void
func getRequestToken(completionHandler: CompletionHandler){
taskForGET(path: "PathToWebsite.com") { (result, error) in
if let error = error {
print(error)
} else {
print(result)
}
}
}
func taskForGET(path: String, completionHandler: CompletionHandler) {
//URLSESSIONCODE with completion handler
(data, response, error) {
if let error = error {
CompletionHandler(result: nil, error: error)
} else {
let data = data
parseJSON(data: data, completionHandler: completionHandler)
}
}
}
func parseJSON(data: NSData, completionHandler: CompletionHandler) {
//SerializingJSON with do catch block
do {
completionHandler(result: theResult, error: nil)
} catch {
completionHandler(result: nil, error: error)
}
}
So basically what this code does is it kicks off a GET request to a server. If the GET request sends back data, then it parses it into JSON. If at any point along the way something fails, it returns an error.
I understand basically what is going on here, but I don't understand how the completion handlers are being fired off.
First taskForGET is called which has a completion handler as a parameter that can return a result or an error, I've got that.
The next step is calling parseJSON, where the data from taskForGET is passed but then the completionhandler that's being passed is taskForGET's completion handler. I don't understand this at all.
Then down in parseJSON, the completion handler either returns JSON or an error by calling the completion handler from its parameters..which in this case is taskForGET's completion handler?
I don't understand the flow. Even once we've parsed JSON successfully, how does calling taskForGET's result ever get back up to getRequestToken.
Any help with this would be appreciated.
There is only one completion handler which is passed from one method to another.
Lets declare the handler separately, btw. in Swift 3 omit the parameter labels in the type alias:
typealias CompletionHandler = (Any?, Error?) -> Void
let handler : CompletionHandler = { (result, error) in
if let error = error {
print(error)
} else {
print(result)
}
}
This closure is supposed to be executed at the end of the process. Now the getRequestToken method looks like
func getRequestToken(completionHandler: CompletionHandler){
taskForGET(path: "PathToWebsite.com", completionHandler: handler)
}
The handler / closure is passed as a parameter from getRequestToken to taskForGET and then from taskForGET to parseJSON but it's always the same object.

swift 3 from working swift 2.3 : #escaping with optionals and calls needing error chain management

I have optionals (NSError?) that are flagged by Xcode/Swift as non-escaping. These are called within a function (that has #escaping on its closure) by a second function (also with #escaping on its closure). The problem seems to be that the errors are not captured in the closure of either function and so Xcode/Swift is seeing them as potentially escaping.
Previous stack overflow posts had noted 'withoutActuallyEscaping' as a workaround. That no longer seems to be supported (Xcode version 8.2.1).
Refactoring to try to keep everything local to the function has not worked. I've also tried moving all of the NSError? to Error (via local enum), but that also hasn't worked.
The core problem is how to enable an error returned (via success/failure) from one function to the calling statement to be either captured locally and retained or tagged as escaping so that Swift/XCode can compile.
Again, this was working in Swift 2.3, so I'm looking for suggestions on how to refactor or what to look at for correctly handling these calls with NSError or Error.
This is part of a large code stack (about 25K lines). I'm posting part of the stack below (given Hamish's comment) to try and make the question clearer. Currently there are about 17-different variations of this error that are present in the code-stack.
public func fetchMostRecentSamples(ofTypes types: [HKSampleType], completion: #escaping HMTypedSampleBlock)
{
let group = DispatchGroup()
var samples = [HKSampleType: [MCSample]]()
let updateSamples : ((MCSampleArray, CacheExpiry) -> Void, (MCError) -> Void, HKSampleType, [MCSample], MCError) -> Void = {
(success, failure, type, statistics, error) in
guard error == nil else {
failure(error)
return
}
guard statistics.isEmpty == false else {
failure(error)
return
}
samples[type] = statistics
success(MCSampleArray(samples: statistics), .never)
}
let onStatistic : ((MCSampleArray, CacheExpiry) -> Void, (MCError) -> Void, HKSampleType) -> Void = { (success, failure, type) in
self.fetchMostRecentSample(type) { (samples, error) in
guard error == nil else {
failure(error)
return
}
Then fetchMostRecentSample has this header:
public func fetchMostRecentSample(_ sampleType: HKSampleType, completion: #escaping HMSampleBlock)
and the error message on the failure is "Closure use of non-escaping parameter 'failure' may allow it to escape" : "Parameter 'failure' is implicitly non-escaping"
Note that the let updateSamples is fine (not calling another function), but that the onStatistic with the failure (having error codes) is where the problem with escaping/non-escaping is coming from. MCError is an enum with the error codes (refactored from NSError? in Swift 2.3 version).
Major credits to Hamish for helping me to see into this. In case it helps others coming across this on their Swift 3 conversions, the other place that I stumbled was in assuming that all of our third-party pods were complete if they stated ready for Swift 3. In this case I had to update the AwesomeCache code to handle the errors correctly:
open func setObject(forKey key: String, cacheBlock: #escaping (#escaping(CacheBlockClosure), #escaping(ErrorClosure)) -> Void, completion: #escaping (T?, Bool, NSError?) -> Void) {
if let object = object(forKey: key) {
completion(object, true, nil)
} else {
let successBlock: CacheBlockClosure = { (obj, expires) in
self.setObject(obj, forKey: key, expires: expires)
completion(obj, false, nil)
}
let failureBlock: ErrorClosure = { (error) in
completion(nil, false, error)
}
cacheBlock(successBlock, failureBlock)
}
}
This is simply adding two more #escaping beyond what was in the github master. Otherwise, as Hamish pointed out, it comes down to paying attention to where #escaping needs to be added on the function calls. The update for AwesomeCache came from code like this:
aggregateCache.setObjectForKey(key, cacheBlock: { success, failure in
let doCache : ([MCAggregateSample], NSError?) -> Void = { (aggregates, error) in
guard error == nil else {
failure(error)
return
}
success(MCAggregateArray(aggregates: aggregates), .Date(self.getCacheExpiry(period)))
}
where the success, failure will be flagged as potentially escaping without the code changes on the AwesomeCache.

Calling a function that has a TypeAlias as a parameter?

So I've written a little practice program that has to do with closures. I'm trying to better understand how the asynchronous concept works. When I try to call request(), I get conversion errors as seen below:
import UIKit
let correctPasscode = "3EyX"
typealias CompletionHandler = (result: AnyObject?, error: String?) -> Void
func request(passcode: String, completionHandler: CompletionHandler) {
sendBackRequest(passcode) {(result, error) -> Void in
if error != nil {
print(error)
}
else {
print(result)
}}
}
func sendBackRequest(passCode: String, completionHandler: CompletionHandler) {
if passCode == correctPasscode {
completionHandler(result: "Correct. Please proceed", error: nil)
} else {
completionHandler(result: nil, error: "There was an error signing in")
}
}
request(correctPasscode, completionHandler: CompletionHandler) // Error happens here
Type alias is there to tell you what actual type you need to pass. In this case, the type is a closure of type
(result: AnyObject?, error: String?) -> Void
You pass it like this:
request(correctPasscode, completionHandler:{
(result: AnyObject?, error: String?) in
print("Inside the handler...")
// Do some useful things here
})
or even shorter -
request(correctPasscode) {
(result: AnyObject?, error: String?) in
print("Inside the handler...")
// Do some useful things here
}
or even shorter - (the types are known via the func declaration) -
request(correctPasscode) { result, error in
print("Inside the handler...")
// Do some useful things here
}
I'm not sure what you try to accomplish, but this line:
request(correctPasscode, completionHandler: CompletionHandler)
doesn't compile because you are not passing a CompletionHandler closure to the function but rather a type object representing the type of that completion handler.
Hence the error: Cannot convert value of 'CompletionHandler.*Type*'.
A valid call would be:
request(correctPasscode) { result, error in
print("result was \(result), and error was \(error)")
}
But then your request function doesn't do anything with the closure which is passed in. It is a little hard to say what you want ...