I would like somehow to asynchronously validate the pin in ABPadLockScreen since pins are not saved on the device. I'm using Alamofire for http requests along with PromiseKit to have promises.
I have tried to use AwaitKit
but the problem is that i get into a deadlock.
I have also tried to use semaphore
as well, but the result is the same. Since i can't change the ABPadLock method to accommodate something like a completion handler i need some solution, it doesn't matter if it blocks the main thread, just that it works.
Alamofire request method:
public func loginAsync(pinCode: String?, apiPath: String?) -> Promise<LoginResult>{
return Promise { fullfil, reject in
let params = [
"Pin": pinCode!
]
Alamofire.request(.POST, "\(baseUrl!)/\(apiPath!)", parameters: params).responseObject{(response: Response<LoginResult, NSError>) in
let serverResponse = response.response
if serverResponse!.statusCode != 200 {
reject(NSError(domain: "http", code: serverResponse!.statusCode, userInfo: nil))
}
if let loginResult = response.result.value {
fullfil(loginResult)
}
}
}
}
ABPadLockScreen pin validation method:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
let pinCode = pin!
let defaults = NSUserDefaults.standardUserDefaults()
let serverUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: serverUrl)
service.loginAsync(pinCode, apiPath: "sw/airpharm/login").then { loginResult -> Void in
if loginResult.code == HTTPStatusCode.OK {
AirpharmService.id = loginResult.result!.id
}
}
return false // how do i get the result of above async method here?
}
With semaphore:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
var loginResult: LoginResult?
let defaults = NSUserDefaults.standardUserDefaults()
let baseUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: baseUrl)
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
service.loginAsync(pin, apiPath: "sw/airpharm/login").then { loginResultRaw -> Void in
loginResult = loginResultRaw
dispatch_semaphore_signal(semaphore)//after a suggestion from Josip B.
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return loginResult != nil // rudimentary check for now
}
EDIT:
After a suggestion from Josip B. i added semaphore signal in then, but it still doesn't work
AirpharmService
is a class that contains a static property called id, and the Alamofire
request method.
ABPadLockScreen
pin validation is done on main thread in a ViewController
SOLVED EDIT:
Thanks to everyone for being so patient with me and my, not so good, knowledge of swift and iOS. There are a lot of good answers here and in the end i just went with, in my opinion, simplest solution. I listened to Losiowaty-s suggestion; implemented a spinner and manually dismissed the lock screen when i get the response from the server. I've used a SwiftSpinner. The final solution looked like this:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
let baseUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: baseUrl)
SwiftSpinner.show("Logging in. Please wait...")
service.loginAsync(pin, apiPath: "sw/airpharm/login").then { loginResult -> Void in
if loginResult.code == HTTPStatusCode.OK {
SwiftSpinner.hide()
AirpharmService.id = loginResult.result!.id
self.unlockWasSuccessfulForPadLockScreenViewController(padLockScreenViewController)
} else if loginResult.code == HTTPStatusCode.Unauthorized {
let toast = JLToast.makeText("Invalid pin, please try again", duration: 5)
toast.show()
SwiftSpinner.hide()
} else {
let toast = JLToast.makeText("\(loginResult.code) sent from server. Please try again.", duration: 5)
toast.show()
SwiftSpinner.hide()
}
}.error { error in
let toast = JLToast.makeText("\((error as NSError).code) sent from server. Please try again.", duration: 5)
toast.show()
SwiftSpinner.hide()
}
return false
}
It's great that a lot of people tried to help you make your asynchronous call synchronous. Personally I agree with @OOPer and his comment that you should redesign your code, especially after looking through
ABPadLockScreen
code. It seems they don't support asynchronous pin verification, which is a shame. Also it seems from their github repo that the original author has abandoned the project, for the time being at least.I'd attempt to solve your issue like this :
With this approach your users will know that something is going on (the spinner, you can use for example
SVProgressHUD
as a drop-in solution) and that the app didn't hang. It is quite important, ux-wise, as users with poor connection could get frustrated thinking the app hanged and close it.There is a potential problem though - if the padlock screen shows some kind of "wrong pin" message when you return
false
from the delegate method, it could be visible to the user creating some confusion. Now this can be tackled by making/positioning the spinner so that it obscures the message, though this is a very crude and unelegant solution. On the other hand, maybe it can be customised enough so that no message gets shown, and you'd display your own alert after server side verification.Let me know what you think about this!
Based on the comments we exchanged, it sounds like the endless wait when you tried using a semaphore is because the semaphore signal is never being sent. Let's try to simplify this down to the minimum code needed to test:
This should either:
crash because you are force unwrapping several variables (e.g.
baseUrl!
,loginResult.result!.id
, etc. and one of them is nilreturn true if you got a valid
LoginResult
return false if you didn't get a valid
LoginResult
But theoretically, it shouldn't deadlock.
Try this:
add dispatch_group:
Then after calling the function, wait for this group:
And release the group after the function returns an answer:
One problem could be it is blocking the
main thread
withdispatch_semaphore_wait
, so theAlamofire response
never get a chance to run on themain thread
and you're deadlocking.The solution to this could be create another queue on which the
Alamofire
completion handler is dispatched.For example:
If you making a request like this:
You can modify this call to dispatch completion handler in your defined
queue
like this:A simplified version for test.
I've tried to make the
ABPadLockScreen
support asynchronous pin verification.I've modified
ABPadLockScreenViewController
. Added a newABPadLockScreenViewControllerDelegate
protocol methodshouldValidatePinManuallyForPadLockScreenViewController:
.Added a new instance method
processUnlock
Modified the
processPin
methodNow in your viewController implement
shouldValidatePinManuallyForPadLockScreenViewController
Made a demo project at https://github.com/rishi420/ABPadLockScreen
See the swift demo example.
I think semaphore can help. Here is a usage example:
This is a function comes from AFNetworking. The method
getTasksWithCompletionHandler
is a method ofNSURLSession
which willSemaphore_wait will ensure that tasks has be assigned with proper value. This way you can get the asynchronously request result.