I'd like to implement method chaining in my swift code, likely to Alamofire methods. For example, if I have to use my function like below
getListForID(12).Success {
// Success block
}. Failure {
// Failure block
}
How would I create the function getListForID?
To expand on the great points #dasblinkenlight and #Sulthan have made – here's a small example of how you could achieve your request function to take a success and failure closure, in the convenient syntax that you want.
First, you'll have to define a new class to represent the 'result handler'. This is what your success and failure functions will pass around, allowing you to add multiple trailing closures to make up your completion block logic. You'll want it to look something like this:
class ResultHandler {
typealias SuccessClosure = RequestHandler.Output->Void
typealias FailureClosure = Void->Void
// the success and failure callback arrays
private var _successes = [SuccessClosure]()
private var _failures = [FailureClosure]()
/// Invoke all the stored callbacks with a given callback result
func invokeCallbacks(result:RequestHandler.Result) {
switch result {
case .Success(let output): _successes.forEach{$0(output)}
case .Failure: _failures.forEach{$0()}
}
}
// remove all callbacks – could call this from within invokeCallbacks
// depending on the re-usability of the class
func removeAllCallbacks() {
_successes.removeAll()
_failures.removeAll()
}
/// appends a new success callback to the result handler's successes array
func success(closure:SuccessClosure) -> Self {
_successes.append(closure)
return self
}
/// appends a new failure callback to the result handler's failures array
func failure(closure:FailureClosure) -> Self {
_failures.append(closure)
return self
}
}
This will allow you to define multiple success or failure closures to be executed on completion. If you don't actually need the capacity for multiple closures, then you can simplify the class down by stripping out the arrays – and just keeping track of the last added success and failure completion blocks instead.
Now all you have to do is define a function that generates a new ResultHandler instance and then does a given asynchronous request, with the invokeCallbacks method being invoked upon completion:
func doRequest(input:Input) -> ResultHandler {
let resultHandler = ResultHandler()
doSomethingAsynchronous(resultHandler.invokeCallbacks)
return resultHandler
}
Now you can call it like this:
doRequest(input).success {result in
print("success, with:", result)
}.failure {
print("fail :(")
}
The only thing to note is your doSomethingAsynchronous function will have to dispatch its completion block back to the main thread, to ensure thread safety.
Full project (with added example on usage): https://github.com/hamishknight/Callback-Closure-Chaining
In order to understand what is going on, it would help to rewrite your code without the "convenience" syntax, which lets you omit parentheses when a closure is the last parameter of a function:
getListForID(12)
.Success( { /* Success block */ } )
.Failure( { /* Failure block */ } )
This makes the structure of the code behind this API more clear:
The return value of getListForID must be an object
The object must have two function called Success and Failure*
Both Success and Failure need to take a single parameter of closure type
Both Success and Failure need to return self
* The object could have only Success function, and return a different object with a single Failure function, but then you wouldn't be able to re-order the Success and Failure handlers, or drop Success handler altogether.
Related
Between these 2 codes
( returns -> Void)
static func dropboxWorkoutList ( userCompletionHandler: #escaping ([String]) -> Void) {
let client = DropboxClientsManager.authorizedClient
var files: [String] = []
_ = client?.files.listFolder(path: "/workouts")
.response { response, error in
if let result = response {
for entry in result.entries {
if let file = entry as? Files.FileMetadata {
let ext = (file.name as NSString).pathExtension
switch ext {
case "txt", "mrc", "zwo":
// print("filename:\(file.name) ext:\(ext) path:\(file.pathLower)")
files.append(file.pathLower!)
default:
continue
}
}
}
files = files.sorted(by: { $0 < $1 } )
// print("Contents of Workout Folder:\(files)")
userCompletionHandler(files)
} else if let error = error {
print(error)
}
}
}
calling it
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
results in:
value:["/1-01.txt", "/1-17.txt"]
value[0] : /1-01.txt
value.count : 5
however when changing it
from Return -> Void
to Return -> Any
trying to execute the below will have swift telling me:
"Missing return in a closure expected to return 'Any'"
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
I can only allowed to execute 1 print statement. Just want to understand the difference.
Note: Asked this
Return list of files from function call
and was given this as possible answer
How I can return value from async block in swift
Specifying a closure of ([String]) -> Any means that the closure is going to return something, and it is of type Any. But in your examples, (a) your closures are not returning anything at all; and (b) the dropboxWorkoutList does not appear to need/use an object returned by the closure supplied by the caller, anyway. This is a “completion handler closure” pattern, and completion handler closures almost always have a Void return type.
I actually want to use the return values from dropboxWorkoutList to populate a tableview which I've not coded yet
OK, then I think you may be conflating the closure parameter (what dropboxWorkoutList will be supplying back to the caller) and the closure’s return value (the far less common scenario, where the caller needs to supply dropboxWorkoutList some value based upon the closure’s parameter). In this case, you want the former (the closure parameter), not the latter (the closure’s return value).
You likely do not want to change the closure to be ([String]) -> Any, at all, but rather leave it as ([String]) -> Void. The caller should just take the parameter of the closure and use that. For example:
dropboxFunc.dropboxWorkoutList { values in
self.strings = values // update whatever model object your table view data source is using
self.tableview.reloadData() // and tell the table view to reload the data
}
In short, your question here (and in that other post) was, effectively, “how do I return a value using a closure?”, to which the answer is that you should not think of it as “returning” a value. Rather, in asynchronous routines with completion handler closures, results are supplied as parameter(s) to the closure. Essentially, dropboxWorkoutList is calling a function (in this case, a closure) to say “hey, here is your data”. It is providing data to that closure, not returning data from that closure.
I have an async function that currently looks something like this
func startLoginFlow() {
IdentityProvider.shared.login { success, error in
// on success a user has completed authentication
if success {
delegate?.userIsAuthenticated()
}
// on error something wen't wrong
....
}
}
Essentially on success a delegate method is called and some action takes place as a result.
I'd like to wrap this as an observable instead. I do not have the option refactoring IdentityProvider.shared.login.
I essentially just need the observable to emit so I can subscribe and take action elsewhere using onNext.
I am currently doing the following
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(Void())
}
}
return Disposables.create()
}
}
Is this the best way to do this? I wasn't sure if I should use Observable.of and subscribe to the result of IdentityProvider.shared.login
This is how I create Observables as well. The only thing I would note is to add in the errors so you can handle your observables when it errors out, and the completion, as well, to signal that your observable is complete.
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(())
observable.onCompleted()
} else {
observable.onError(error)
}
}
return Disposables.create()
}
}
Observable.of's work in this case as well. It just emits the completed method. You can test this out yourself, if you were trying to create an Observable<String>, with both methods.
I find that doing Observable.create is beneficial here as you're doing network requests and that you can control how you want your observables to error, fail, or be completed.
Someone here gave a pretty good example as well:
Rxswift What difference between Observable.of and Observable<String>.create
I'm trying to get the value passed to a completion handler to test against for unit testing. It is not a normal setup for a completion handler since the completion handler is a var:
typealias ResultType = Result<Data>
typealias CompletionType = (ResultType) -> ()
var onCompletion: CompletionType// this is the completion handler
open override func finished(_ errors: [NSError]) {
self.urlSession.invalidateAndCancel()
self.onCompletion(Result.failure(EComError.networkError(.notReachable)))
}
}
I'm trying to compare the var: onCompletion with the
self.onCompletion(Result.failure(EComError.networkError(.notReachable)))
but when I try and print the var: onCompletion after the function has been it returns:
Optional((Function))
I'm trying to write a test case like this:
func test_finishedResult(){
sut?.finished([NSError(code: OperationErrorCode.executionFailed)])
XCTAssertEqual(sut?.onCompletion(Result.failure(EComError.networkError(.notReachable))), sut?.onCompletion)
}
but it returns the error:
Cannot convert value of type 'DownloadOperation.CompletionType?' (aka
'Optional<(Result) -> ()>') to expected argument type '()?'
where
DownloadOperation
is the name of the class the function is in.
Whats the best way to unit test the that the var:
onCompletion
is equal toself.onCompletion(Result.failure(EComError.networkError(.notReachable)))?
With your test code you are not comparing the value passed to the completion, but instead the function pointers of your callbacks.
What you probably want is something like:
func test_finishedResult() {
// Set your callback here
sut?.onCompletion = { result in
// Compare the result here
XCTAssertEqual(Result.failure(EComError.networkError(.notReachable)), result)
}
// Trigger the callback with the finished method
sut?.finished([NSError(code: OperationErrorCode.executionFailed)])
}
I have a Completable being returned from a simple function.
This is not an async call, so I just need to return a succcessful completion or error depending on a conditional (using Rx here so I can tie into other Rx usages):
func exampleFunc() -> Completable {
if successful {
return Completable.just() // What to do here???
} else {
return Completable.error(SomeErrorType.someError)
}
}
The error case works pretty easily, but am having a block on how to just return a successful completable (without needing to .create() it).
I was thinking I just need to use Completable's .just() or .never(), but just is requiring a parameter, and never doesn't seem to trigger the completion event.
.empty() is the operator I was looking for!
Turns out, I had mixed up the implementations of .never() and .empty() in my head!
.never() emits no items and does NOT terminate
.empty() emits no items but does terminates normally
So, the example code above works like this:
func exampleFunc() -> Completable {
if successful {
return Completable.empty()
} else {
return Completable.error(SomeErrorType.someError)
}
}
Here is the documentation on empty/throw/never operators.
I would be more inclined to do the following:
func example() throws {
// do something
if !successful {
throw SomeErrorType.someError
}
}
Then in order to tie it into other Rx code, I would just use map as in:
myObservable.map { try example() }
But then, mapping over a Completable doesn't work because map's closure only gets called on next events. :-(
I tend to avoid Completable for this very reason, it doesn't seem to play well with other observables. I prefer to use Observable<Void> and send an empty event before the completed...
Something like this:
let chain = Observable<Void>.just()
let foo = chain.map { try example() }
foo.subscribe { event in print(event) }
I'm new to swift and not overly experienced in any major threading work, so I'm try to improve my skills a bit, hoping for a bit of help or guidance. This is one concept I can't seem to figure out.
I have a class which does communication via an Input and Output stream, the app sends via the output stream, and reads the result from the input stream. Right now I have created the method to send async via callbacks, but I would like the ability to send synchronously, and handle the threading at a higher level.
Currently my communication method looks something like this:
typealias CommunicationCallback = ((CommunicationResult) -> Void)
func sendAsync(send message: String, closure: #escaping CommunicationCallback) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else {
return
}
let messagePair = MessagePair(SendReceiveResult(sent: message, received: nil),
closure)
strongSelf.writeQueue.enqueue(messagePair)
strongSelf.writeFromQueue() //Calls the CommunicationCallback closure after it has read result
}
}
func sendSyncronized(send message: String) -> CommunicationResult {
???
}
Is there a general way to wrap a async all like that above?
In case I am thinking of this totally wrong, what Id' like in some cases to simply be able to call
let result = sendSyncronized("foo")
instead of doing
send("foo", closure: { result in
switch result {
case .a:
foo()
base .b:
bar()
}
})
everytime, as there are some cases in which I need to do many sequential writes while waiting for the previous result.
Any help is welcome!
Maybe you can use an optional return type.
typealias CommunicationCallback = ((CommunicationResult) -> Type?)
return nil in async method and the value in synchronous method. Use if let to check if the optional binding is successful. If not, you need to get the value from closure anyway.