I am trying to follow the example in the Swift docs for a trailing closure.
This is the function:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
print("we do something here and then go back")//does not print
}
And I call it here.
print("about to call function")//prints ok
someFunctionThatTakesAClosure(closure: {
print("we did what was in the function and can now do something else")//does not print
})
print("after calling function")//prints ok
The function, however, is not getting called. What is wrong with the above?
Here's the Apple example:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here }
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here })
Here is your example fixed:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
print("we do something here and then go back")
// don't forget to call the closure
closure()
}
print("about to call function")
// call the function using trailing closure syntax
someFunctionThatTakesAClosure() {
print("we did what was in the function and can now do something else")
}
print("after calling function")
Output:
about to call function
we do something here and then go back
we did what was in the function and can now do something else
after calling function
Docs isn't very clear in explanation you need
print("1")
someFunctionThatTakesAClosure() { // can be also someFunctionThatTakesAClosure { without ()
print("3")
}
func someFunctionThatTakesAClosure(closure: () -> Void) {
print("2")
/// do you job here and line blow will get you back
closure()
}
the trailing closure is meant for completions like when you do a network request and finally return the response like this
func someFunctionThatTakesAClosure(completion: #escaping ([String]) -> Void) {
print("inside the function body")
Api.getData {
completion(arr)
}
}
And to call
print("Before calling the function")
someFunctionThatTakesAClosure { (arr) in
print("Inside the function callback / trailing closure " , arr)
}
print("After calling the function")
what you missed to read
Related
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.
In the Button action, I call getLatLongValues method, It will return completion property after API success.
Hear problem is, If I click N th times in button action, getLatLongValues method execute N times.
Like I click the button in 1st time getLatLongValues execute 1 time, I'm click 2nd time not two times getLatLongValues method execute 2 times.
#IBAction func updateDeliveryAddress() {
guard let address = self.addressTextField.text else { return }
self.getLatLongValues(address, true, viewModel) { success in
if success {
//Success
} else {
// Error
}
}
}
func getLatLongValues(address: String, setAsDefault: Bool, viewModel:ViewModel, completion: #escaping (_ success: Bool) -> Void) {
viewModel.location.subscribe(onNext: { [weak self] results in
guard self != nil else { return }
if let result = results {
completion(true) // Success
}
}).disposed(by: disposeBag)
viewModel.fetchLocation(address: address)
}
Why getLatLongValues Execute N times?
because each time you are creating a new subscription.
a subscription is not a completion handler that gets executed once.
viewModel.location.subscribe(onNext: { [weak self] results in
should only be called once, and on each location update, you get the result in the block
If you want the completion handler to only be called once, you should set a flag:
var completionHandlerExecuted = false /// false at first
func getLatLongValues(address: String, setAsDefault: Bool, viewModel:ViewModel, completion: #escaping (_ success: Bool) -> Void) {
if completionHandlerExecuted == false {
completionHandlerExecuted = true /// set to true, so it won't be called again
viewModel.location.subscribe(onNext: { [weak self] results in
guard self != nil else { return }
if let result = results {
completion(true) // Success
}
}).disposed(by: disposeBag)
viewModel.fetchLocation(address: address)
}
}
Completion handlers are like any other instruction you put in your functions.
Every time getLatLongValues is called, you are doing a subscribe, which will call the completion handler once it's finished.
I want to change the API request code written using the closure to RxSwift.
For example, I would like to make rxGetList() function using getList() function.
// This function cannot be modified.
func getList(success: #escaping ([String]) -> Void,
failure: #escaping (Error) -> Void) {
// Request to Server...
}
func rxGetList() -> Observable<String> {
// Using getList() function
// TODO
}
What code should I write in TODO section?
Please give me some advice.
The easiest way to meet your expectations is to use something like this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
for everyString in result {
observer.onNext(everyString)
}
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Note that you have [String] specified as an input type of your success closure. If it's not a typo then above code fits. If you want one String instead, it's as simple as this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
observer.onNext(result)
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Petr Grigorev's answer is the correct one, but if you want to have fun with some extreme function composition, here's a more advanced way to handle it:
let rxGetList = Observable.create(rx_(getList(success:failure:)))
.flatMap { Observable.from($0) }
func rx_<A>(_ fn: #escaping (#escaping (A) -> Void, #escaping (Error) -> Void) -> Void) -> (AnyObserver<A>) -> Disposable {
{
fn(singleObserve($0), $0.onError)
return Disposables.create()
}
}
func singleObserve<A>(_ observer: AnyObserver<A>) -> (A) -> Void {
{
observer.onNext($0)
observer.onCompleted()
}
}
I'm not sure about actually using the above, but if you have a lot of functions that you want to wrap, it may help reduce the amount of code you have to write.
I am trying to get user data from a server. The application does not have to show any views until the data is loaded.
I read about typealias and I don't understand how to use it.
What I want: when data is loaded, move on to next step. If failed, load data again.
Here's how I declare typealias
typealias onCompleted = () -> ()
typealias onFailed = () -> ()
Here is my request code
func getUserData(_ completed: #escaping onCompleted, failed: #escaping onFailed){
let fullURL = AFUtils.getFullURL(AUTHURL.getUserData)
AFNetworking.requestGETURL(fullURL, params: nil, success: {
(JSONResponse) -> Void in
if let status = JSONResponse["status"].string {
switch status{
case Status.ok:
completed()
break
default:
failed()
break
}
}
})
}
But how could I use this on my view controller when calling getUserData?
Assuming your custom AFNetworking.requestGETURLs completion handler is called on the main queue:
func viewDidLoad() {
super.viewDidLoad()
getUserData({
//do somthing and update ui
}) {
//handle error
}
}
Edit:
How I understand your comment, you actually want to name your completion and error block parameters. If so, change the method to :
func getUserData(completion completed: #escaping onCompleted, error failed: #escaping onFailed){ ... }
and call it like this:
getUserData(completion: {
//do somthing and update ui
}, error: {
//handle error
})
Say I have a function that has a completion handler, then calls another function, with a completion handler like this:
func register(withCompletion complete: #escaping (() -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
if error != nil {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
else {
complete()
}
})
}
In the event of an error, it will segue away with calling complete().
Am I ok to segue away like this, without calling complete()? I do not need to return from this function as I'm now wanting to go to another View Controller.
Thanks.
This is a bad idea. A completion handler should be called no matter what. The caller is waiting for a response. It wants to know when it is done. That's the whole point of having a completion handler.
In your case (like many other cases), it would be much better if the completion handler accepted a boolean parameter (and/or an error parameter). This way the completion handler provides some basic information about the success or failure of the method.
Try like this
override func viewDidLoad() {
super.viewDidLoad()
register { (error) in
if error == nil {
// do what you want in success case
} else {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
}
}
func register(withCompletion complete: #escaping ((_ error: Error?) -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
complete(Error)
})
}
Thanks:)