iOS如何優(yōu)雅的處理“回調(diào)地獄Callback hell”(一)——使用PromiseKit

前言

最近看了一些Swift關(guān)于封裝異步操作過程的文章冠场,比如RxSwift秆剪,RAC等等赊淑,因為回調(diào)地獄我自己也寫過,很有感觸仅讽,于是就翻出了Promise來研究學(xué)習(xí)一下√杖保現(xiàn)將自己的一些收獲分享一下,有錯誤歡迎大家多多指教洁灵。

目錄

  • 1.PromiseKit簡介
  • 2.PromiseKit安裝和使用
  • 3.PromiseKit主要函數(shù)的使用方法
  • 4.PromiseKit的源碼解析
  • 5.使用PromiseKit優(yōu)雅的處理回調(diào)地獄

一.PromiseKit簡介

PromiseKit是iOS/OS X 中一個用來出來異步編程框架饱岸。這個框架是由Max Howell(Mac下Homebrew的作者,傳說中因為"不會"寫反轉(zhuǎn)二叉樹而沒有拿到Google offer)大神級人物開發(fā)出來的徽千。

在PromiseKit中苫费,最重要的一個概念就是Promise的概念,Promise是異步操作后的future的一個值双抽。

A promise represents the future value of an asynchronous task.
A promise is an object that wraps an asynchronous task

Promise也是一個包裝著異步操作的一個對象黍衙。使用PromiseKit,能夠編寫出整潔荠诬,有序的代碼琅翻,邏輯簡單的位仁,將Promise作為參數(shù),模塊化的從一個異步任務(wù)到下一個異步任務(wù)中去方椎。用PromiseKit寫出的代碼就是這樣:


[self login].then(^{
                  
     // our login method wrapped an async task in a promise
     return [API fetchData];
                  
}).then(^(NSArray *fetchedData){
                          
     // our API class wraps our API and returns promises
     // fetchedData returned a promise that resolves with an array of data
     self.datasource = fetchedData;
     [self.tableView reloadData];
                          
}).catch(^(NSError *error){
                                   
     // any errors in any of the above promises land here
     [[[UIAlertView alloc] init…] show];
                                   
});

PromiseKit就是用來干凈簡潔的代碼聂抢,來解決異步操作,和奇怪的錯誤處理回調(diào)的棠众。它將異步操作變成了鏈式的調(diào)用琳疏,簡單的錯誤處理方式。

PromiseKit里面目前有2個類闸拿,一個是Promise<T>(Swift)空盼,一個是AnyPromise(Objective-C),2者的區(qū)別就在2種語言的特性上新荤,Promise<T>是定義精確嚴格的揽趾,AnyPromise是定義寬松,靈活苛骨,動態(tài)的篱瞎。

在異步編程中,有一個最最典型的例子就是回調(diào)地獄CallBack hell痒芝,要是處理的不優(yōu)雅,就會出現(xiàn)下圖這樣:


上圖的代碼是真實存在的严衬,也是朋友告訴我的,來自快的的代碼粱挡,當然現(xiàn)在人家肯定改掉了单起。雖然這種代碼看著像這樣:


代碼雖然看上去不優(yōu)雅嘀倒,功能都是正確的局冰,但是這種代碼基本大家都自己寫過,我自己也寫過很多康二。今天就讓我們動起手來碳胳,用PromiseKit來優(yōu)雅的處理掉Callback hell吧。

二.PromiseKit安裝和使用

1.下載安裝CocoaPods

在墻外的安裝步驟:
在Terminal里面輸入

sudo gem install cocoapods && pod setup

大多數(shù)在墻內(nèi)的同學(xué)應(yīng)該看如下步驟了:

//移除原有的墻外Ruby 默認源
$ gem sources --remove https://rubygems.org/
//添加現(xiàn)有的墻內(nèi)的淘寶源
$ gem sources -a https://ruby.taobao.org/
//驗證新源是否替換成功
$ gem sources -l
//下載安裝cocoapods
// OS 10.11之前
$ sudo gem install cocoapods
//mark:OS 升級 OS X EL Capitan 后命令應(yīng)該為:
$ sudo gem install -n /usr/local/bin cocoapods
//設(shè)置cocoapods
$ pod setup

2.找到項目的路徑沫勿,進入項目文件夾下面挨约,執(zhí)行:

$ touch Podfile && open -e Podfile

此時會打開TextEdit味混,然后輸入一下命令:

platform:ios, ‘7.0’

