【问题标题】:nested callback hell in objective c目标c中的嵌套回调地狱
【发布时间】:2015-07-29 13:37:15
【问题描述】:

我现在正在开发一个适用于 BLE、后端服务器和位置的应用程序。我面临一个我不知道如何解决的问题,这就是人们所说的“回调地狱”。 iOS 中的整个 CoreBluetooth 框架都基于委托模式,在您可以使用 CBPeripheral 之前,必须至少进行 3 个回调:

DidConnectToPeripheral
DidDiscoverServices
DidDiscoverCharacteristics

但实际上可能还有更多,您对设备执行的每个操作都会作为对其中一个功能的回调返回。现在,当我想“租用”这个 ble 产品时,我必须连接到它,连接后向服务器发送请求并获取用户的当前位置,之后我必须在蓝牙设备中写入一个值并获得确认.这不会那么困难,但不幸的是,这些阶段中的每一个都是失败的,因此需要添加错误处理。更不用说实现超时了。

我确信我不是唯一一个解决此类问题的人,所以我环顾四周,发现 2 件事可能会有所帮助:

  1. wwdc 2015 中的 Advanced NSOperations 演讲,但在尝试了 4 天后,似乎代码错误太多。
  2. Promisekit 但我找不到封装 CoreBluetooth 的方法。

拥有更复杂应用程序的人如何处理这个问题?在 swift 或 objc 中。

一些有问题的示例代码:

-(void)startRentalSessionWithLock:(DORLock *)lock timeOut:(NSTimeInterval)timeout forSuccess:(void (^)(DORRentalSession * session))successBlock failure:(failureBlock_t)failureBlock{
    //we set the block to determine what happens
    NSAssert(lock.peripheral, @"lock has to have peripheral to connect to");
    if (!self.rentalSession) {
        self.rentalSession = [[DORRentalSession alloc] initWithLock:nil andSessionDict:@{} active:NO];
    }
    self.rentalSession.lock = lock;
    [self connectToLock:self.rentalSession.lock.peripheral timeOut:timeout completionBlock:^(CBPeripheral *peripheral, NSError *error) {

        self.BTConnectionCompleted = nil;
        if (!error) {
            [[INTULocationManager sharedInstance] requestLocationWithDesiredAccuracy:INTULocationAccuracyHouse timeout:1 delayUntilAuthorized:YES block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {
                if (status == INTULocationStatusSuccess || status == INTULocationStatusTimedOut) {
                    [self startServerRentalForSessionLockWithUserLocation:currentLocation.coordinate forSuccess:^(DORRentalSession *session) {
                        if (self.rentalSession.lock.peripheral && self.rentalSession.lock.peripheral.state == CBPeripheralStateConnected) {
                            [self.rentalSession.lock.peripheral setNotifyValue:YES forCharacteristic:self.rentalSession.lock.charectaristics.sensorCharacteristic];
                        }else{
                            //shouldnt come here
                        }

                        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                            if (self.rentalSession.lock.peripheral.state == CBPeripheralStateConnected) {
                                !self.rentalSession.lock.open ? [self sendUnlockBLECommandToSessionLock] : nil;
                                if (successBlock) {
                                    successBlock(session);
                                }
                            }else{
                                [self endCurrentRentalSessionWithLocation:self.rentalSession.lock.latLng andPositionAcc:@(1) Success:^(DORRentalSession *session) {
                                    if (failureBlock) {
                                        failureBlock([[NSError alloc] initWithDomain:DonkeyErrorDomain code:46 userInfo:@{NSLocalizedDescriptionKey:@"Could't connect to lock"}],200);
                                    }
                                } failure:^(NSError *error, NSInteger httpCode) {
                                    if (failureBlock) {
                                        failureBlock([[NSError alloc] initWithDomain:DonkeyErrorDomain code:45 userInfo:@{NSLocalizedDescriptionKey:@"fatal error"}],200);
                                    }
                                }];

                            }
                        });

                    } failure:^(NSError *error, NSInteger httpCode) {
                        if (failureBlock) {
                            failureBlock(error,httpCode);
                        }
                    }];

                }else{
                    NSError *gpsError = [self donkeyGPSErrorWithINTULocationStatus:status];
                    if (failureBlock) {
                        failureBlock(gpsError,200);
                    }
                }
            }];
        }else{
            if (failureBlock) {
                failureBlock(error,200);
            }
        }

    }];
}

【问题讨论】:

    标签: ios objective-c bluetooth callback core-bluetooth


    【解决方案1】:

    要摆脱这种嵌套调用,您可以使用 GCD 组 + 串行执行队列:

    dispatch_queue_t queue = ddispatch_queue_create("com.example.queue", NULL);
    dispatch_group_t group = dispatch_group_create();
    
    // Add a task to the group
    dispatch_group_async(group, queue, ^{
       // Some asynchronous work
    });
    
    // Make dispatch_group_async and dispatch_group_sync calls here
    
    // Callback to be executed when all scheduled tasks are completed.
    dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
        // Do smth when everything has finished
    });
    
    // wait for all tasks to complete
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    

    另一种基于GCD组的解决方案描述here

    【讨论】:

    • 与 NSOperationQueue 相比,调度组有具体优势吗?
    • 如果您想使用基于 NSOperation 的解决方案,您应该实现自定义 asynchronous 操作。使用调度组,您可以通过dispatch_group_enter/dispatch_group_leave 调用来判断任务已开始/结束。