How to call a function instead of implementing closure - ios

I have this function
func performNetworkRequest(timeout: Int, completion: (Result<Response, Error>) -> Void) {
// ....
}
I want to be able to call performNetworkRequest without writing the closure, but to pass an function to do the logic's over there.
something like :
func onNetwotkResponse(result: (Result<Response, Error>) -> Void) {
}
performNetworkRequest(timeout: 60, completion: onNetwotkResponse)
How can I achieve this?
Thanks

The type of closure is:
(Result<Response, Error>) -> Void
which means
A function that accepts a Result<Response, Error> and returns Void.
This means that your onNetwotkResponse function should accept Result<Response, Error>, rather than (Result<Response, Error>) -> Void:
func onNetwotkResponse(result: Result<Response, Error>) {
}

Related

Completion handler in function

I have a function that looks like this, and I have tried to add a completionHandler in the code below:
func getValueFromAPI(completionHandler: (_ result: Bool) -> Void){
apii.getVehicle(id!).done {
(vehicle: Vehicle) -> Void in
print("ggg.state: \(vehicle.state!)")
print("ggg.state: \(vehicle.displayName!)")
apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
let entryBattery = (extendedVehicle.chargeState?.batteryLevel)!
let entryCarState = (extendedVehicle.state)!
print("entryBattery: \(entryBattery)")
print("entryCarState: \(entryCarState)")
completionHandler(true)
}.catch { (error) in
print("ERROOOOR: \(error)")
}
}.catch { error in
print("errorr: \(error)")
}
}
I have already tried to add a complete handler, but I get the following error on these lines:
Line: apii.getVehicle(id!).done {
Error: Escaping closure captures non-escaping parameter 'completionHandler'
Line: apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
Error: Escaping closure captures non-escaping parameter 'completionHandler'
What am I doing wrong here, and how can I fix this?
I am using Swift 5.
You need to declare your completionHandler to be an escaping closure. E.g.:
func getValueFromAPI(completionHandler: #escaping (Bool) -> Void) {
...
}
Note the #escaping qualifier.

Store completion handler and call success/error later

I have a protocol/function in my class which is below,
func getMovieList(completionHandler: #escaping (Result<[String], Error>) -> Void) { }
When the above method is called, I want to store the completion handler and call the success/error in the latter part.
I tried creating a typealias like below,
typealias AlbumListCompletionHandler = (((Result<[String], Error>)) -> Void)?
And in my class,
var completionHandlerObj: AlbumListCompletionHandler
func getMovieList(completionHandler: #escaping (Result<[String], Error>) -> Void) {
completionHandlerObj = completionHandler
/...
.../
}
But I wonder how do I call the success/error blocks in completionHandlerObj, kind of struck here. Can anyone help me with this ?
It should work like this
completionHandlerObj(.success(["",""]))
completionHandlerObj(.failure(ErrorObject))

Swift Optimise nested async calls

I have this case where I need to make 3 nested async calls to receive the data I want.
So the second call needs data from the first one and the third one needs data from the second one. I do not have a lot of cases like this. Only this one and another one with only two nested call so I was thinking about a pure swift solution without any external libraries but I'm open to everything.
Since I'm using Firebase, is it better to move this logic to CloudFunctions? So to prepare it in the backend?
FirestoreService().fetchCollection(query: query) { (result: Result<[Request], Error>) in
// do stuff
FirestoreService().fetchCollection(query: query) { (result: Result<[Request], Error>) in
// do stuff
FirestoreService().fetchDocument(documentReference: documentReference) { (result: Result<Package, Error>) in
// finish
}
}
}
}
If you don't to used 3rd party library, then probably you want to consider wrap those operations inside some class, and utilise closure in imperative way.
here is the sample:
class CustomFirestoreHandler {
private var onFetchFirstQueryArrived: ((Result<[Request], Error>) -> ())? = nil
private var onFetchSecondQueryArrived: ((Result<[Request], Error>) -> ())? = nil
private var onFetchDocumentArrived: ((Result<Package, Error>) -> ())? = nil
init() {
onFetchFirstQueryArrived = { [weak self] (result: Result<[Request], Error>) in
self?.executeSecondQuery()
}
onFetchSecondQueryArrived = { [weak self] (result: Result<[Request], Error>) in
self?.executeFetchDocument()
}
}
func executeQuery(completion: #escaping (Result<Package, Error>) -> ()) {
self.onFetchDocumentArrived = completion
FirestoreService().fetchCollection(query: query) { [weak self] (result: Result<[Request], Error>) in
// validate if some error occurred and do early return here, so that we don't need necessarily call second query.
if (result.error == whatever) {
self?.onFetchDocumentArrived?(result)
return
}
self?.onFetchFirstQueryArrived?(result)
}
}
private func executeSecondQuery() {
FirestoreService().fetchCollection(query: query) { [weak self] (result: Result<[Request], Error>) in
// validate if some error occurred and do early return here, so that we don't need necessarily call fetch document.
if (result.error == whatever) {
self?.onFetchDocumentArrived?(result)
return
}
self?.onFetchSecondQueryArrived?(result)
}
}
private func executeFetchDocument() {
FirestoreService().fetchDocument(documentReference: documentReference) { (result: Result<Package, Error>) in
self?.onFetchDocumentArrived?(result)
}
}
}
And here's the usage of CustomFirestoreHandler above :
let firestoreHandler = CustomFirestoreHandler()
firestoreHandler.executeQuery { (result: Result<Package, Error>) in
// Handle `result` here...
}
I know it look complicated, but this is the only way I think (CMIIW) at the moment to prevent pyramid of dooms since swift doesn't have async await style(just like javascript does).

OperationQueue / DispatchGroup and recursion

I have a problem understanding how to use GCD when using asynchronous, recursive calls to the API.
Below is one of the three similar methods that contains the same logic, just for different data and API endpoint. If there is no next page request the method should finish and next method should start.
How would I make sure that fetchItems2 gets called after fetchItems1 finishes, and fetchItems3 after fetchItems2?
private func fetchItems1(completion: #escaping (Error?) -> Void) {
var _items = [Item]()
func handleReceivedItemsPage(_ page: PagingObject<Item>, _completion: ((Error?) -> Void)?) {
let newItems = page.items!
_tracks.append(contentsOf: newTracks)
if page.canMakeNextRequest {
page.getNext(success: { nextPage in
handleReceivedItemsPage(nextPage)
}) { nextError in
_completion?(nextError)
}
} else {
// Finished, next method can now start
self.items = _items
_completion?(nil)
}
}
API.getSavedItems(success: { page in
handleReceivedItemsPage(page, _completion: completion)
}, failure: completion)
}
private func fetchItems2(completion: #escaping (Error?) -> Void)) { ... }
private func fetchItems3(completion: #escaping (Error?) -> Void)) { ... }
You can keep an extra variable that keeps track of when the API calls are complete. In the completion block, increment this variable. Then, when the variable reaches the amount of API calls complete, perform your task.
I would use DispatchGroup:
public void FetchItems(completion: #escaping (Error?) -> Void) {
let group = DispatchGroup()
group.enter()
fetchItems1() { error in
completion(error)
group.leave()
}
group.wait()
group.enter()
fetchItems2() { error in
completion(error)
group.leave()
}
// 3rd function call
}
Code after group.wait() is not called until the number of group.enter() and group.leave() invocations is equal.

Swift incorrect closure in function declaration

I am trying to use Moya Networking in my project. I am using this example. The example is making moya request to connect to server in view controller on Line 56 which is below and using callback methods in Line 72 and Line 78
func uploadGiphy() {
provider.request(MultiTarget(Giphy.upload(gif: Giphy.animatedBirdData)),
callbackQueue: DispatchQueue.main,
progress: progressClosure,
completion: progressCompletionClosure)
}
I want to write this function in NetworkHelper.swift instead of my view controller but, use its two callback methods (Line 72 and Line 78) in my view controller.
So I wrote the function in NetworkHelper:
static func getUsers(amount:Int=2,
gender:Gender = .Male,
success successCallback: #escaping ([UserModelMain]) -> Void,
error errorCallback: #escaping (Swift.Error) -> Void,
failure failureCallback: #escaping (Moya.MoyaError) -> Void,
progress progressClosure: #escaping (Moya.ProgressResponse) -> Void,
progressCompletion progressCompletionClosure: #escaping (Moya.Completion) -> Void)
{
provider.request(.getUsers(amount: amount, gender: gender),
callbackQueue: DispatchQueue.main,
progress: progressClosure,
completion: progressCompletionClosure)
}
Its showing error:
Cannot convert value of type '((Result) -> Void)
-> Void' to expected argument type 'Completion' (aka '(Result) -> ()')
I think I am writing my function getUsers wrong. I messed it up. I am weak at closures.
Kindly help.
Source Code of request function from Moya networking library:
/// Designated request-making method.
Returns a `Cancellable` token to cancel the request later.
#discardableResult
open func request(_ target: Target,
callbackQueue: DispatchQueue? = .none,
progress: ProgressBlock? = .none,
completion: #escaping Completion) -> Cancellable {
let callbackQueue = callbackQueue ?? self.callbackQueue
return requestNormal(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}
Moya.Completion is already a completion block. You just need to pass Moya.Completion as an argument, instead of (Moya.Completion) -> Void.
progressCompletion progressCompletionClosure: #escaping Moya.Completion)
Your current code, like the error suggest, is sending ((Result) -> Void) -> Void

Resources