Objective-C 之 PromiseKit入門

圖片來源:https://github.com/mxcl/PromiseKit

1、PromiseKit介紹

PromiseKit,優雅的的管理多個異步操做,讓你今後遠離多層嵌套。html

用一個例子做比較:git

需求:先註冊,後登陸,提示登陸成功。github

// 常規方法:嵌套執行多個任務
[self signUpWithUserName:uid pwd:pwd resultBlock:^(BOOL success, NSString *message) {
    if (success) {
        [self signInWithUserName:uid pwd:pwd resultBlock:^(BOOL success, NSString *message) {
            if (success) {
                NSLog(@"登陸成功");
            }
        }];
    }
}];
複製代碼
// PromiseKit提供的方法,簡潔清晰的展現多任務操做
signUpPromise.then(^{
    return signInPromise;
}).then(^{
    NSLog(@"登陸成功");
}).catch(^(NSError* error){
});
複製代碼

因而可知,用PromiseKit實現的方法更美觀,且更易維護。算法

2、PromiseKit的使用

仍是上面那個需求,下面是具體實現步驟:數組

1.  修改現有的註冊任務和登陸任務,以下:
// 註冊僞代碼(登陸代碼相似)
- (AnyPromise*)promise_signUpWithUserName:(NSString*)uid pwd:(NSString*)pwd {
   return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter  _Nonnull adapter) {
          // 網絡請求 xxx
          if(註冊成功) {
            adapter(@"註冊成功", nil);
          }else {
            adapter(nil,[self errorWithMessage:"註冊失敗!"]); // error本身定義
          }
    }];
}
- (NSError*)errorWithMessage:(NSString*)msg {
    return [[NSError alloc] initWithDomain:NSNetServicesErrorDomain code:-101 userInfo:@{NSLocalizedDescriptionKey:msg}];
}

2.  在一個地方管理全部異步操做
[HTTPUtil promise_signUpWithUserName:uid pwd:pwd].then(^{// 註冊成功走這裏
    NSLog(@"註冊成功");
    return [HTTPUtil promise_signInWithUserName:uid pwd:pwd];// 接下來執行登陸操做
}).then(^(NSDate* date){// 登陸成功走這裏
    NSLog(@"登陸成功, 時間:%@",date);
}).catch(^(NSError* error){// 註冊失敗或者登陸失敗跳到這裏,不會執行then方法
    NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
});

複製代碼

接下來逐句解讀該段代碼,並穿插知識點。xcode

2.1 建立任務

AnyPromise能夠看做是任務,下文以任務代替。promise

- (AnyPromise*)promise_signUpWithUserName:(NSString*)uid pwd:(NSString*)pwd {
   return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter  _Nonnull adapter) {
    }];
}
複製代碼

任務的全部初始化方法以下,可在源碼查看它們說明,這裏不一一解釋:安全

// 經常使用
+ (instancetype __nonnull)promiseWithAdapterBlock:(void (^ __nonnull)(PMKAdapter __nonnull adapter))block; 
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock; 

+ (instancetype __nonnull)promiseWithValue:(__nullable id)value;
+ (instancetype __nonnull)promiseWithIntegerAdapterBlock:(void (^ __nonnull)(PMKIntegerAdapter __nonnull adapter))block;
+ (instancetype __nonnull)promiseWithBooleanAdapterBlock:(void (^ __nonnull)(PMKBooleanAdapter __nonnull adapter))block;
- (instancetype __nonnull)initWithResolver:(PMKResolver __strong __nonnull * __nonnull)resolver ;

複製代碼

2.2 處理Block

任務的初始化方法中附有一個Block:PMKAdapter,其返回的參數表達了該任務的處理結果是成功仍是失敗,如:bash

if(登陸成功) {
    adapter([NSDate date], nil);// 處理成功
}else {
    adapter(nil, error); // 處理失敗
}
複製代碼

任務有2種狀態:pendingresolved(本人習慣翻譯爲:等待處理和處理結束), 其中resolved包括fulfilled(處理成功)和rejected(處理失敗)。網絡

爲何上段代碼中adapter([NSDate date], nil);表明處理成功,而adapter(nil, error);表明處理失敗呢?

接下來了解PMKAdapter

