【问题标题】:Waiting for network call to finish等待网络调用完成
【发布时间】:2019-07-25 13:05:18
【问题描述】:

我想要实现的是发出网络请求并等待它完成,以便我可以决定下一步应该是什么应用程序。 通常我会避免这样的解决方案,但这种情况很少见,因为代码库有很多遗留问题,我们没有足够的时间应用必要的更改来把事情做好

我正在尝试使用以下定义编写一个简单的输入输出方法:

- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId;

问题是,为了在这个方法中执行一些验证,我需要发出一个网络请求来接收必要的信息,所以我想等待这个请求完成。

我首先想到的是使用dispatch_semaphore_t,所以我最终得到了这样的结果:

- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
    id<LocationsReader> locationsReader = [self locationsReader];

    __block LocationStatus *status = nil;
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    [locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
        status = locationStatus;
        dispatch_semaphore_signal(sema);
    } failure:nil];
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

    return [self.paymentCards firstCardForStatus:status];
}

一切都编译并运行,但我的 UI 冻结,我实际上从未收到 sempahore 的信号。 所以,我开始玩dispatch_group_t,结果完全相同。

看起来我可能在执行代码的地方遇到了一些问题,但我不知道如何解决这个问题并获得预期的结果。当我尝试将所有内容包装在dispatch_async 中时,我实际上停止阻塞主队列,但dispatch_async 立即返回,所以我在网络请求完成之前从这个方法return

我错过了什么?如果没有一些while hacks,这实际上可以实现吗?还是我想和风车搏斗?


我能够通过以下解决方案实现我想要的,但它真的感觉像是一种 hacky 方式,而不是我喜欢在我的代码库中发布的东西。

- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
    id<LocationsReader> locationsReader = [self locationsReader];

    __block LocationStatus *status = nil;
    __block BOOL flag = NO;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
            status = locationStatus;
            flag = YES;
        } failure:nil];
    });

    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};

    return [self.paymentCards firstCardForStatus:status];
}

【问题讨论】:

  • 我会推荐执行异步代码并显示一个 Hud 以等待异步任务结束,因为不推荐锁定 UI 等待这样的任务。
  • @kjoe 正如我所提到的,我对这种方法也不满意。问题是在这个阶段我不能这样做。

标签: ios objective-c concurrency grand-central-dispatch


【解决方案1】:

我猜fetchLocationProviderStatusFor:completion:failure: 在主队列中调用这些回调。这就是为什么你会陷入僵局。不可能。我们还不能时间旅行。

【讨论】:

    【解决方案2】:

    已弃用的NSURLConnection.sendSynchronousRequest API 对于那些您确实不能(或只是懒得)正确做事的情况非常有用,例如以下示例:

        private func pageExists(at url: URL) -> Bool {
            var request = URLRequest(url: url)
            request.httpMethod = "HEAD"
            request.timeoutInterval = 10
            var response: URLResponse?
            try! NSURLConnection.sendSynchronousRequest(request,
                                                        returning: &response)
            let httpResponse = response as! HTTPURLResponse
            if httpResponse.statusCode != 200 { return false }
            if httpResponse.url != url { return false }
            return true
        }
    

    【讨论】:

      【解决方案3】:

      目前,您的方法会导致在主线程上完成工作,从而冻结 UI。您的解决方案有效,但最好更改方法以包含完成块。然后,您可以在异步块的末尾调用完成块。这是示例代码:

      - (void)validCardForLocationWithId:(ObjectId)locationId completion:(nullable id<UserPaymentCard> (^)(void))completion {
          id<LocationsReader> locationsReader = [self locationsReader];
          __block LocationStatus *status = nil;
          [locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
              status = locationStatus;
              completion([self.paymentCards firstCardForStatus:status]);
          } failure:nil];
      }
      

      【讨论】:

        猜你喜欢
        • 2020-06-12
        • 1970-01-01
        • 2020-10-27
        • 2020-01-06
        • 1970-01-01
        • 2019-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多