陈斌彬的技术博客

Stay foolish,stay hungry

iOS 开发异步第三方库 PromiseKit 解析

iOS 开发异步 第三方库 PromiseKit 解析

PromiseKit is not just a Promises implementation, it is also a collection of helper functions that make the typical asynchronous patterns we use in iOS development delightful too.

PromiseKit 不仅仅是Promises的实现,它还是收集了一系列的有用的函数并且让他们异步化,让我们能愉快的进行IOS开发。

这里提到了Promises 实现,我们看看你什么是promises :

这里有相关参考链接:http://docs.scala-lang.org/overviews/core/futures.html

Futures provide a nice way to reason about performing many operations in parallel– in an efficient and non-blocking way. The idea is simple, a Future is a sort of a placeholder object that you can create for a result that does not yet exist. Generally, the result of the Future is computed concurrently and can be later collected. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code.

future 提供了一种漂亮的方式去论述并行操作的——有效率并且非阻塞的方式。思想很简单,Future是一种占位对象,你可以为一个现在还没有存在的东西创建它。总之,future的结果是在后来并行计算出来的。并行的任务,它让结果获得得更快。

以上描述是不是听起来很有IOS中block块的意思,只是它的操作是异步的。(目前至少我试这么理解的)

我们再来看看promises :

While futures are defined as a type of read-only placeholder object created for a result which doesn’t yet exist, a promise can be thought of as a writable, single-assignment container, which completes a future. That is, a promise can be used to successfully complete a future with a value (by “completing” the promise) using the success method. Conversely, a promise can also be used to complete a future with an exception, by failing the promise, using the failure method.

future定了了一种为现在还不存在的结果而创建的只读得占位对象, promise 可以认为是可读写的,单一赋值的容器,它可以完成future。promise可以带一个值成功完成future返回一个值,promise也可以完成future带来异常。

promise 感觉就是管理future的一个对象。(常用的XXmanager ?_?)

废话不多说,我们直接来看看这个第三方库,废话补多少,先上使用的例子: 我们通常在发一个异步请求的时候一般会这么做:

@implementation MyViewController

- (void)viewDidLoad {
    id rq = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://placekitten.com/320/320"]];

    void (^handleError)(id) = ^(NSString *msg){
        UIAlertView *alert = [[UIAlertView alloc] init… delegate:self …];
        [alert show];
    };

    [NSURLConnection sendAsynchronousRequest:rq completionHandler:^(id response, id data, id error) {
        if (error) {
            handle([error localizedDescription]);
        } else {
            UIImage *image = [UIImage imageWithData:data];
            if (!image) {
                handleError(@"Bad server response");
            } else {
                self.imageView.image = image;
            }
        }
    }];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    // hopefully we won’t ever have multiple UIAlertViews handled in this class
    [self dismissViewControllerAnimated:YES];
}

@end

当我们使用promiseKit之后,代码如下:

#import <PromiseKit.h>

- (void)viewDidLoad {
    [NSURLConnection GET:@"http://placekitten.com/320/320"].then(^(UIImage *image) {
        self.imageView.image = image;
    }).catch(^(NSError *error){
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:…];
        [alert promise].then(^{
            [self dismissViewControllerAnimated:YES];
        })
    });
}

是不是很神奇的说,呵呵…看起来有点高上大的样子,简介了不少代码。看起来还是比较有诱惑性的代码。

下面我们正式来看看promiseKit(貌似前奏太多啦)。 promiseKit 中,主要的类有一个.m文件就行了,其他都是关于常用UI或者请求方法的扩展。下面是目录结构,主要的类用红色标记出来了。是不是很简单,其他都是扩张,你如果要用就加入,不用可以不需要加入你的项目当中。 img

我们看看Promise.h文件,下面是

A Promise represents the future value of a task.

To obtain the value of a Promise, call then. When the asynchronous task that this Promise represents has resolved successfully, the block you pass to then will be executed with the resolved value. If the Promise has already been resolved succesfully at the time you then the Promise, the block will be executed immediately.

Effective use of Promises involves chaining thens, where the return value from one then is fed as the value of the next, et cetera.

promise 代表了一个任务的未来的值,

要获得promise的值,调用then方法。当异步的任务解决成功,传递给then的block块将会根据获得的值执行,如果promise已经解决成功了当你then的时候,这个block块将会立刻执行。 这个可以使用链式结果调用,then返回的值会成为下个then的中得值。

下面我们通过简单的一个例子一步步跟进它里面的源码,并且告诉你怎么用(再用发上面比较简单,一下仅仅轻轻带过使用方法,主要解释它内部的实现):

UIAlertView * alert=[[UIAlertView alloc] initWithTitle:@"title" message:@"message" delegate:nil cancelButtonTitle:@"sure" otherButtonTitles:nil];
[alert promise].then(^(){
    NSLog(@"excute!");
});

在创建 UI|AlertView之后调用它的promise对象,然后调用then就可以了。调用方法相当简单,具体使用可以参考:http://promisekit.org%E3%80%82

