等待网络调用完成

5
我想要实现的是发起一个网络请求并等待其完成,以便我可以决定应用程序下一步该做什么。 通常情况下,我会避免这样的解决方案,但这是一个罕见的情况,代码库中有很多旧的内容,我们没有足够的时间来进行必要的更改以“把事情做对”。 我正在尝试编写一个具有以下定义的简单输入输出方法:
- (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)会冻结,我实际上从未收到信号量(semaphore)的信号。 因此,我开始尝试使用dispatch_group_t,但结果完全相同。
看起来我可能遇到了代码执行位置的问题,但我不知道如何解决并获得预期的结果。当我尝试将所有内容包装在dispatch_async内部时,我实际上停止了阻塞主队列,但dispatch_async立即返回,因此在网络请求完成之前,我就从这个方法中return了。
我错过了什么吗?这是否可以实现,而无需使用某些while技巧?还是说我正在与风车战斗?
我使用以下解决方案实现了我想要的功能,但它确实感觉像一种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等待此类任务。 - Yoel Jimenez del valle
@kjoe,正如我之前提到的,我也不满意这种方法。问题是在这个阶段我无法做出不同的选择。 - cojoj
3个回答

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
    }

1
我猜 fetchLocationProviderStatusFor:completion:failure: 在主队列中调用这些回调函数。这就是为什么你会遇到死锁问题。这是不可能的。我们还不能穿越时空。

0

目前,你的方法会导致主线程执行任务,从而使用户界面停止响应。尽管你的解决方案能够工作,但最好还是修改方法,添加一个完成块。然后,在异步块的末尾调用完成块。以下是示例代码:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接