I tried to do error handling via throw completion handler.
When i call completion i get compiler error "Argument passed to call that takes no arguments". How can i fix my code?
private func executeRefreshTokenRequest(_ completion: #escaping () throws -> Void) {
DispatchQueue.global(qos: .userInitiated).sync {
guard let refreshToken = currentRefreshToken else {
completion({ throw RefreshTokenError.notFounded })
// Argument passed to call that takes no arguments
}
var refreshTokenUrlRequest:CustomURLRequest
do {
refreshTokenUrlRequest = try AuthService.refresh(token: refreshToken).doUrlRequest()
} catch (let error) {
completion({ throw UrlRequest.notDone })
// Argument passed to call that takes no arguments
}
execute(refreshTokenUrlRequest, PlaceToken.self) { [weak self] (placeToken, error) in
if error != nil {
self?.cancelAllRequests()
NotificationCenter.default.post(name: Notification.Name("AuthorizationFail"), object: nil)
} else {
Cache.currentAuthToken = placeToken
}
completion()
}
}
Related
I have type aliased the completion handler as below, having a completion handler inside the completion handler.
typealias voidCompletionHandler = () -> Void
typealias dataCompletionHandler = (Data, [String: String], voidCompletionHandler) -> Void
I wanted to use computed properties to define the completion handler.
var completion: dataCompletionHandler {
return { data, _, completion in
jsonDataProcess(data: data) { json in // ERROR --> This is SwiftyJSON process with completion handler.
if json != nil {
Country.instantiate(json: json!) {. // ERROR --> The is Core Data process with completion handler
completion()
}
}
}
}
With the above code, I get "Escaping closure captures non-escaping parameter 'completion'.
The sub processes also has #escaping so, they are not the problem. It is the completion handler inside the dataCompletionHandler that I do not know how to define it as #escaping.
// Inside JSON Handler class
func jsonDataProcess(data: Data, completion: #escaping jsonCompletionHandler) {
do {
let json = try JSON(data: data)
if json["Status"].string == "Success" {
completion(json["Data"])
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
// Inside Country Class
static func instantiate(json: JSON, completion: #escaping voidCompletionHandler) {
container.performBackgroundTask { (context) in
let country = Country(context: context)
country.name = json["name"].string
country.iso2digit = json["alpha-2"].string
country.iso3digit = json["alpha-3"].string
country.region = json["region"].string
try? context.save()
print("Country.instantiate : \(String(describing: json["alpha-2"].string))")
completion()
}
}
How do I make the completion handler inside the completion handler as #escaping when using as computed property?
Make the escaping declaration part of the typealias.
I have the following functions:
func getUserProfile() -> AnyPublisher<UserProfileDTO?, Error> {
return Future { [unowned self] promise in
do {
if let data = KeychainWrapper.standard.data(forKey: profileKey) {
let profileDTO = try PropertyListDecoder().decode(UserProfileDTO.self, from: data)
setCurrentSession(profileDTO)
promise(.success(profileDTO))
}
else {
promise(.success(nil))
}
}
catch {
// Delete current UserProfile if cannot decode
let _ = KeychainWrapper.standard.removeAllKeys()
promise(.failure(error))
}
}.eraseToAnyPublisher()
}
func connect(userProfile: UserProfileDTO) -> AnyPublisher<UserProfileDTO, Error> {
return Future { promise in
SBDMain.connect(withUserId: userProfile.email) { (user, error) in
if let error = error {
promise(.failure(error))
}
else {
promise(.success(userProfile))
}
}
}.eraseToAnyPublisher()
}
What I want to do is to first call the getUserProfile() method and if the return value in not nil then call the connect() method. However, if the getUserProfile() has nil response it does not need to call the connect() and it should just return the nil response. Both these methods needs to be called from the autoLoginUser() method.
The problem I'm having right now is figuring out how to do this in a clean swift way without writing too much nested statements.
I tried to use flatMaps but it didn't workout the way I expected. Any help is much appreciated.
A solution I've been working on at the moment is this. But it doesn't quite work.
func autoLoginUser2() -> AnyPublisher<UserProfile?,Error> {
getUserProfile()
.tryMap { [unowned self] in
if let currentProfile = $0 {
return connect(userProfile: currentProfile)
.tryMap {
//Map from UserProfileDTO --> UserProfile
return UserProfileDTOMapper.map($0)
}
}
return nil
}.eraseToAnyPublisher()
}
With some adjustment for used types and error types this should work. First you ask for the profile, then you force unwrap the profile if it is nil you throw an error that will be sent to the sink as a failure.
If the profile is present you call connect.
getUserProfile()
.tryMap { userDTO -> UserProfileDTO in
if let id = userDTO {
return id
}
throw MyError.noProfileDT
}
.flatMap { id in
connect(id)
}
.sink {
//.....
}
If you change the signature of connect to return Optional profile:
func connect(userProfile: UserProfileDTO) -> AnyPublisher<UserProfileDTO?, Error>
You could do something like this:
getUserProfile()
.flatMap { userProfile -> AnyPublisher<UserProfileDTO?, Error> in
if let userProfile = userProfile {
return connect(userProfile: userProfile)
.eraseToAnyPublisher()
} else {
return Just<UserProfileDTO?>(nil)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}
//.sink etc.
If you don't need the publisher to emit nil, you could leave the connect signature as is and use compactMap:
getUserProfile()
.compactMap { $0 }
.flatMap {
connect(userProfile: $0)
.eraseToAnyPublisher()
}
How to use completion handlers/dispatchqueue within while loops?
I have this method called getHub() which is a completion handler as I would like code to be executed after it has finished with the relevant values. I call this when a user presses a button:
SetupAPI().getHub(completion: { response, error in
print("testing")
print(response)
print(error)
})
(The code above is where all of the code below should end at)
It calls my API and if the API returns an error/a value that I wasn't expecting, or if Almofire couldn't do the request for some reason, then it adds one onto the tries variable. The max amount of tries allowed is 3 given by the maxTries variable. If the tries variable is equal to the maxTries variable, then a bool timeout is set to true. If the tries variable is below the maxTries variable then the code waits timeoutInSeconds - which is 10 seconds - amount of time before exiting the while loop, which should run the code once more.
Similarly, If the right value is returned from fetching the data from my API then a bool found is set to true.
If either of these variables are true, then the while loop breaks. And an error is sent back to the completion handler for the code above (which then allows me to tell the user that something has gone wrong).
However, when I run it, the completion handler above is not finished, and the code just runs through the while loop and called function over and over again as my console fills with starting and fetching via my two print statements for debugging in below code. What's the problem, can I use a DispatchQueue/ completion handlers in this situation?
Function that gets called via above code:
func getHub(completion: #escaping (Bool, Error?) -> Void) {
var tries = 0
let maxTries = 3
let timeoutInSeconds = 10.0
var found = false
var timeout = false
while !found || !timeout{
print("starting")
getHubCallAPI(completion: {status, error in
if(error == nil){
print(status)
if (status == "No documents found"){
if(tries >= maxTries){
print("Tired too many times")
timeout = true
return completion(false, nil)
}
tries += 1
DispatchQueue.main.asyncAfter(deadline: .now() + timeoutInSeconds){
return
}
}else{
found = true
print("Hub found")
return completion(true, nil)
}
}else{
print("error")
return completion(false, error)
}
})
}
}
Function that calls the API and returns it back to the function above ^^:
func getHubCallAPI(completion: #escaping (String, Error?) -> Void) {
print("fetching")
AF.request("https://discovery.ellisn.com", encoding: URLEncoding.default).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
if(response.error != nil){
return completion("", response.error)
}
if let data = response.data, let status = String(data: data, encoding: .utf8) {
return completion(status, nil)
}
}
}
Any questions, or more clarification needed, then just ask. Thanks.
You can try the following:
func getHub(triesLeft: Int = 3, completion: #escaping (Bool, Error?) -> Void) {
let timeoutInSeconds = 1.0
print("starting")
getHubCallAPI(completion: { status, error in
if error == nil {
print(status)
if status != "No documents found" {
print("Hub found")
return completion(true, nil)
}
} else {
print("error")
return completion(false, error) // comment out if the loop should continue on error
}
if triesLeft <= 1 {
print("Tried too many times")
return completion(false, nil)
}
DispatchQueue.main.asyncAfter(deadline: .now() + timeoutInSeconds) {
getHub(triesLeft: triesLeft - 1, completion: completion)
}
})
}
And just call it once like this:
getHub(triesLeft: 2, completion: { ... })
Note that unless you need it for some other reason, there is no need to return (Bool, Error?). And the second parameter is always nil - you may want to propagate your error. You could in theory return (String?, Error?).
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 7 years ago.
My question is simple but after many research and tests I do not succeed to wait for a function end before continuing the flow.
Example :
print("Before stuff")
do_stuff {
print("After stuff")
}
func do_stuff(onCompleted: () -> ()) {
let postEndpoint: String = "http://localhost:8080/users/1234567890987654"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on /users/1234567890987654")
print(error)
return
}
// parse the result as JSON
let user: NSDictionary
do {
user = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
// Means that user does not exist
return
}
print("The user is: " + user.description)
})
task.resume()
onCompleted()
}
How to wait do_stuff() end before the second print?
Thanks for your help, I think I miss something.
Thomas
There is something fundamental that you haven't understood. It's not actually the function, which is executing. It's the closure defined inside the function. What you need to wait is the closure to be called when the request has completed. And in fact, you should not wait, but assign an another closure outside the function to be called from the closure inside the function.
print("Before stuff")
do_stuff {
// Now the "function" has completed.
print("After stuff")
}
func do_stuff(onCompleted: () -> ()) {
let task = session.dataTaskWithRequest(urlRequest) { data, response, error in
...
onCompleted()
}
}
You could always use a completion handler for the do_stuff() function:
func do_stuff(completion: (() -> Void)?) -> () {
...
if completion != nil {
return completion!() // Return completion where you want you "after stuff" declarations to run.
}
}
And call the function as such:
do_stuff({
print("After stuff")
})
I'm trying to add a throws to my existing function with a completion handler but I keep getting a warning saying no calls throwing functions occur within try expression. In the section where I throw the errors, I get an error saying
invalid conversion from throwing function of type '() throwing -> Void' to non-throwing function type.
enum LoginError: ErrorType {
case Invalid_Credentials
case Unable_To_Access_Login
case User_Not_Found
}
#IBAction func loginPressed(sender: AnyObject) {
do{
try self.login3(dict, completion: { (result) -> Void in
if (result == true)
{
self.performSegueWithIdentifier("loginSegue", sender: nil)
}
})
}
catch LoginError.User_Not_Found
{
//deal with it
}
catch LoginError.Unable_To_Access_Login
{
//deal with it
}
catch LoginError.Invalid_Credentials
{
//deal with it
}
catch
{
print("i dunno")
}
}
func login3(params:[String: String], completion: (result:Bool) throws -> Void)
{
//Request set up
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json
{
let userID = parseJSON["user_id"] as? Int
let loginError = parseJSON["user_not_found"] as? String
let validationError = parseJSON["invalid_credentials"] as? String
let exception = parseJSON["unable_to_access_login"] as? String
var responseArray = [(parseJSON["user_id"] as? Int)]
if userID != nil
{
dispatch_async(dispatch_get_main_queue()) {
completion(result:true)
}
}
else if loginError != ""
{
dispatch_async(dispatch_get_main_queue()){
completion(result: false)
self.loginErrorLabel.text = loginError
throw LoginError.User_Not_Found
}
}
else if validationError != ""
{
dispatch_async(dispatch_get_main_queue()){
completion(result:false)
self.validationErrorLabel.text = validationError
throw LoginError.Invalid_Credentials
}
}
else if exception != nil
{
dispatch_async(dispatch_get_main_queue()){
completion(result:false)
self.exceptionErrorLabel.text = "Unable to login"
throw LoginError.Unable_To_Access_Login
}
}
}
else
{
}
}
catch let parseError {
// Log the error thrown by `JSONObjectWithData`
})
task.resume()
}
What you can do is encapsulating the error into a throwable closure like in the following code to achieve what you want:
func login3(params:[String: String], completion: (inner: () throws -> Bool) -> ()) {
let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error -> Void in
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json {
let userID = parseJSON["user_id"] as? Int
let loginError = parseJSON["user_not_found"] as? String
let validationError = parseJSON["invalid_credentials"] as? String
let exception = parseJSON["unable_to_access_login"] as? String
var responseArray = [(parseJSON["user_id"] as? Int)]
if userID != nil {
dispatch_async(dispatch_get_main_queue()) {
completion(inner: { return true })
}
}
else if loginError != ""
{
dispatch_async(dispatch_get_main_queue()) {
self.loginErrorLabel.text = loginError
completion(inner: { throw LoginError.User_Not_Found })
}
}
else if validationError != ""
{
dispatch_async(dispatch_get_main_queue()) {
self.validationErrorLabel.text = validationError
completion(inner: {throw LoginError.Invalid_Credentials})
}
}
else if exception != nil
{
dispatch_async(dispatch_get_main_queue()){
self.exceptionErrorLabel.text = "Unable to login"
completion(inner: {throw LoginError.Unable_To_Access_Login})
}
}
}
else
{
}
}
task.resume()
}
And the you can call it like in the following way:
self.login3(dict) { (inner: () throws -> Bool) -> Void in
do {
let result = try inner()
self.performSegueWithIdentifier("loginSegue", sender: nil)
} catch let error {
print(error)
}
}
The trick is that the login3 function takes an additional closure called 'inner' of the type () throws -> Bool. This closure will either provide the result of the computation, or it will throw. The closure itself is being constructed during the computation by one of two means:
In case of an error: inner: {throw error}
In case of success: inner: {return result}
I strongly recommend you an excellent article about using try/catch in async calls Using try / catch in Swift with asynchronous closures
I hope this help you.
You're asking for X and I'm answering Y, but just in case...
There's always the possibility to add the throwing capability to your function instead of the completion handler:
func login3(params:[String: String], completion: (result:Bool) -> Void) throws {
...
}
Then you can call it from inside IBAction:
do {
try self.login3(dict) { result -> Void in
...
}
} catch {
print(error)
}
Read the below with a grain of salt. I haven't worked much with Swift 2.0 yet:
You created a "dataTask" task who's's completion handler has a throw in it, but the only actual code in your login3 method is task.resume(). The completion handler won't get executed until after login3 returns. (In fact, it's a parameter to another object, so the compiler has no idea what's going to happen with that code.)
As I understand it, the actual top-to-bottom body of your login3 method must contain a throw. Since it's an async method, you can't do that. Thus, don't make your login3 function throw. Instead have it pass an error object to it's completion handler.
In my impression this is caused by the signature of your functions. In #IBAction func loginPressed(sender: AnyObject) you don't have to use try when calling login3 as the function itself is not marked as throwing but the completion handler is. But in fact the completion closure of login3 will never throw as you execute all throwing functions inside a do {} catch {} block, so you could try to remove the throws annotation from the login3 completion closure and also call the closure if you catch an error in login3 with an according result. This way you handle all throwing functions inside of login3 in a do {} catch {} block and call the completion handler with a suitable value.
Generally I am also not aware that you can catch without a preceding do {} block like you did in the #IBAction.