等待异步请求结果

时间:2016-07-31 13:52:40

标签: ios swift asynchronous alamofire

我想以某种方式异步验证ABPadLockScreen中的引脚,因为引脚未保存在设备上。我正在使用Alamofire的http请求以及PromiseKit来承诺。

我曾尝试使用AwaitKit,但问题是我遇到了僵局。

我也试过使用semaphore,但结果是一样的。由于我无法更改ABPadLock方法来容纳类似完成处理程序的东西,我需要一些解决方案,如果它阻塞主线程无关紧要,只是它有效。

  

Alamofire请求方法:

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引脚验证方法:

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?
}
  

使用信号量:

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
}

编辑:

  

在Josip B.的建议之后我添加了信号量信号,但它仍然不起作用

AirpharmService是一个包含名为id的静态属性和Alamofire请求方法的类。

ABPadLockScreen引脚验证在ViewController

中的主线程上完成

解决编辑:

感谢所有人对我和我的耐心如此耐心,不太了解swift和iOS的知识。这里有很多好的答案,最后我认为最简单的解决方案。我听了Losiowaty的建议;当我从服务器获得响应时,实现了一个微调器并手动解除了锁定屏幕。我使用了SwiftSpinner。最终的解决方案看起来像这样:

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
}

6 个答案:

答案 0 :(得分:3)

  

......如果它阻止主线程并不重要......但问题是我遇到了僵局。

一个问题可能就是main thread阻止了dispatch_semaphore_wait Alamofire response,因此main thread永远不会有机会在Alamofire上运行,而您将陷入僵局

解决方案可以创建另一个队列,在该队列上调度Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts").validate().responseData() { response in print(response.result.value) } 完成处理程序。

例如:

如果您提出这样的请求:

queue

您可以将此调用修改为您定义的let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT) let request = Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts", parameters: .None).validate() request.response(queue: queue, responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments)) { response in print(response.result.value) } 中的调度完成处理程序,如下所示:

//MARK: Lock Screen Delegate
func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
    print("Validating Pin \(pin)")

    let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT)
    let semaphore = dispatch_semaphore_create(0)

    let request = Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts", parameters: .None).validate()
    request.response(queue: queue, responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments)) { response in
        print(response.result.value)
        //isPinValid = ???
        dispatch_semaphore_signal(semaphore);
    }

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

    return thePin == pin
    //return isPinValid
}

测试的简化版本。

DerivedKey::calculateDataEncryptionRequestKey($key, $bdk);

答案 1 :(得分:3)

很多人试图帮助您使异步调用同步,这很棒。我个人同意@OOPer和他的评论,你应该重新设计你的代码,特别是在查看ABPadLockScreen代码之后。它们似乎不支持异步引脚验证,这是一种耻辱。从他们的github回购看来,原作者至少暂时放弃了这个项目。

我试图像这样解决你的问题:

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
            self.handleLoginOk()
        } else {
            self.handleLoginFailed()
        }
    }

    // disable interaction on padlock screen 
    // indicate to user that an async operation is going on, show a spinner

    return false // always return false here
}

func handleLoginOk() {
    // dismiss the ABPadlockScreenViewController manually
}

func handleLoginFailed() {
    // dismiss the spinner indicating the async operation
    // restore user interaction to padlock screen
}

使用这种方法,您的用户将知道正在发生的事情(微调器,您可以使用例如SVProgressHUD作为插入式解决方案)并且应用程序不会挂起。这是非常重要的,因为连接不良的用户可能会因为认为应用程序被挂起并关闭而感到沮丧。 但是有一个潜在的问题 - 如果挂锁屏幕显示某种&#34;错误的引脚&#34;当您从委托方法返回false时,该消息可能会对用户可见,从而产生一些混淆。现在可以通过制作/定位微调器来解决这个问题,这样它就会模糊信息,尽管这是一个非常粗糙和不优雅的解决方案。另一方面,它可以进行足够的自定义,以便不显示任何消息,并且在服务器端验证后显示您自己的警报。

让我知道你对此的看法!

答案 2 :(得分:1)

试试这个:

添加dispatch_group:

-1

然后在调用该函数后,等待这个组:

static let serviceGroup = dispatch_group_create();

在函数返回答案后释放组:

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_group_wait(yourClass.serviceGroup, DISPATCH_TIME_FOREVER);

    return loginResult != nil // rudimentary check for now
}

答案 3 :(得分:1)

根据我们交换的评论,当您尝试使用信号量时,听起来像无休止的等待是因为信号量信号永远不会被发送。让我们尝试将其简化为测试所需的最小代码:

baseUrl!

这应该是:

  1. 崩溃,因为你强行展开了几个变量(例如loginResult.result!.idLoginResult等等,其中一个是零

  2. 如果您获得有效的LoginResult

  3. ,则返回true 如果您没有获得有效的data = {'car': {'name': 'Mazda'}, 'year': 2006};

    ,则
  4. 返回false

    但从理论上讲,它不应该死锁。

答案 4 :(得分:1)

我已尝试使ABPadLockScreen支持异步引脚验证。

我修改了ABPadLockScreenViewController。添加了新的ABPadLockScreenViewControllerDelegate协议方法shouldValidatePinManuallyForPadLockScreenViewController:

/**
  Call when pin validation is needed manually
  Call processUnlock method to validate manually if return true from this method
  */
 - (BOOL)shouldValidatePinManuallyForPadLockScreenViewController:(ABPadLockScreenViewController *)padLockScreenViewController;

添加了新的实例方法processUnlock

- (void)processUnlock {
    if ([self isPinValid:self.currentPin]) {
        [self unlockScreen];
    } else {
        [self processFailure];
    }
}

修改了processPin方法

- (void)processPin {

    if ([self.lockScreenDelegate respondsToSelector:@selector(shouldValidatePinManuallyForPadLockScreenViewController:)]) {
        if ([self.lockScreenDelegate shouldValidatePinManuallyForPadLockScreenViewController:self]) {
            return;
        }
    }

    [self processUnlock];
}

现在在viewController实现shouldValidatePinManuallyForPadLockScreenViewController

func shouldValidatePinManuallyForPadLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!) -> Bool {

    print("Requesting server...")
    Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts").validate().responseJSON() { response in
        //isPinValid = ???
        print("Request complete")
        padLockScreenViewController.processUnlock()
    }

    return true
}

https://github.com/rishi420/ABPadLockScreen制作了一个演示项目
请参阅swift演示示例。

答案 5 :(得分:0)

我认为信号量可以提供帮助。这是一个用法示例:

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

这是一个来自AFNetworking的功能。方法getTasksWithCompletionHandlerNSURLSession的方法

  

在会话中异步调用包含所有数据,上载和下载任务的完成回调。

Semaphore_wait将确保为任务分配适当的值。这样您就可以获得异步请求结果。