等待网络通话结束

时间:2019-07-25 13:05:18

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

我要实现的目标是发出网络请求并等待它完成,这样我就可以决定下一步应该是什么应用。 通常,我会避免这种解决方案,但是这种情况很少见,在这种情况下,代码库有很多遗留问题,并且我们没有足够的时间来应用必要的更改以使事情变得正确。

我正在尝试编写具有以下定义的简单输入输出方法:

- (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骇客,实际上可以实现吗?还是我想打风车?


我可以通过以下解决方案实现我想要的,但是这确实感觉像是一种骇人听闻的方式,而不是我不希望在我的代码库中提供的东西。

- (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];
}

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

已弃用的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
    }

答案 2 :(得分:0)

当前,您的方法使工作在主线程上完成,这将冻结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];
}