target 'PromisekitDemo' do  //由于最新版cocoapods的要求,所以必須加入這句話
    pod 'PromiseKit'
end

Tips:感謝qinfensky大神提醒诫惭,其實這里也可以用init命令
Podfile是CocoaPods的特殊文件翁锡,在其中可以列入在項目中想要使用的開源庫,若想創(chuàng)建Podfile夕土,有2種方法:
1.在項目目錄中創(chuàng)建空文本文件馆衔,命名為Podfile
2.或者可以再項目目錄中運行“$ pod init “,來創(chuàng)建功能性文件(終端中輸入cd 文件夾地址怨绣,然后再輸入 pod init)
兩種方法都可以創(chuàng)建Podfile角溃,使用你最喜歡使用的方法

3.安裝PromiseKit

$ pod install

安裝完成之后,退出終端篮撑,打開新生成的.xcworkspace文件即可

三.PromiseKit主要函數(shù)的使用方法

  1. then
    經(jīng)常我們會寫出這樣的代碼:
- (void)showUndoRedoAlert:(UndoRedoState *)state
{
     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:……];
     alert.delegate = self; 
     self.state = state;
     [alert show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1) {
        [self.state do];
    }

}

上面的寫法也不是錯誤的减细,就是它在調(diào)用函數(shù)中保存了一個屬性,在調(diào)用alertView會使用到這個屬性咽扇。其實這個中間屬性是不需要存儲的邪财。接下來我們就用then來去掉這個中間變量。


- (void)showUndoRedoAlert:(UndoRedoState *)state
 {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:……];
    [alert promise].then(^(NSNumber *dismissedButtonIndex){
        [state do];
    });
}

這時就有人問了质欲,為啥能調(diào)用[alert promise]這個方法树埠?后面點語法跟著then是什么?我來解釋一下嘶伟,原因其實只要打開Promise源碼就一清二楚了怎憋。在pormise源碼中


@interface UIAlertView (PromiseKit)

/**
 Displays the alert view.

 @return A promise the fulfills with two parameters:
 1) The index of the button that was tapped to dismiss the alert.
 2) This alert view.
*/
- (PMKPromise *)promise;

對應(yīng)的實現(xiàn)是這樣的

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

調(diào)用[alert promise]返回還是一個promise對象,在promise的方法中有then的方法绊袋,所以上面可以那樣鏈式的調(diào)用癌别。上面代碼里面的fulfiller放在源碼分析里面去講講展姐。

在PromiseKit里面,其實就默認給你創(chuàng)建了幾個類的延展擂达,如下圖


這些擴展類里面就封裝了一些常用的生成promise方法板鬓,調(diào)用這些方法就可以愉快的一路.then執(zhí)行下去了镀迂!

2.dispatch_promise
項目中我們經(jīng)常會異步的下載圖片

typedefvoid(^onImageReady) (UIImage* image);

+ (void)getImageWithURL:(NSURL *)url onCallback:(onImageReady)callback
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
    dispatch_async(queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}

使用dispatch_promise探遵,我們可以將它改變成下面這樣:

    dispatch_promise(^{
        return [NSData dataWithContentsOfURL:url];     
    }).then(^(NSData * imageData){ 
        self.imageView.image = [UIImage imageWithData:imageData];  
    }).then(^{
        // add code to happen next here
    });

我們看看源碼,看看調(diào)用的異步過程對不對

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

PMKPromise *dispatch_promise(id block) {
    return dispatch_promise_on(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
}

看了源碼就知道上述是正確的藏雏。

3.catch
在異步操作中掘殴,處理錯誤也是一件很頭疼的事情,如下面這段代碼鹰服,每次異步請求回來都必須要處理錯誤套菜。


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[@"avatar_url"]]];
            [NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                UIImage *image = [UIImage imageWithData:data];
                if (!image) {
                    errorHandler(nil); // NSError TODO!
                } else {
                    self.imageView.image = image;
                }
            }];
        }
    }
}];

我們可以用promise的catch來解決上面的錯誤處理的問題

//oc版
[NSURLSession GET:url].then(^(NSDictionary *json){
    return [NSURLConnection GET:json[@"avatar_url"]];
}).then(^(UIImage *image){
    self.imageView.image = image;
}).catch(^(NSError *error){
    [[UIAlertView …] show];
})
//swift版
firstly {
    NSURLSession.GET(url)
}.then { (json: NSDictionary) in
    NSURLConnection.GET(json["avatar_url"])
}.then { (image: UIImage) in
    self.imageView.image = image
}.error { error in
    UIAlertView(…).show()
}