PMKAdapter能夠傳遞2個參數,第一個是id類型的任何對象,能夠是nil,第二個是NSError對象,也能夠是nil。傳遞不一樣參數,任務的狀態會不一樣,以下圖所示:

傳遞不一樣參數,AnyPromise的狀態不同

所以能夠總結PMKAdapter的參數:

總結一:若是任意一個參數的類型是NSError任務 處理失敗

總結二:2個參數都是nil或者第一個是id,第二個是nil,任務 處理成功

處理結果將影響第三步的操做。

下面是全部Block的定義:

typedef void (^PMKAdapter)(id __nullable, NSError * __nullable) ;
typedef void (^PMKResolver)(id __nullable);
typedef void (^PMKIntegerAdapter)(NSInteger, NSError * __nullable) ;
typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) ;
複製代碼

2.3 在一個地方管理全部異步操做

signUpPromise.then(^{// 註冊成功走這裏
    NSLog(@"註冊成功");
    return signInPromise;// 返回signInPromise:接下來執行登陸操做
}).then(^(NSDate* date){// 登陸成功走這裏
    NSLog(@"登陸成功, 時間:%@",date);
}).catch(^(NSError* error){// 註冊失敗或者登陸失敗跳到這裏
    NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
});
複製代碼

若是任務 處理成功,就會執行緊接其後的then方法;若是處理失敗,則會跳事後面的全部then方法,執行catch方法。

怎樣才能使用Block傳遞過來的參數呢?

第一:若是任務 處理成功,能夠在then方法裏面手動增長最多3個參數:

.then(^(NSDate* date, ..., ...){
    NSLog(@"date: %@",date);
})
複製代碼

第二:若是任務 處理失敗,能夠把參數傳給NSError

adapter(nil,[self errorWithMessage:"登陸失敗!"]);
複製代碼

而後在catch方法提取:

.catch(^(NSError* error){
    NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
})
複製代碼

3、PromiseKit之AnyPromise

AnyPromise是PromiseKit的關鍵類,一個AnyPromise對象能夠看做是一個任務。

1 屬性

@property (nonatomic, readonly) __nullable id value; // 已經處理的Promise的值
@property (nonatomic, readonly) BOOL pending; // 等待處理
@property (nonatomic, readonly) BOOL fulfilled; // 處理成功
@property (nonatomic, readonly) BOOL rejected; // 處理失敗
複製代碼

2 鏈接詞

2.1 then

then:當任務 處理成功,在主線程執行。

- (AnyPromise * __nonnull (^ __nonnull)(id __nonnull))then;
複製代碼

thenInBackground:當任務 處理成功,在默認線程執行。

- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))thenInBackground;
複製代碼

thenOn:當任務 處理成功,在指定線程執行。

- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))thenOn;
複製代碼

2.2 catch

catch:當任務 處理失敗,在主線程運行。

- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch;
複製代碼

catchInBackground:當任務 處理失敗,在global線程執行。

- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground;
複製代碼

catchOn:當任務 處理失敗,在指定線程執行。

- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn;
複製代碼

2.3 ensure

ensure:當任務 處理結束,在主線程執行。

- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure;
複製代碼

ensureOn:當任務 處理結束,在指定線程執行。

- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn;
複製代碼

全部鏈接詞在指定線程執行的用法相似:

.ensureOn(queue, ^{
})
複製代碼

3 進階用法

3.1 PMKJoin

AnyPromise *__nonnull PMKJoin(NSArray * __nonnull promises);
複製代碼

等待全部任務 處理結束,執行下一步。(全部任務 處理結束纔會處理處理失敗,若是有就跳轉到then方法)

官方註釋:

PMKJoin waits on all provided promises, then rejects if any of those promises rejects, otherwise it fulfills with values from the provided promises.

大意:PMKJoin將會等待全部任務promises參數提供)處理結束,以後若是有一個任務 處理失敗PMKJoin 纔會處理失敗,不然處理成功

例子:吃完飯,喝完湯,才能吃水果。

PMKJoin(@[ricePromise, soupPromise]).then(^(NSArray* messages){// 此時參數是數組類型
    return fruitPromise;
}).catch(^(NSError* error){
    NSArray* promises = error.userInfo[PMKJoinPromisesKey];// 錯誤信息中包含了全部任務
     for (AnyPromise* promise in promises) {
        if (promise.rejected) {
            NSLog(@"%@ is rejected",promise);
        }
     }
});
複製代碼

