• iOS 如何优雅的处理“回调地狱Callback hell”(一) (下)


    了解完流程之后,就可以开始继续研究源码了。在PromiseKit当中,最常用的当属then,thenInBackground,catch,finally

    - (PMKPromise *(^)(id))then {

        return ^(id block){

            return self.thenOn(dispatch_get_main_queue(), block);

        };

    }

    - (PMKPromise *(^)(id))thenInBackground {

        return ^(id block){

            return self.thenOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);

        };

    }

    - (PMKPromise *(^)(id))catch {

        return ^(id block){

            return self.catchOn(dispatch_get_main_queue(), block);

        };

    }

    - (PMKPromise *(^)(dispatch_block_t))finally {

        return ^(dispatch_block_t block) {

            return self.finallyOn(dispatch_get_main_queue(), block);

        };

    }

    这四个方法底层调用了各自的thenon,catchon,finallyon方法,这些on的方法实现基本都差不多,那我就以最重要的thenon来分析一下。

    - (PMKResolveOnQueueBlock)thenOn {

        return [self resolved:^(id result) {

            if (IsPromise(result))

                return ((PMKPromise *)result).thenOn;

            if (IsError(result)) return ^(dispatch_queue_t q, id block) {

                return [PMKPromise promiseWithValue:result];

            };

            return ^(dispatch_queue_t q, id block) {

                block = [block copy];

                return dispatch_promise_on(q, ^{

                    return pmk_safely_call_block(block, result);

                });

            };

        }

        pending:^(id result, PMKPromise *next, dispatch_queue_t q, id block, void (^resolve)(id)) {

            if (IsError(result))

                PMKResolve(next, result);

            else dispatch_async(q, ^{

                resolve(pmk_safely_call_block(block, result));

            });

        }];

    }

    这个thenon就是返回一个方法,所以继续往下看

    - (id)resolved:(PMKResolveOnQueueBlock(^)(id result))mkresolvedCallback

           pending:(void(^)(id result, PMKPromise *next, dispatch_queue_t q, id block, void (^resolver)(id)))mkpendingCallback

    {

        __block PMKResolveOnQueueBlock callBlock;

        __block id result;

        dispatch_sync(_promiseQueue, ^{

            if ((result = _result))

                return;

            callBlock = ^(dispatch_queue_t q, id block) {

                block = [block copy];

                __block PMKPromise *next = nil;

                dispatch_barrier_sync(_promiseQueue, ^{

                    if ((result = _result))

                        return;

                    __block PMKPromiseFulfiller resolver;

                    next = [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {

                        resolver = ^(id o){

                            if (IsError(o)) reject(o); else fulfill(o);

                        };

                    }];

                    [_handlers addObject:^(id value){

                        mkpendingCallback(value, next, q, block, resolver);

                    }];

                });

                return next ?: mkresolvedCallback(result)(q, block);

            };

        });

        // We could just always return the above block, but then every caller would

        // trigger a barrier_sync on the promise queue. Instead, if we know that the

        // promise is resolved (since that makes it immutable), we can return a simpler

        // block that doesn't use a barrier in those cases.

        return callBlock ?: mkresolvedCallback(result);

    }

    这个方法看上去很复杂,仔细看看,函数的形参其实就是2个block,一个是resolved的block,还有一个是pending的block。当一个promise经历过resolved之后,可能是fulfill,也可能是reject,之后生成next新的promise,传入到下一个then中,并且状态会变成pending。上面代码中第一个return,如果next为nil,那么意味着promise没有生成,这是会再调用一次mkresolvedCallback,并传入参数result,生成的PMKResolveOnQueueBlock,再次传入(q, block),直到next的promise生成,并把pendingCallback存入到handler当中。这个handler存了所有待执行的block,如果把这个数组里面的block都执行,那么就相当于依次完成了上面的所有异步操作。第二个return是在callblock为nil的时候,还会再调一次mkresolvedCallback(result),保证一定要生成next的promise。

    这个函数里面的这里dispatch_barrier_sync这个方法,就是promise后面可以链式调用then的原因,因为GCD的这个方法,让后面then变得像一行行的then顺序执行了。

    可能会有人问了,并没有看到各个block执行,仅仅只是加到handler数组里了,这个问题的答案,就是promise的核心了。promise执行block的操作是放在resove里面的。先来看看源码

    static void PMKResolve(PMKPromise *this, id result) {

        void (^set)(id) = ^(id r){

            NSArray *handlers = PMKSetResult(this, r);

            for (void (^handler)(id) in handlers)

                handler(r);

        };

        if (IsPromise(result)) {

            PMKPromise *next = result;

            dispatch_barrier_sync(next->_promiseQueue, ^{

                id nextResult = next->_result;

                if (nextResult == nil) {  // ie. pending

                    [next->_handlers addObject:^(id o){

                        PMKResolve(this, o);

                    }];

                } else

                    set(nextResult);

            });

        } else

            set(result);

    }

    这是一个递归函数,能形成递归的条件就是那句PMKResolve(this, o);当nextResult = nil的时候,就代表了这个promise还是pending状态,还没有被执行,这个时候就要递归调用,直到nextResult不为nil。不为nil,就会调用set方法,set方法是一个匿名函数,里面的for循环会依次循环,执行handler数组里面的每一个block。里面的那个if语句,是先判断result是否是一个promise,如果不是promise,就去执行set方法,依次调用各个block。

    至此,一个then的执行原理就到此结束了。接下来我们再看看when的原理。

    return newPromise = [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter){

            NSPointerArray *results = nil;

          #if TARGET_OS_IPHONE

            results = [NSPointerArray strongObjectsPointerArray];

          #else

            if ([[NSPointerArray class] respondsToSelector:@selector(strongObjectsPointerArray)]) {

                results = [NSPointerArray strongObjectsPointerArray];

            } else {

              #pragma clang diagnostic push

              #pragma clang diagnostic ignored "-Wdeprecated-declarations"

                results = [NSPointerArray pointerArrayWithStrongObjects];

              #pragma clang diagnostic pop

            }

          #endif

            results.count = count;

            NSUInteger ii = 0;

            for (__strong PMKPromise *promise in promises) {

                if (![promise isKindOfClass:[PMKPromise class]])

                    promise = [PMKPromise promiseWithValue:promise];

                promise.catch(rejecter(@(ii)));

                promise.then(^(id o){

                    [results replacePointerAtIndex:ii withPointer:(__bridge void *)(o ?: [NSNull null])];

                    if (--count == 0)

                        fulfiller(results.allObjects);

                });

                ii++;

            }

        }];

    这里只截取了return的部分,理解了then,这里再看when就好理解了。when就是在传入的promises的数组里面,依次执行各个promise,结果最后传给新生成的一个promise,作为返回值返回。

    这里要额外提一点的就是如果给when传入一个字典,它会如何处理的

    if ([promises isKindOfClass:[NSDictionary class]])

            return newPromise = [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter){

                NSMutableDictionary *results = [NSMutableDictionary new];

                for (id key in promises) {

                    PMKPromise *promise = promises[key];

                    if (![promise isKindOfClass:[PMKPromise class]])

                        promise = [PMKPromise promiseWithValue:promise];

                    promise.catch(rejecter(key));

                    promise.then(^(id o){

                        if (o)

                            results[key] = o;

                        if (--count == 0)

                            fulfiller(results);

                    });

                }

            }];

    方式和when的数组方式基本一样,只不过多了一步,就是从字典里面先取出promise[key],然后再继续对这个promise执行操作而已。所以when可以传入以promise为value的字典。

    五.使用PromiseKit优雅的处理回调地狱

    这里我就举个例子,大家一起来感受感受用promise的简洁。

    先描述一下环境,假设有这样一个提交按钮,当你点击之后,就会提交一次任务。首先要先判断是否有权限提交,没有权限就弹出错误。有权限提交之后,还要请求一次,判断当前任务是否已经存在,如果存在,弹出错误。如果不存在,这个时候就可以安心提交任务了。

    void (^errorHandler)(NSError *) = ^(NSError *error) {

        [[UIAlertView] show];

    };

    [NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

        if (connectionError) {

            errorHandler(connectionError);

        } else {

            NSError *jsonError = nil;

            NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];

            if (jsonError) {

                errorHandler(jsonError);

            } else {

                id rq = [NSURLRequest requestWithURL:[NSURL URLWithString:json[@"have_authority"]]];

                [NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

                    NSError *jsonError = nil;

                    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];

                    if (jsonError) {

                        errorHandler(jsonError);

                    } else {

                        id rq = [NSURLRequest requestWithURL:[NSURL URLWithString:json[@"exist"]]];

                        [NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

                            NSError *jsonError = nil;

                            NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];

                            if (jsonError) {

                                errorHandler(jsonError);

                            } else {

                                if ([json[@"status"] isEqualToString:@"OK"]) {

                                    [self submitTask];

                                } else {

                                    errorHandler(json[@"status"]);

                                }

                            }

                        }];

                    }

                }];

            }

        }

    }];

    上面的代码里面有3层回调,看上去就很晕,接下来我们用promise来整理一下。

    [NSURLSession GET:url].then(^(NSDictionary *json){

        return [NSURLConnection GET:json[@"have_authority"]];

    }).then(^(NSDictionary *json){

        return [NSURLConnection GET:json[@"exist"]];

    }).then(^(NSDictionary *json){

        if ([json[@"status"] isEqualToString:@"OK"]) {

            return [NSURLConnection GET:submitJson];

        } else

            @throw [NSError errorWithDomain:… code:… userInfo:json[@"status"]];

    }).catch(^(NSError *error){

        [[UIAlertView] show];

    })

    之前将近40行代码就一下子变成15行左右,看上去比原来清爽多了,可读性更高。

    最后

    看完上面关于PromiseKit的使用方法之后,其实对于PromiseKit,我个人的理解它就是一个Monad(这是最近很火的一个概念,4月底在上海SwiftCon 2016中,唐巧大神分享的主题就是关于Monad,还不是很了解这个概念的可以去他博客看看,或者找视频学习学习。)Promise就是一个盒子里面封装了一堆操作,then对应的就是一组flatmap或map操作。不过缺点也还是有,如果网络用的AFNetWorking,网络请求很有可能会回调多次,这时用PromiseKit,就需要自己封装一个属于自己的promise了。PromiseKit原生的是用的OMGHTTPURLRQ这个网络框架。PromiseKit里面自带的封装的网络请求也还是基于NSURLConnection的。所以用了AFNetWorking的同学,要想再优雅的处理掉网络请求引起的回调地狱的时候,自己还是需要先封装一个自己的Promise,然后优雅的then一下。很多人可能看到这里,觉得我引入一个框架,本来是来解决问题的,但是现在还需要我再次封装才能解决问题,有点不值得。

    我自己的看法是,PromiseKit是个解决异步问题很优秀的一个开源库,尤其是解决回调嵌套,回调地狱的问题,效果非常明显。虽然需要自己封装AFNetWorking的promise,但是它的思想非常值得我们学习的!这也是接下来第二篇想和大家一起分享的内容,利用promise的思想,自己来优雅的处理回调地狱!这一篇PromiseKit先分享到这里。

    如有错误,还请大家请多多指教。

  • 相关阅读:
    37个绝对不容错过的HTML5教程和资源
    Google的自动驾驶汽车无事故成功完成30万英里的驾驶路程
    一个基于jQuery Mobile的移动设备实时幻灯javascript类库 taciónJS
    推荐免费黑色UI工具包下载
    分享一些前端开发人员必备的工具,脚本和资源
    使用HTML5画布实现的超棒javascript动画仪表板:gauge.js
    Google(谷歌)将打算在搜索结果中展示Gmail内容
    免费资源下载:30个用户界面工具栏图标设计
    一张超诡异的HTML图片“松鼠” 是图片同时也是web页面
    带给你设计灵感的30个超棒的暗色系网站设计
  • 原文地址:https://www.cnblogs.com/fengmin/p/5586567.html
Copyright © 2020-2023  润新知