用了catch以后,在傳遞promise的鏈中戏溺,一旦中間任何一環(huán)產(chǎn)生了錯誤于购,都會傳遞到catch去執(zhí)行Error Handler。

4.when
通常我們有這種需求:
在執(zhí)行一個A任務(wù)之前還有1控淡,2個異步的任務(wù)掺炭,在全部異步操作完成之前炕矮,需要阻塞A任務(wù)肤视。代碼可能會寫的像下面這樣子:


__block int x = 0;
void (^completionHandler)(id, id) = ^(MKLocalSearchResponse *response, NSError *error){
    if (++x == 2) {
        [self finish];
    }
};
[[[MKLocalSearch alloc] initWithRequest:rq1] startWithCompletionHandler:completionHandler];
[[[MKLocalSearch alloc] initWithRequest:rq2] startWithCompletionHandler:completionHandler];

這里就可以使用when來優(yōu)雅的處理這種情況:


id search1 = [[[MKLocalSearch alloc] initWithRequest:rq1] promise];
id search2 = [[[MKLocalSearch alloc] initWithRequest:rq2] promise];

PMKWhen(@[search1, search2]).then(^(NSArray *results){
    //…
}).catch(^{
    // called if either search fails
});

在when后面?zhèn)魅胍粋€數(shù)組邢滑,里面是2個promise困后,只有當這2個promise都執(zhí)行完摇予,才會去執(zhí)行后面的then的操作趾盐。這樣就達到了之前所說的需求救鲤。

這里when還有2點要說的本缠,when的參數(shù)還可以是字典。


id coffeeSearch = [[MKLocalSearch alloc] initWithRequest:rq1];
id beerSearch = [[MKLocalSearch alloc] initWithRequest:rq2];
id input = @{@"coffee": coffeeSearch, @"beer": beerSearch};

PMKWhen(input).then(^(NSDictionary *results){
    id coffeeResults = results[@"coffee"];
});

這個例子里面when傳入了一個input字典楣黍,處理完成之后依舊可以生成新的promise傳遞到下一個then中租漂,在then中可以去到results的字典哩治,獲得結(jié)果业筏。傳入字典的工作原理放在第四章會解釋蒜胖。

when傳入的參數(shù)還可以是一個可變的屬性:


@property id dataSource;

- (id)dataSource {
    return dataSource ?: [PMKPromise new:…];
}

- (void)viewDidAppear {
    [PMKPromise when:self.dataSource].then(^(id result){
        // cache the result
        self.dataSource = result;
    });
}

dataSource如果為空就新建一個promise妖啥,傳入到when中荆虱,執(zhí)行完之后怀读,在then中拿到result菜枷,并把result賦值給dataSource啤誊,這樣dataSource就有數(shù)據(jù)了。由此看來牡昆,when的使用非常靈活丢烘!

5.always & finally

//oc版
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self myPromise].then(^{
    //…
}).finally(^{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
})
//swift版
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
myPromise().then {
    //…
}.always {
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}

在我們執(zhí)行完then播瞳,處理完error之后赢乓,還有一些操作,那么就可以放到finally和always里面去執(zhí)行尼斧。

四.PromiseKit的源碼解析

經(jīng)過上面對promise的方法的學(xué)習(xí)棺棵,我們已經(jīng)可以了解到烛恤,在異步操作我們可以通過不斷的返回promise苹熏,傳遞給后面的then來形成鏈式調(diào)用轨域,所以重點就在then的實現(xiàn)了干发。在討論then之前,我先說一下promise的狀態(tài)和傳遞機制琼讽。

一個promise可能有三種狀態(tài):等待(pending)自点、已完成(fulfilled)桂敛、已拒絕(rejected)术唬。
一個promise的狀態(tài)只可能從“等待”轉(zhuǎn)到“完成”態(tài)或者“拒絕”態(tài),不能逆向轉(zhuǎn)換塘淑,同時“完成”態(tài)和“拒絕”態(tài)不能相互轉(zhuǎn)換存捺。
promise必須實現(xiàn)then方法(可以說捌治,then就是promise的核心)肖油,而且then必須返回一個promise森枪,同一個promise的then可以調(diào)用多次,并且回調(diào)的執(zhí)行順序跟它們被定義時的順序一致
then方法接受兩個參數(shù)显拳,第一個參數(shù)是成功時的回調(diào)杂数,在promise由“等待”態(tài)轉(zhuǎn)換到“完成”態(tài)時調(diào)用,另一個是失敗時的回調(diào)那伐,在promise由“等待”態(tài)轉(zhuǎn)換到“拒絕”態(tài)時調(diào)用。同時诉探,then可以接受另一個promise傳入肾胯,也接受一個“類then”的對象或方法毕荐,即thenable對象