3.2 PMKWhen

extern AnyPromise * __nonnull PMKWhen(id __nonnull input);
複製代碼

等待全部任務 處理成功,執行下一步。(一旦有任務 處理失敗,當即跳轉到thencatch方法,且中止執行其餘任務)

官方註釋:

PMKWhen rejects as soon as one of the provided promises rejects.

大意:只要其中一個任務input參數提供)處理失敗PMKWhen當即處理失敗

例子:作飯,拿碗筷,才能吃飯。

PMKWhen(@[cookPromise, setPromise]).then(^(NSArray* messages){// 此時參數是數組類型
    return eatPromise;
}).catch(^(NSError* error) {// "處理失敗"的任務
    NSInteger index = [error.userInfo[PMKFailingPromiseIndexKey] integerValue];
    NSLog(@"index:%@ error: %@\n", @(index),error.userInfo[NSLocalizedDescriptionKey]);
});
複製代碼

3.3 PMKRace

extern AnyPromise * __nonnull PMKRace(NSArray * __nonnull promises);
複製代碼

只要處理結束一個任務,當即執行then或者catch方法,其餘任務繼續執行。(只關心第一個處理結束任務,不關心其餘任務

例子:在衆多算法中找出解題速度最快的那個。

PMKRace(@[algPromise1, algPromise2, algPromise3]).then(^(id message){
    NSLog(@"%@",message);
}).catch(^(NSError* error){
    NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey]);
});
複製代碼

3.4 PMKAfter

extern AnyPromise * __nonnull PMKAfter(NSTimeInterval duration);
複製代碼

延遲必定時間執行任務,至關於dispatch_after

例子:註冊成功後3秒再登陸。

signUpPromise.then(^{
    // 註冊成功
    return PMKAfter(3.f).then(^{// 延遲執行signInPromise
        return signInPromise;
    });
}).then(^{
    // 登陸成功
}).catch(^(NSError* error){
    // handle error
});

複製代碼

3.5 PMKHang

extern id __nullable PMKHang(AnyPromise * __nonnull promise);
複製代碼

阻塞線程,直到任務 處理結束。這個作法不安全,只在調試時用!!!

例子:阻塞當前線程,直到註冊成功或者註冊失敗纔會執行next step。

id value = PMKHang([self promise_signUpWithUserName:uid pwd:pwd]);
NSLog(@"%@ is resolved",value);

// next step
// ...
複製代碼

4. 綜合使用

設定任務執行的超時時間

// 限定註冊任務的超時時間爲2秒
PMKRace(@[PMKWhen([HTTPUtil promise_signUpWithUserName:uid pwd:pwd]), PMKAfter(2.).then(^{
    NSLog(@"計時結束~");
})]).then(^(NSString* msg){
    NSLog(@"%@",msg);
});
複製代碼

4、PromiseKit的集成(手動)

剛着手把PromiseKit集成到項目的時候,按照網上的教程,不管是CocoaPods仍是手動安裝都失敗,通過摸索,步驟以下:

版本:Xcode 10.1,PromiseKit 6.8.4

  1. 下載項目

  2. 把PromiseKit項目放在你的項目文件夾,如圖:

文件夾位置

  1. 而後把PromiseKit.xcodeproj文件拖到你的項目

項目位置

  1. 前往Target-Embbedded Binaries添加PromiseKit.framework,如圖:

導入Framework

  1. 導入頭文件,編譯
#import <PromiseKit/PromiseKit.h>
複製代碼

此時可能報錯:

編譯報錯

解決方法:把Build Active Architecture Only屬性改成YES

修改屬性

這樣,就能在模擬器上使用PromiseKit。可是依然不能在真機上運行,可能報錯:

運行報錯

解決方法:修改PromiseKit的可用架構

修改可用架構
報錯緣由:因爲在OC中使用了Swift,編譯成功後Xcode會生成PromiseKit-Swift.h文件。若是編譯失敗,則沒有此文件,所以報錯。

到此,成功集成PromiseKit。

簡書地址

參考:

juejin.im/post/5c78ea…

juejin.im/post/5c1649…

www.hangge.com/blog/cache/…

相關文章
相關標籤/搜索