99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

MVVM的具體實踐

2018-08-01 16:11 更新

  本章的其他部分將把Functional Reactive Pixels Demo的其他代碼遷移到MVVM架構(gòu)中。我們將添加一個新的庫到Podfile文件里。Github上創(chuàng)作了ReactiveCocoa的黑客,也同時創(chuàng)建了一個ViewModel的基類:ReactiveViewModel.我們將要使用它的0.1.1版本。更新Podfile之后立即運行pod install以安裝該庫。

  重構(gòu)的第一個類是高清圖片視圖控制器。從這兒開始是因為它的業(yè)務(wù)邏輯比較少,抽象成viewModel時相對簡單。我們循序漸進,慢慢來。

  目前,我們的FRPFullSizePhotoViewController包含一個圖片數(shù)組和當前圖片(在數(shù)組中)的下標值。我們將把他們抽象到我們的視圖模型中來。

  從頭文件中移除自定義初始化,追加FRPFullSizePhotoViewModel的預(yù)申明。然后在這個新類中追加一個屬性。

@property (nonatomic ,strong ) FRPFullSizePhotoViewModel *viewModel;

  在實現(xiàn)文件里,#import這個新的視圖模型(別擔心,我們很快就會創(chuàng)建它),

#import "FRPFullSizePhotoViewModel.h"

  然后,移除photoModelArray私有屬性的申明。重寫我們的初始化方法以移除對photoModelArray實例的引用。代碼看起來應(yīng)該像下面這樣:

- (instancetype)init {
    self = [super init];
    if(!self) return nil;

    //ViewControllers
    self.pageViewController = [UIPageViewController alloc]
                    initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                    navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                       options:@{ UIPageViewControllerOptionInterPageSpacingKey : @30 };

    self.pageViewController.dataSource = self;
    self.pageViewController.delegate      = self;
    [self addChildViewController:self.pageViewController];

    return self;
}

  在你的ViewDidLoad:中添加如下代碼:

//Configure child view controllers
[self.pageViewController \
        setViewControllers: @[ [self photoViewControllerForIndex:self.viewModel.initialPhotoIndex] ]
        direction:UIPageViewControllerNavigationDirectionForward
        animated:NO
        completion:nil ];

//Configure self
self.title = [self.viewModel.initialPhotoModel photoName];

我們將要寫的這個我們提到的方法,對于veiwModel中發(fā)生的事情,給你一種XX感。最后,進到photoViewControllerForIndex方法中,它應(yīng)用了已經(jīng)解除分配的photoModelArray,用下面的實現(xiàn)替代它。

