ReactiveCocoa網(wǎng)絡(luò)層回訪

2018-08-01 16:31 更新

還有一個(gè)機(jī)會(huì)來進(jìn)一步接受我們函數(shù)反應(yīng)型編程的理念,那就是我們的網(wǎng)絡(luò)層 FRPPhotoImporter,我們先來看看下載圖片的方法:

+ (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel {
    [self download:photoModel.thumbnailURL withCompletion:^(NSData *data) {
        photoModel.thumbnailData = data;
    }];
}

+ (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
    [self download:photoModel.fullsizedURL withCompletion:^(NSData *data){
        photoModel.fullsizedData = data;
    }];
}

+ (void)download:(NSString *)urlString withCompletion:(void (^)(NSData *data))completion {
    NSAssert(urlString, @"URL must not be nil");

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    [NSURLConnection sendAsynchronousRequest:request
                                queue:[NSOperationQueue mainQueue]
                                completionHandler:
                                     ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                                            if(completion) {
                                                completion(data);
                                            }
                                     }];
}

Completion blocks?這是另外一個(gè)使用Signals的機(jī)會(huì)。更深入一點(diǎn)來說,我們可以使用NSURLConnection的ReactiveCocoa的擴(kuò)展。下面我們來重寫上面的方法:

+ (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel {
    RAC(photoModel, thumbnailData) = [self download:photoModel.thumbnailURL];
}

+ (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
    RAC(photoModel,fullsizedData) = [self download:photoModel.fullsizedURL];
}

+ (RACSignal *)download:(NSString *)urlString {
    NSAssert(urlString , @"URL must not be nil");

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: urlString]];

    return [[[NSURLConnection rac_sendAsynchronousRequest:request]
                map:^id (RACTuple *value) {
                    return [value second];
                }] deliverOn:[RACScheduler mainThreadScheduler]];
}

這里有兩個(gè)大的不同:

  1. 我們使用RAC來綁定downloadFullsizedImageForPhotoModel:返回的信號(hào)的最新值。
  2. 我們返回NSURLConnection的rac_sendAsynchronousRequest:返回值的映射。

我們來看看這里究竟發(fā)生了什么。 看文檔:rac_sendAsynchronousRequest:返回一個(gè)發(fā)送網(wǎng)絡(luò)請(qǐng)求響應(yīng)值的信號(hào)。RACTuple它所發(fā)送的內(nèi)容分別包含響應(yīng)和數(shù)據(jù)。有網(wǎng)絡(luò)錯(cuò)誤發(fā)生時(shí),它會(huì)拋出錯(cuò)誤。 最后我們改變線程的調(diào)度,將signal切換到主線程上。 (一個(gè)線程的調(diào)度者類似于一個(gè)線程。)

看,網(wǎng)絡(luò)信號(hào)將會(huì)把它的值返回給后臺(tái)的調(diào)度者,如果我們不阻止它,它可能最終會(huì)去從事更新UI的事件,而后臺(tái)線程是沒有能力更新UI的。

我們回過頭來看看最開始的那兩行。注意下這行:

RAC(photoModel, thumbnailData) = [self download:photoModel.thumbnailURL];

通常,我不推薦將一個(gè)model綁定到多個(gè)signal,然而,我們知道這個(gè)信號(hào)會(huì)在完成網(wǎng)絡(luò)調(diào)用后立即執(zhí)行完并結(jié)束訂閱。只要我們僅在一個(gè)實(shí)例上綁定這個(gè)keyPath,這種就是安全的。

我們可以用類似的方式抽象掉使用RACReplaySubject的部分,來重新審視我們的fetchPhotoDetails:方法吧。

+ (RACReplaySubject *)fetchPhotoDetails:(FRPPhotoModel *)photoModel {
    RACReplaySubject *subject = [RACReplaySubject subject];

    NSURLRequest *request = [self photoURLRequest:photoModel];
    [NSURLConnection sendAsynchronousRequest:request
                 queue:[NSOperationQueue mainQueue]
    completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            if(data) {
                id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil][@"photo"];
                [self configurePhotoModel:photoModel withDictionay:results];
                [self downloadFullsizedImageForPhotoModel:photoModel];
                [subject sendNext:photoModel];
                [subject sendCompleted];
            }
            else {
                [subject sendError:connectionError];
            }
    }];


    return subject;

}

有一點(diǎn)點(diǎn)凌亂,我們來整理下。

+ (RACSignal *)fetchPhotoDetails:(FRPPhotoModel *)photoModel {
    NSURLRequest *request = [self photoURLRequest:photoModel];
    return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request]
                                map:^id(RACTuple *value){
                                    return [value second];
                                }]
                                deliverOn:[RACScheduler mainThreadScheduler]]
                                    map:^id (NSData *data) {
                                        id results = [NSJSONSerialization JSONObjectWithData:data
                                                                       options:0 error:nil][@"photo"];
                                        [self configurePhotoModel:photoModel withDictionary:results];
                                        [self downloadFullsizedImageForPhotoModel:photoModel];
                                        return photoModel;
                                    }] publish] autoconnect];
}

注意: 返回值從RACReplaySubject *變成了RACSignal *. 這里有很多地方需要梳理,所以我們提前做了下面這個(gè)示意圖來說明:

RACSignal_Process_Diagram

我們已經(jīng)知道deliverOn:是怎樣工作的,所以讓我們來關(guān)注信號(hào)鏈條最末端的信號(hào)操作publishpublish返回一個(gè)RACMulitcastConnection,當(dāng)信號(hào)連接上時(shí),他將訂閱該接收信號(hào)。autoconnect為我們做的是:當(dāng)它返回的信號(hào)被訂閱,連接到 該(訂閱背后的)信號(hào)(underly signal)。

執(zhí)行獲取每一個(gè)訂閱,在訂閱的時(shí)候,我們返回的信號(hào)將會(huì)變“冷”。那是因?yàn)槲覀儗?duì)底層信號(hào)進(jìn)行多播,網(wǎng)絡(luò)請(qǐng)求只會(huì)執(zhí)行一次,但是它的結(jié)果被多播。這會(huì)導(dǎo)致:網(wǎng)絡(luò)信號(hào)將只會(huì)被執(zhí)行一次(當(dāng)它被訂閱時(shí)執(zhí)行),是冷的(直到訂閱為止,它不會(huì)被執(zhí)行),甚至可刪除的(如果一次性處理訂閱的生成)。

基本上,我們能保證信號(hào)只會(huì)被訂閱一次,我們不需要回滾(replay).

注意:我們可以用下面的reduceEach:替代使用RACTuple的第一個(gè)map:,以便提供編譯時(shí)檢查。

reduceEach:^id(NSURLResponse *response, NSData *data) {
    return data;
}]

剩下的網(wǎng)絡(luò)訪問接口,importPhotos方法重構(gòu)如下:

+ (RACSignal *)importPhotos {
    NSURLRequest *request = [self popularURLRequest];

    return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request]
                reduceEach:^id(NSURLResponse *response , NSData *data){
                    return data;
                }]
                deliverOn:[RACScheduler mainThreadScheduler]]
                map:^id (NSData *data) {
                    id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                    return [[[results[@"photo"] rac_sequence]
                        map:^id (NSDictionary *photoDictionary) {
                            FRPPhotoModel *model = [FRPPhotoModel new];
                            [self configurePhotoModel:model withDictionary:photoDictionary];
                            [self downloadThumbnailForPhotoModel:model];
                            return model;
                        }] array];
                }] publish] autoconnect];
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)