總結(jié)起來就是上圖,pending狀態(tài)的promise對象既可轉(zhuǎn)換為帶著一個成功值的 fulfilled 狀態(tài)丁恭,也可變?yōu)閹е粋€ error 信息的 rejected 狀態(tài)。當狀態(tài)發(fā)生轉(zhuǎn)換時牲览, promise.then 綁定的方法就會被調(diào)用墓陈。(當綁定方法時,如果 promise 對象已經(jīng)處于 fulfilled 或 rejected 狀態(tài)第献,那么相應(yīng)的方法將會被立刻調(diào)用贡必, 所以在異步操作的完成情況和它的綁定方法之間不存在競爭關(guān)系。)從Pending轉(zhuǎn)換為fulfilled或Rejected之后庸毫, 這個promise對象的狀態(tài)就不會再發(fā)生任何變化仔拟。因此 then是只被調(diào)用一次的函數(shù),從而也能說明飒赃,then生成的是一個新的promise炒事,而不是原來的那個睡扬。

了解完流程之后骇钦,就可以開始繼續(xù)研究源碼了。在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);
    };
}

這四個方法底層調(diào)用了各自的thenon驱富,catchon叫榕,finallyon方法伶选,這些on的方法實現(xiàn)基本都差不多掸鹅,那我就以最重要的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就是返回一個方法,所以繼續(xù)往下看

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

這個方法看上去很復(fù)雜享怀,仔細看看履植,函數(shù)的形參其實就是2個block眷蚓,一個是resolved的block,還有一個是pending的block扒披。當一個promise經(jīng)歷過resolved之后价说,可能是fulfill兵迅,也可能是reject构拳,之后生成next新的promise沪伙,傳入到下一個then中塞赂,并且狀態(tài)會變成pending。上面代碼中第一個return延欠,如果next為nil衫冻,那么意味著promise沒有生成贰镣,這是會再調(diào)用一次mkresolvedCallback永部,并傳入?yún)?shù)result衫画,生成的PMKResolveOnQueueBlock毫炉,再次傳入(q, block),直到next的promise生成削罩,并把pendingCallback存入到handler當中瞄勾。這個handler存了所有待執(zhí)行的block,如果把這個數(shù)組里面的block都執(zhí)行弥激,那么就相當于依次完成了上面的所有異步操作进陡。第二個return是在callblock為nil的時候,還會再調(diào)一次mkresolvedCallback(result)微服,保證一定要生成next的promise趾疚。

這個函數(shù)里面的這里dispatch_barrier_sync這個方法,就是promise后面可以鏈式調(diào)用then的原因以蕴,因為GCD的這個方法糙麦,讓后面then變得像一行行的then順序執(zhí)行了。

可能會有人問了丛肮,并沒有看到各個block執(zhí)行赡磅,僅僅只是加到handler數(shù)組里了,這個問題的答案宝与,就是promise的核心了焚廊。promise執(zhí)行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);
}

這是一個遞歸函數(shù)习劫,能形成遞歸的條件就是那句PMKResolve(this, o);當nextResult = nil的時候咆瘟,就代表了這個promise還是pending狀態(tài),還沒有被執(zhí)行榜聂,這個時候就要遞歸調(diào)用搞疗,直到nextResult不為nil嗓蘑。不為nil须肆,就會調(diào)用set方法,set方法是一個匿名函數(shù)桩皿,里面的for循環(huán)會依次循環(huán)豌汇,執(zhí)行handler數(shù)組里面的每一個block。里面的那個if語句泄隔,是先判斷result是否是一個promise拒贱,如果不是promise,就去執(zhí)行set方法,依次調(diào)用各個block逻澳。

至此闸天,一個then的執(zhí)行原理就到此結(jié)束了。接下來我們再看看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的數(shù)組里面笼吟,依次執(zhí)行各個promise,結(jié)果最后傳給新生成的一個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的數(shù)組方式基本一樣诱告,只不過多了一步撵枢,就是從字典里面先取出promise[key],然后再繼續(xù)對這個promise執(zhí)行操作而已精居。所以when可以傳入以promise為value的字典诲侮。