- (FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index {
    if (index >= 0 && index < self.viewModel.photoArray.coung ) {
        FRPPhotoModel *photoModel = self.viewModel.model[index];

        FRPPhotoViewController *photoViewController = \
            [[FRPPhotoViewController alloc] initWithPhotoModel:photoModel index:index];

        return photoViewController;
    }

    // Index was out of bounds, return nil
    return nil;
}

  好了!現(xiàn)在輪到我們的視圖模型本身了。創(chuàng)建一個新的RVMViewModel的子類,并將其命名為FRPFullSizedPhotoViewModel.基于它將要封裝的信息,以及我們在視圖控制器中的需求,我們知道,我們的頭文件看起來應(yīng)該是下面這樣:

@class FRPPhotoModel;

@interface FRPFullSizePhotoViewModel : RVMViewModel

- (instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInteger)initialPhotoIndex;
- (FRPPhotoModel *)photoModelAtIndex:(NSInteger)index;

@property (nonatomic , readonly, strong) NSArray *model;
@property (nonatomic, readonly) NSInteger initialPhotoIndex;
@property (nonatomic, readonly) NSString *initialPhotoName;

@end

  model屬性在RVMViewModel中被定義為id類型,我們把它重定義為NSArray. 我們也勾住了(即使用全局變量記錄)我們最初照片的索引(下標)并且給我們最初的照片名屬性定義了只讀屬性。這種微不足道的邏輯我們可以放到我們的視圖控制器中,但很快我們就會看到更為復(fù)雜的情況。

  我們來完成實現(xiàn)文件里的東西。第一件事就是:我們需要#import FRPPhotoModel類的頭文件。然后,我們將打開私有屬性的讀寫訪問權(quán)限。

//Model
#import "FRPPhotoModel.h"

@interface FRPFullSizePhotoViewModel ()
//private access
@property (nonatomic, assign) NSInteger initialPhotoIndex;

@end

  好!下一步處理我們的初始化方法

- (instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInteger)initialPhotoIndex {
    self = [super initWithModel:photoArray];
    if(!self) return nil;

    self.initialPhotoIndex = initialPhotoIndex;

    return self;
}

初始化方法中,先調(diào)用超類的initWithModel:實現(xiàn),然后設(shè)置自己的initialPhotoIndex屬性。剩下的兩個只讀屬性的獲取邏輯微不足道。

- (NSString *)initialPhotoName {
    return [[self photoModelAtIndex:self.initialPhotoIndex] photoName];
}

- (FRPPhotoModel *)photoModelAtIndex:(NSInteger)index {
    if(index < 0 || index > self.model.count - 1) {
        //Index was out of bounds, return nil
        return nil;
    }
    else {
        return self.model[ index ];
    }
}

  這樣做的另一個優(yōu)點是:業(yè)務(wù)邏輯不需要重復(fù)書寫,而且也使得業(yè)務(wù)邏輯非常好進行單元測試。

  最后,我們需要在高清視圖控制器中設(shè)置該視圖模型,否則屏幕上將不會顯示任何東西。導(dǎo)航到我們的畫廊視圖控制器(那個我們實例化并推出高清視圖控制器的地方)。用下面的代碼來替換這個業(yè)務(wù)邏輯:

[[self rac_signalForSelector:@selector(collectionView:didSelectItemAtIndexPath:)
    fromProtocol:@protocol(UIcollectionViewDelegate)] subscribeNext:^(RACTuple *arguments) {
        @strongify(self);

        NSIndexPath *indexPath = arguments.second;
        FRPFullSizePhotoViewModel *viewModel = [[FRPPhotoViewModel alloc]
            initWithPhotoArray:self.viewModel.model initialPhotoIndex:indexPath.item];

        FRPFullSizePhotoViewController *viewController = [[FRPFullSizePhotoViewController alloc] init];

        viewController.viewModel = viewModel;
        viewController.delegate = (id)self;

        [self.navigationController pushViewController:viewController animated:YES];
    }];

在下一節(jié)開始之前,我們沒有計劃為視圖模型撰寫單元測試。下一節(jié)我們看到在視圖模型上如何運行測試驅(qū)動開發(fā)的概念。現(xiàn)在我們來完成FRPGalleryViewModel吧,很基礎(chǔ)。我們想要從視圖控制器中抽象出來的邏輯是通過API加載model的數(shù)據(jù)內(nèi)容。我們來看一下應(yīng)該怎么做:


@interface FRPGalleryViewModel : RVMViewModel

@property (nonatomic, readonly, strong) NSArray *model;

@end

  基本的接口:將model申明為數(shù)組NSArray.接下來,我們簡單實現(xiàn)它:


//Utilities

#import "FRPPhotoImporter.h"

@interface FRPGalleryViewModel ()

@end

@implementation FRPGalleryViewModel

- (instancetype)init {
    self = [super init];
    if(!self) return nil;

    RAC(self, model) = [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]];

    return self;
}

@end

  有爭議的是,我們應(yīng)該把從API加載數(shù)據(jù)的(RAC綁定的)邏輯放在初始化方法中,還是放在視圖模型被激活的地方。接下來我們會討論更多的關(guān)于激活的內(nèi)容,但我想要展示給你們看這個視圖模型到底能做到多簡單。將直接在畫廊視圖控制器中加載數(shù)據(jù)內(nèi)容的邏輯遷移到畫廊的視圖模型中是非常簡單的:在視圖控制器的初始化中初始化視圖模型===》任何引用試圖控制self.model屬性的地方使用self.viewModel.model來代替即可。

  我們可以進一步深挖視圖模型的構(gòu)造,甚至可以通過一系列的訪問器把model的訪問邏輯抽象出來,但在這個例子里就有點過多‘抽象’了。更重要的是你可以根據(jù)你的喜好將更多的或者更少的業(yè)務(wù)邏輯抽象到視圖模型中。我發(fā)現(xiàn),就我個人而言,這個架構(gòu)使用的越多,業(yè)務(wù)邏輯抽象出來的越多,就意味著更輕量級的視圖控制器以及高內(nèi)聚和可測試的代碼。

  把注意力移到單元測試之前,我們來做多一次用視圖模型來抽象業(yè)務(wù)邏輯的實踐。

  我們的最后一個例子是FRPPhotoViewController上的FRPPhotoViewModel:創(chuàng)建一個RVMViewModel的視圖模型子類并放置在視圖控制器中(很快我們會回到視圖模型中)。

  視圖控制器的新的初始化方法如下:

- (instancetype)initWithViewModel:(FRPPhotoViewModel *)viewModel index:(NSInteger)photoIndex {

    self = [self init];//NS_DESIGNATED_INITIALIZER
    if(!self) return nil;

    self.viewModel = viewModel;
    self.photoIndex = photoIndex;

    return self;
}

  確定導(dǎo)入必要的頭文件并為視圖模型申明私有屬性。現(xiàn)在我們需要使用新的初始化方法初始化視圖控制器??匆豢匆晥D控制器到頁面視圖控制器的方法photoViewControllerForIndex:.

