目标c中的嵌套回调地狱

Posted

技术标签:

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

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

DidConnectToPeripheral
DidDiscoverServices
DidDiscoverCharacteristics

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

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

    wwdc 2015 中的 Advanced NSOperations 演讲,但在尝试了 4 天后,似乎代码错误太多。 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);
            
        

    ];

【问题讨论】:

【参考方案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 调用来判断任务已开始/结束。

以上是关于目标c中的嵌套回调地狱的主要内容,如果未能解决你的问题,请参考以下文章

基于PROMISE解决回调地狱问题

在 SQL 查询之外导出值(回调地狱)[重复]

js-promise以及asyncawait

回调地狱——JavaScript异步编程指南

[译] 回调地狱——JavaScript异步编程指南

目标 -C 回调不适用于不幸的后台崩溃