五.使用PromiseKit優(yōu)雅的處理回調(diào)地獄

這里我就舉個例子,大家一起來感受感受用promise的簡潔箱蟆。
先描述一下環(huán)境沟绪,假設(shè)有這樣一個提交按鈕,當你點擊之后空猜,就會提交一次任務(wù)绽慈。首先要先判斷是否有權(quán)限提交,沒有權(quán)限就彈出錯誤辈毯。有權(quán)限提交之后坝疼,還要請求一次,判斷當前任務(wù)是否已經(jīng)存在谆沃,如果存在钝凶,彈出錯誤。如果不存在唁影,這個時候就可以安心提交任務(wù)了耕陷。


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層回調(diào),看上去就很暈据沈,接下來我們用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行左右,看上去比原來清爽多了锌介,可讀性更高嗜诀。

最后

看完上面關(guān)于PromiseKit的使用方法之后猾警,其實對于PromiseKit,我個人的理解它就是一個Monad(這是最近很火的一個概念隆敢,4月底在上海SwiftCon 2016中发皿,唐巧大神分享的主題就是關(guān)于Monad,還不是很了解這個概念的可以去他博客看看拂蝎,或者找視頻學(xué)習(xí)學(xué)習(xí)雳窟。)Promise就是一個盒子里面封裝了一堆操作,then對應(yīng)的就是一組flatmap或map操作匣屡。不過缺點也還是有封救,如果網(wǎng)絡(luò)用的AFNetWorking,網(wǎng)絡(luò)請求很有可能會回調(diào)多次捣作,這時用PromiseKit誉结,就需要自己封裝一個屬于自己的promise了。PromiseKit原生的是用的OMGHTTPURLRQ這個網(wǎng)絡(luò)框架券躁。PromiseKit里面自帶的封裝的網(wǎng)絡(luò)請求也還是基于NSURLConnection的惩坑。所以用了AFNetWorking的同學(xué),要想再優(yōu)雅的處理掉網(wǎng)絡(luò)請求引起的回調(diào)地獄的時候也拜,自己還是需要先封裝一個自己的Promise以舒,然后優(yōu)雅的then一下。很多人可能看到這里慢哈,覺得我引入一個框架蔓钟,本來是來解決問題的,但是現(xiàn)在還需要我再次封裝才能解決問題卵贱,有點不值得滥沫。

我自己的看法是,PromiseKit是個解決異步問題很優(yōu)秀的一個開源庫键俱,尤其是解決回調(diào)嵌套兰绣,回調(diào)地獄的問題,效果非常明顯编振。雖然需要自己封裝AFNetWorking的promise缀辩,但是它的思想非常值得我們學(xué)習(xí)的!這也是接下來第二篇想和大家一起分享的內(nèi)容踪央,利用promise的思想臀玄,自己來優(yōu)雅的處理回調(diào)地獄!這一篇PromiseKit先分享到這里杯瞻。

如有錯誤镐牺,還請大家請多多指教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魁莉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旗唁,老刑警劉巖畦浓,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異检疫,居然都是意外死亡讶请,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門屎媳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夺溢,“玉大人,你說我怎么就攤上這事烛谊》缦欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵丹禀,是天一觀的道長状勤。 經(jīng)常有香客問我,道長双泪,這世上最難降的妖魔是什么持搜? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮焙矛,結(jié)果婚禮上葫盼,老公的妹妹穿的比我還像新娘。我一直安慰自己村斟,他們只是感情好剪返,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邓梅,像睡著了一般脱盲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上日缨,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天钱反,我揣著相機與錄音,去河邊找鬼匣距。 笑死面哥,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的毅待。 我是一名探鬼主播尚卫,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尸红!你這毒婦竟也來了吱涉?” 一聲冷哼從身側(cè)響起刹泄,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怎爵,沒想到半個月后特石,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡鳖链,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年姆蘸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芙委。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡逞敷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灌侣,到底是詐尸還是另有隱情推捐,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布顶瞳,位于F島的核電站玖姑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏慨菱。R本人自食惡果不足惜焰络,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望符喝。 院中可真熱鬧闪彼,春花似錦、人聲如沸协饲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茉稠。三九已至描馅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間而线,已是汗流浹背铭污。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留膀篮,地道東北人嘹狞。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像誓竿,于是被迫代替她去往敵國和親磅网。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容