- (FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index {
    FRPPhotoModel *photoModel = [self.viewModel photoModelAtIndex:index];
    if(photoModel) {
        FRPPhotoViewModel *photoViewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel];
        FRPPhotoViewController *photoViewController = [[FRPPhotoViewController alloc] \
                             initWithViewModel:photoViewModel
                                          index:index];

        return photoViewController;
    }

    return nil;
}

  新的初始化過程中我們創(chuàng)建了一個視圖模型。

  在我們的viewDidLoad:方法里,我們將使用這個新的視圖模型為我們的圖片視圖提供數(shù)據(jù),并且為用戶顯示圖片的下載進度。這里有個貌似沖突的地方:圖片的下載是視圖的模型的業(yè)務(wù)邏輯之一,但視圖什么時候顯示開始加載數(shù)據(jù)(這個業(yè)務(wù)邏輯)視圖模型中沒有體現(xiàn)---記住一個好的視圖模型不應(yīng)該引用視圖本身。那么我們?nèi)绾蝸砘旌系厥褂眠@兩個業(yè)務(wù)邏輯?

  答案是我們借助視圖模型的active狀態(tài)來對付(上面的情況)。RVMViewModel提供了一個布爾屬性active,當試圖控制器變得"活躍"時(不管在語義的上下文里這是啥意思),在這里,我們可以在viewWillAppear:viewDidDisappear:這些方法來設(shè)置這個屬性。

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.viewModel.active = YES;
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    self.viewModel.active = NO;
}

相當簡單吧,我們來看一下我們新的viewDidLoad方法:

- (void)viewDidLoad {
    [super viewDidLoad];

    //Configure self's view
    self.view.backgroundColor = [UIColor blackColor];

    //Configure subViews
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    RAC(imageView, image) = RACObserve(self.viewModel,photoImage);
    imageView.contentModel = UIViewContentModelScaleAspectFit;
    [self.view addSubView:imageView];
    self.imageView = imageView;

    [RACObserve(self.viewModel, loading) subscribeNext:^(NSNumber *loading) {
        if(loading.boolValue) {
            [SVProgressHUD show];
        }
        else {
            [SVProgressHUD dismiss];
        }
    }];
}

  該圖片視圖的圖片屬性的綁定是標準的ReactiveCocoa方式,有趣的是下面(我們要提到的)我們使用loading的時刻。當加載信號發(fā)送YES的時候我們展示進度HUD,發(fā)送NO的時候,讓進度HUD消失。我們將看到該loading信號本身如何依賴于didBecomeActiveSignal?,F(xiàn)在只是視圖模型通過網(wǎng)絡(luò)請求獲取圖像數(shù)據(jù)的序幕。

  接口的申明如下:

@class FRPPhotoModel;

@interface FRPPhotoViewModel : RVMViewModel

@property (nonatomic, readonly) FRPPhotoModel *model;
@property (nonatomic, readonly) UIImage *photoImage;
@property (nonatomic, readonly, getter = isLoading) BOOL loading;

- (NSString *)photoName;

@end

  該modelphotoImage屬性的用法已經(jīng)解釋過了。photoName事實上作為屬性在代碼庫的其他地方被用來設(shè)置一些東西,類似于分頁視圖控制器的標題這樣。你可以下載Github的代碼庫了解詳情。我們來看一下實現(xiàn):

#import "FRPPhotoViewModel.h"

//Utilities
#import "FRPPhotoImporter.h"
#import "FRPPhotoModel.h"

@interface FRPPhotoViewModel ()

@property (nonatomic, strong) UIImage *photoImage;
@property (nonatomic, assign, getter = isLoading) BOOL loading;

@end

@implementation FRPPhotoViewModel

- (instancetype)initWithModel:(FRPPhotoModel *)photoModel {
    self = [super initWithModel:photoModel];
    if(!self) return nil;

    @weakify(self);
    [self.didBeComeActiveSignal subscribeNext:^(id x) {
        @strongify(self);
        self.loading = YES;
        [[FRPPhotoImporter fetchPhotoDetails:self.model] subscribeError:^(NSError *error) {
            NSLog(@"Could not fetch photo details: %@",error);
        } completed:^{
            self.loading = NO;
            NSLog(@"Fetched photoDetails.");
        }];
    }];

    RAC(self, photoImage) = [RACObserve(self.model, fullsizedData) map:^id (id value) {
        return [UIImage imageWithData:value];
    }];

    return self;
}

- (NSString *)photoName {
    return self.model.photoName;
}

@end

  該didBecomeActive信號訂閱帶有"函數(shù)副作用"的加載照片詳情包括它的高清圖片的數(shù)據(jù)。然后photoImage屬性與模型的映射結(jié)果綁定。

  使用didBecomeActiveSignal這種方法來啟動一些像網(wǎng)絡(luò)操作這樣昂貴的任務(wù),遠遠優(yōu)于我們早前在初始化方法中啟動他們的方法。

  本書要介紹的全部內(nèi)容已經(jīng)講完了,更多的內(nèi)容請參考functional reactive pixels,這個代碼庫包含了更多的在圖片詳情視圖控制器和登陸視圖控制器中使用視圖模型的例子。這些Demo將向你展示如何有效地使用ReactiveCocoa執(zhí)行網(wǎng)絡(luò)操作和使用RACCommands響應(yīng)用戶界面交互。


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號