- (PMKPromise *)promise {
    PMKAlertViewDelegater *d = [PMKAlertViewDelegater new];
    PMKRetain(d);
    self.delegate = d;
    [self show];
    return [PMKPromise new:^(id fulfiller, id rejecter){
        d->fulfiller = fulfiller;
    }];
}

它的promise方法很简单,仅仅就是创建并返回了一个PMKPromise对象,继续来看看promise的then方法,这个是最主要的方法,如果这个方法理解清楚了,这个框架就可以举一反三的大致理解清楚了。

- (PMKPromise *(^)(id))then {
    return ^(id block){
        return self.thenOn(dispatch_get_main_queue(), block);
    };
}

我们看到then 他会调用thenOn方法,都返回的是一个block块。

  • (PMKPromise *(^)(id))then 函数和调用形式[alert promise].then(^(){ NSLog(@“excute!”);}) ,我们自己看看是不是感觉很奇怪,为什么then后面竟然带的是中括号,而我们通常在使用block的时候,一般都是带{}进行操作的,想想 [UIView animate:^{ }] ,是不是完全不一样,这里就涉及了一个block的用法了,就我来说看的和用的还是比较少的。区别如下:

1.通常,我们例如[UIView animate:^{ }] 都是在文件内部进行block的调用,而在客户端(调用的时候)来定义block块,我们通常用block块替换delegate的时候就是这么做得,也是大众的做法

2.此处,刚刚是反过来的,在内部定义block块,在外部调用,这样做得好处是在调用的时候就直接能执行。(想想promise是异步操作的一个库哦)

打断了一下,我们继续说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) {

            // HACK we seem to expose some bug in ARC where this block can
            // be an NSStackBlock which then gets deallocated by the time
            // we get around to using it. So we force it to be malloc'd.
            block = [block copy];

            return dispatch_promise_on(q, ^{
                return safely_call_block(block, result);
            });
        };
    }
    pending:^(id result, PMKPromise *next, dispatch_queue_t q, id block, PMKPromiseFulfiller resolve) {
        if (IsError(result))
            return PMKResolve(next, result);

        dispatch_async(q, ^{
            resolve(safely_call_block(block, result));
        });
    }];
}

thenOn方法又调用了- (id)resolved:(PMKResolveOnQueueBlock(^)(id result))mkresolvedCallback pending:(void(^)(id result, PMKPromise *next, dispatch_queue_t q, id block, PMKPromiseFulfiller resolver))mkpendingCallback 函数,一层层的调用,一层还没理解就下一次,无穷无尽了呢。不过没办法,只能接着往下看了。

- (id)resolved:(PMKResolveOnQueueBlock(^)(id result))mkresolvedCallback
       pending:(void(^)(id result, PMKPromise *next, dispatch_queue_t q, id block, PMKPromiseFulfiller resolver))mkpendingCallback
{
    __block PMKResolveOnQueueBlock callBlock;
    __block id result;

    dispatch_sync(_promiseQueue, ^{
        if ((result = _result))
            return;

        callBlock = ^(dispatch_queue_t q, id block) {
            __block PMKPromise *next = nil;

            // HACK we seem to expose some bug in ARC where this block can
            // be an NSStackBlock which then gets deallocated by the time
            // we get around to using it. So we force it to be malloc'd.
            block = [block copy];

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

            // next can still be `nil` if the promise was resolved after
            // 1) `-thenOn` read it and decided which block to return; and
            // 2) the call to the block.

            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);
}

我们看到dispatch_barrier_sync函数,等待该队列前面的操作任务执行完成后才会执行这个block块。这至关重要的函数保证了then的同步执行,它的block块作用如下,创建了一个新的PMKPromise并且把then中带的block添加到_handlers, 这两个操作是关键步骤,至于这个函数中得大多其他代码都是进行递归解析then方法(别忘了,这是一个链式调用方法)。 最复杂的函数解决了,我们再返回去看看thenOn函数吧,由于then函数的主题是block,那么我们主要看thenOn,thenOn函数的后半部分,很简单就两步操作,首先进行了错误的处理,再调用resolve函数执行resolved:pending函数中得

resolver = ^(id o){
                          if (IsError(o)) reject(o); else fulfill(o);
                          };

这里我们需要最后跟进一步,看看new 方法中得fulfill是什么:

id fulfiller = ^(id value){
    if (PMKGetResult(this))
        return NSLog(@"PromiseKit: Promise already resolved");
    if (IsError(value))
        @throw PMKE(@"You may not fulfill a Promise with an NSError");

    PMKResolve(this, value);
};

调用了PMKResolve函数,其他都是一些异常的处理。最后看看这个函数

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);
}

执行所有的handler操作,并且设置result值。这里我们是否记得,在上面我们有把block存到handler中得。于是block就顺利的执行了。 因此fulfiller才是执行block的真正方法。

关于UIAlertView的调用如下

貌似洋洋洒洒写了这么多,有些乱,稍微整理下好了: 1. 创建promise 2. 调用then,then调用thenOn ,然后thenOn调用resolved:pending方法返回一个PMKPromise对象。 3. alertView:didDismissWithButtonIndex:方法调用了fulfiller.