前言
最近看了一些Swift關於封裝非同步操作過程的文章,比如RxSwift,RAC等等,因為回撥地獄我自己也寫過,很有感觸,於是就翻出了Promise來研究學習一下。現將自己的一些收穫分享一下,有錯誤歡迎大家多多指教。
目錄
- 1.PromiseKit簡介
- 2.PromiseKit安裝和使用
- 3.PromiseKit主要函式的使用方法
- 4.PromiseKit的原始碼解析
- 5.使用PromiseKit優雅的處理回撥地獄
一.PromiseKit簡介
PromiseKit是iOS/OS X 中一個用來出來非同步程式設計框架。這個框架是由Max Howell(Mac下Homebrew的作者,傳說中因為”不會”寫反轉二叉樹而沒有拿到Google offer)大神級人物開發出來的。
在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作為引數,模組化的從一個非同步任務到下一個非同步任務中去。用PromiseKit寫出的程式碼就是這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[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就是用來乾淨簡潔的程式碼,來解決非同步操作,和奇怪的錯誤處理回撥的。它將非同步操作變成了鏈式的呼叫,簡單的錯誤處理方式。
PromiseKit裡面目前有2個類,一個是Promise<T>(Swift),一個是AnyPromise(Objective-C),2者的區別就在2種語言的特性上,Promise<T>是定義精確嚴格的,AnyPromise是定義寬鬆,靈活,動態的。
在非同步程式設計中,有一個最最典型的例子就是回撥地獄CallBack hell,要是處理的不優雅,就會出現下圖這樣:
上圖的程式碼是真實存在的,也是朋友告訴我的,來自快的的程式碼,當然現在人家肯定改掉了。雖然這種程式碼看著像這樣:
程式碼雖然看上去不優雅,功能都是正確的,但是這種程式碼基本大家都自己寫過,我自己也寫過很多。今天就讓我們動起手來,用PromiseKit來優雅的處理掉Callback hell吧。
二.PromiseKit安裝和使用
1.下載安裝CocoaPods
在牆外的安裝步驟:
在Terminal裡面輸入
1 |
sudo gem install cocoapods && pod setup |
大多數在牆內的同學應該看如下步驟了:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//移除原有的牆外Ruby 預設源 $ gem sources --remove https://rubygems.org/ //新增現有的牆內的淘寶源 $ gem sources -a https://ruby.taobao.org/ //驗證新源是否替換成功 $ gem sources -l //下載安裝cocoapods // OS 10.11之前 $ sudo gem install cocoapods //mark:OS 升級 OS X EL Capitan 後命令應該為: $ sudo gem install -n /usr/local/bin cocoapods //設定cocoapods $ pod setup |
1 |
$ touch Podfile && open -e Podfile |
此時會開啟TextEdit,然後輸入一下命令:
1 2 3 4 5 |
platform:ios, ‘7.0’ target 'PromisekitDemo' do //由於最新版cocoapods的要求,所以必須加入這句話 pod 'PromiseKit' end |
Tips:感謝qinfensky大神提醒,其實這裡也可以用init命令
Podfile是CocoaPods的特殊檔案,在其中可以列入在專案中想要使用的開源庫,若想建立Podfile,有2種方法:
1.在專案目錄中建立空文字檔案,命名為Podfile
2.或者可以再專案目錄中執行“$ pod init “,來建立功能性檔案(終端中輸入cd 資料夾地址,然後再輸入 pod init)
兩種方法都可以建立Podfile,使用你最喜歡使用的方法
3.安裝PromiseKit
1 |
$ pod install |
安裝完成之後,退出終端,開啟新生成的.xcworkspace檔案即可
三.PromiseKit主要函式的使用方法
- then
經常我們會寫出這樣的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (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]; } } |
上面的寫法也不是錯誤的,就是它在呼叫函式中儲存了一個屬性,在呼叫alertView會使用到這個屬性。其實這個中間屬性是不需要儲存的。接下來我們就用then來去掉這個中間變數。
1 2 3 4 5 6 7 |
- (void)showUndoRedoAlert:(UndoRedoState *)state { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:……]; [alert promise].then(^(NSNumber *dismissedButtonIndex){ [state do]; }); } |
這時就有人問了,為啥能呼叫 alert promise 這個方法?後面點語法跟著then是什麼?我來解釋一下,原因其實只要開啟Promise原始碼就一清二楚了。在pormise原始碼中
1 2 3 4 5 6 7 8 9 10 |
@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; |
對應的實現是這樣的
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; }]; } |
呼叫 alert promise 返回還是一個promise物件,在promise的方法中有then的方法,所以上面可以那樣鏈式的呼叫。上面程式碼裡面的fulfiller放在原始碼分析裡面去講講。
這些擴充套件類裡面就封裝了一些常用的生成promise方法,呼叫這些方法就可以愉快的一路.then執行下去了!
2.dispatch_promise
專案中我們經常會非同步的下載圖片
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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,我們可以將它改變成下面這樣:
1 2 3 4 5 6 7 |
dispatch_promise(^{ return [NSData dataWithContentsOfURL:url]; }).then(^(NSData * imageData){ self.imageView.image = [UIImage imageWithData:imageData]; }).then(^{ // add code to happen next here }); |
我們看看原始碼,看看呼叫的非同步過程對不對
1 2 3 4 5 6 7 8 9 |
- (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
在非同步操作中,處理錯誤也是一件很頭疼的事情,如下面這段程式碼,每次非同步請求回來都必須要處理錯誤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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; } }]; } } }]; |
1 2 3 4 5 6 7 8 |
//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]; }) |
1 2 3 4 5 6 7 8 9 10 |
//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() } |
總結起來就是上圖,pending狀態的promise物件既可轉換為帶著一個成功值的 fulfilled 狀態,也可變為帶著一個 error 資訊的 rejected 狀態。當狀態發生轉換時, promise.then 繫結的方法就會被呼叫。(當繫結方法時,如果 promise 物件已經處於 fulfilled 或 rejected 狀態,那麼相應的方法將會被立刻呼叫, 所以在非同步操作的完成情況和它的繫結方法之間不存在競爭關係。)從Pending轉換為fulfilled或Rejected之後, 這個promise物件的狀態就不會再發生任何變化。因此 then是隻被呼叫一次的函式,從而也能說明,then生成的是一個新的promise,而不是原來的那個。
瞭解完流程之後,就可以開始繼續研究原始碼了。在PromiseKit當中,最常用的當屬then,thenInBackground,catch,finally
用了catch以後,在傳遞promise的鏈中,一旦中間任何一環產生了錯誤,都會傳遞到catch去執行Error Handler。
4.when
通常我們有這種需求:
在執行一個A任務之前還有1,2個非同步的任務,在全部非同步操作完成之前,需要阻塞A任務。程式碼可能會寫的像下面這樣子:
1 2 3 4 5 6 7 8 |
__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來優雅的處理這種情況:
1 2 3 4 5 6 7 8 |
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後面傳入一個陣列,裡面是2個promise,只有當這2個promise都執行完,才會去執行後面的then的操作。這樣就達到了之前所說的需求。
這裡when還有2點要說的,when的引數還可以是字典。
1 2 3 4 5 6 7 |
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的字典,獲得結果。傳入字典的工作原理放在第四章會解釋。
when傳入的引數還可以是一個可變的屬性:
1 2 3 4 5 6 7 8 9 10 11 12 |
@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中,執行完之後,在then中拿到result,並把result賦值給dataSource,這樣dataSource就有資料了。由此看來,when的使用非常靈活!
5.always & finally
1 2 3 4 5 6 7 |
//oc版 [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; [self myPromise].then(^{ //… }).finally(^{ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; }) |
1 2 3 4 5 6 7 |
//swift版 UIApplication.sharedApplication().networkActivityIndicatorVisible = true myPromise().then { //… }.always { UIApplication.sharedApplication().networkActivityIndicatorVisible = false } |
在我們執行完then,處理完error之後,還有一些操作,那麼就可以放到finally和always裡面去執行。
四.PromiseKit的原始碼解析
經過上面對promise的方法的學習,我們已經可以瞭解到,在非同步操作我們可以通過不斷的返回promise,傳遞給後面的then來形成鏈式呼叫,所以重點就在then的實現了。在討論then之前,我先說一下promise的狀態和傳遞機制。
一個promise可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)。
一個promise的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換。
promise必須實現then方法(可以說,then就是promise的核心),而且then必須返回一個promise,同一個promise的then可以呼叫多次,並且回撥的執行順序跟它們被定義時的順序一致
then方法接受兩個引數,第一個引數是成功時的回撥,在promise由“等待”態轉換到“完成”態時呼叫,另一個是失敗時的回撥,在promise由“等待”態轉換到“拒絕”態時呼叫。同時,then可以接受另一個promise傳入,也接受一個“類then”的物件或方法,即thenable物件
總結起來就是上圖,pending狀態的promise物件既可轉換為帶著一個成功值的 fulfilled 狀態,也可變為帶著一個 error 資訊的 rejected 狀態。當狀態發生轉換時, promise.then 繫結的方法就會被呼叫。(當繫結方法時,如果 promise 物件已經處於 fulfilled 或 rejected 狀態,那麼相應的方法將會被立刻呼叫, 所以在非同步操作的完成情況和它的繫結方法之間不存在競爭關係。)從Pending轉換為fulfilled或Rejected之後, 這個promise物件的狀態就不會再發生任何變化。因此 then是隻被呼叫一次的函式,從而也能說明,then生成的是一個新的promise,而不是原來的那個。
瞭解完流程之後,就可以開始繼續研究原始碼了。在PromiseKit當中,最常用的當屬then,thenInBackground,catch,finally
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
- (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來分析一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
- (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就是返回一個方法,所以繼續往下看
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 |
- (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裡面的。先來看看原始碼
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 *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的原理。
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 |
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傳入一個字典,它會如何處理的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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的簡潔。
先描述一下環境,假設有這樣一個提交按鈕,當你點選之後,就會提交一次任務。首先要先判斷是否有許可權提交,沒有許可權就彈出錯誤。有許可權提交之後,還要請求一次,判斷當前任務是否已經存在,如果存在,彈出錯誤。如果不存在,這個時候就可以安心提交任務了。
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 |
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來整理一下。
1 2 3 4 5 6 7 8 9 10 11 12 |
[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先分享到這裡。
如有錯誤,還請大家請多多指教。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式