【问题标题】:Wait for asynchronous request result等待异步请求结果
【发布时间】:2016-12-05 16:41:53
【问题描述】:

我想以某种方式异步验证 ABPadLockScreen 中的引脚,因为引脚未保存在设备上。我将 Alamofire 与 PromiseKit 一起用于 http 请求以做出承诺。

我曾尝试使用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 pin验证方法:

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 pin 验证在 ViewController 的主线程上完成

已解决编辑:

感谢大家对我和我的 swift 和 iOS 知识的耐心等待。这里有很多很好的答案,最后我只是选择了,在我看来,最简单的解决方案。我听了Losiowaty-s 的建议;当我从服务器收到响应时,实现了一个微调器并手动关闭了锁定屏幕。我用过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
}

【问题讨论】:

  • 您说“ABPadLockScreen pin 验证已在主线程上完成”,那么您不应该在那里等待。重构匹配异步调用的验证功能。
  • 此函数padLockScreenViewController 是 abpadlockscreen 协议的一部分,该函数由 abpadlockscreen 自动调用。你打算怎么做?如果我问了一个愚蠢的问题,我真的很抱歉,但我只使用 Swift 几天,所以我仍然不知道它是如何工作的。
  • 带信号量的代码不完整,添加dispatch_semaphore_signal(semaphore);在 loginResult = loginResultRaw 之后发出信号量继续。
  • 不幸的是结果相同
  • @Nikola.Lukovic 您是否使用断点来验证是否已到达此行:dispatch_semaphore_signal(semaphore)//after a suggestion from Josip B.

标签: ios swift asynchronous alamofire


【解决方案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 演示示例。

【讨论】:

    【解决方案2】:

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

    我会尝试这样解决您的问题:

    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 作为插入式解决方案)并且应用程序没有挂起。在用户体验方面,这一点非常重要,因为连接不佳的用户可能会因为应用挂起并关闭它而感到沮丧。
    但是有一个潜在的问题 - 如果当您从委托方法返回 false 时挂锁屏幕显示某种“错误的 pin”消息,那么用户可能会看到它,从而造成一些混乱。现在可以通过制作/定位微调器来解决这个问题,使其模糊信息,尽管这是一个非常粗糙和不雅的解决方案。另一方面,也许可以对其进行足够的自定义,以便不显示任何消息,并且您会在服务器端验证后显示自己的警报。

    让我知道你对此的看法!

    【讨论】:

      【解决方案3】:

      ...阻塞主线程没关系...但问题是我陷入了死锁。

      一个问题可能是它用dispatch_semaphore_wait 阻塞了main thread,所以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
      }
      

      【讨论】:

        【解决方案4】:

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

        public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
        
            var success = false
            let defaults = NSUserDefaults.standardUserDefaults()
            let baseUrl = defaults.stringForKey(Util.serverUrlKey)
            let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
        
            let params = ["Pin": pin]
        
            Alamofire.request(.POST, "\(baseUrl!)/sw/airpharm/login", parameters: params).responseObject {
                (response: Response<LoginResult, NSError>) in
        
                if let loginResult = response.result.value where loginResult.code == HTTPStatusCode.OK {
                    AirpharmService.id = loginResult.result!.id
                    success = true
                }
        
                dispatch_semaphore_signal(semaphore)
            }
        
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
            return success
        }
        

        这应该是:

        1. 崩溃,因为您强制解包多个变量(例如baseUrl!loginResult.result!.id 等,其中一个为 nil

        2. 如果你有一个有效的LoginResult,则返回true

        3. 如果您没有得到有效的LoginResult,则返回false

        但理论上不应该死锁。

        【讨论】:

          【解决方案5】:

          试试这个:

          添加调度组:

          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
          }
          

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

          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)
                      }
                      dispatch_group_leave(yourClass.serviceGroup)
                  }
              }
          }
          

          【讨论】:

            【解决方案6】:

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

            - (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 将确保已为任务分配了适当的值。这样就可以得到异步请求的结果了。

            【讨论】:

              猜你喜欢
              • 2021-05-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-28
              • 1970-01-01
              • 2021-05-17
              • 2013-03-27
              相关资源
              最近更新 更多