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

来源:互联网 发布:redis数据库有什么用 编辑:程序博客网 时间:2024/06/04 20:01
最近在尝试解决下载管理中,全部下载的速度问题,于是在github中试图寻找答案,无意中发现了Promise这个第三方库。它的作用如下
  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 ?_?)
 
废话不多说,我们直接来看看这个第三方库,废话补多少,先上使用的例子: 
我们通常在发一个异步请求的时候一般会这么做:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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之后,代码如下:
 
 
1
2
3
4
5
6
7
8
9
10
11
12
#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或者请求方法的扩展。下面是目录结构,主要的类用红色标记出来了。是不是很简单,其他都是扩张,你如果要用就加入,不用可以不需要加入你的项目当中。
 
我们看看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 `then`s, 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的中得值。
 
下面我们通过简单的一个例子一步步跟进它里面的源码,并且告诉你怎么用(再用发上面比较简单,一下仅仅轻轻带过使用方法,主要解释它内部的实现):
1
2
3
4
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。
1
2
3
4
5
6
7
8
9
- (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方法,这个是最主要的方法,如果这个方法理解清楚了,这个框架就可以举一反三的大致理解清楚了。
1
2
3
4
5
- (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函数,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (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 函数,一层层的调用,一层还没理解就下一次,无穷无尽了呢。不过没办法,只能接着往下看了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (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函数中得
1
2
3
resolver = ^(id o){
                          if (IsError(o)) reject(o); else fulfill(o);
                          };

 

这里我们需要最后跟进一步,看看new 方法中得fulfill是什么:
1
2
3
4
5
6
7
8
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函数,其他都是一些异常的处理。最后看看这个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void PMKResolve(PMKPromise *thisid 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.
 
调用过程还是很简单的吧,我们了解了这么多,大体步骤也了解了。由于时间有限,我没有详细说明then是如何实现链式调用的,以后有时间补上。
总的来说这个类库还是挺不错的,在github上也有2000多得星了。如果大家喜欢可以研究下它。
 
我觉得:对于reactivecocoa觉得比较难用的,可以用这个先来熟悉下这个框架,毕竟没有那么复杂,还有感觉这两者还是思路和写法上面还是比较类似的。
最后有什么不对的地方,希望大家多多指正!




PromiseKit 解析 (二) 递归 IOS


  在PromiseKit中,里面涉及了很多的递归操作,而递归操作,对于大部分人理解起来都是比较麻烦的(包括我),而github中一些高上大的第三方库,无一不涉及了很漂亮的递归操作,因此理解递归操作是很必要的。如果想成为尽可能优秀的程序猿。

  递归操作:自己调用自己,不停的压栈,最后出栈。(说的简单,但是还是理解起来还是比较复杂的)
  我们来接着看看PromiseKit的递归操作,看看它的链式结构式怎么样实现的。
  相信在上一章就可以了解到,then操作最后返回的是一个新的PMKPromise,然后返回的PMKPromise实例可以再次调用then方法,这就是一个链式结构了(如下图)。很简单。如果仅仅是这种递归,那么就没必要写这篇了。
 
  来仔细看看它内部的递归操作,了解它最后是如何进行这么多then得调用执行的,(它不是立刻调用then就执行了block的函数体,而是在之后的特定情况才执行的,比如UIAlertView 是在点击按钮的情况下进行执行的),还有看看它的值传递是如何进行的。
  废话不多说先贴代码:
  resolved:pending 函数里面的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
                }];
            });
 
和一个PMKRsolve函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void PMKResolve(PMKPromise *thisid 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);
}
   
  看到,set的block块执行的操作是遍历执行所有的储存在_handlers的handler的block块,还会把设置PMKResolved的result值,然后清空所有存储的_handler的block块。这是一个主体的块,能够让我们的then中得block合理的执行,这个在上一章节已经说过了。
  下面便是一个主体的递归操作了,下面看看这个递归的操作是怎么样的:
1.判断是否 result 是 PMKResolved
2.如果不是PMKResolved,则调用set的block块(参数是Result)
3.如果是PMKResolved,则判断result的nextResult,判断它是否是PMKResolved
4.如果不是PMKResolved,则调用set的block块(参数是nextResult)
5.如果是PMKResolved,则回到1
  这是整个递归的过程,打开整个无线循环的操作是2和4.
  如果是UIAlertView实例,则直接会调用4的操作了。这样就是最简单的执行过程了。
 
  我们想想,如果then后面接了好几个then,那么这个执行回事怎么样的,每个PMKResolved都会将他的handler执行完成,这样每个then中得block块就执行了,

dispatch_barrier_sync这个函数,保证了它是顺序执行的。那么它执行的顺序是不是就是类似于我们一行一行代码在执行一个个then中得block块了。至于值传递,我们也再上面说到了。这里就不在强调了。

  最后递归比较难以理解,但是认真去解读的话还是可以的,在国外,很多高校都是以lisp或者haskell这样的函数式语言来接触编程的,有的有scheme这种语言的课程,这些语言的主要思想就是递归。因此递归还是在他们深深的脑海里的。许多情况下递归效率很高,代码很简洁。但是对于我们来说还是在不熟悉的情况下少用为妙。我也尝试学学这样一门语言,进行下头脑风暴。


1 0