MVVM 和 路由層

weixin_34321977發表於2018-08-28

MVVM

MVVM是一種減輕MVC中控制器Controller負擔的一種模式,其中,M是指Model,V是指ViewController/View,VM則是指ViewModel。
其中MVC模式是下面這種的:


2230188-2ce6a64e26898fcf.png
MVC

MVVM模式是下面這種的:


2230188-e3476705765914b6.png
MVVM

2230188-b7b8c7dddef2e82d.png
MVP

Model 有DataModel(純資料), LogicModel(資料邏輯處理)兩種,純資料Model就是指MVVM中的“M”,資料的邏輯處理Model就是指viewModel。這裡是每一個view都可以對應一個viewModel,從而把原來在控制器controller中處理的資料邏輯都放到了viewModel中。
控制器controller的view也可以對應一個viewModel。

應用如下:

controller控制器中程式碼:

/*
 V - VM
 MVCC (多控制器)addChildViewController
 
 對業務的拆分(細分c)
 */
@interface RecommendVCtr ()

@property (nonatomic, strong)RecommendViewModel *recomendViewModel;
@property (nonatomic, strong)ReTableViewModel *reTableViewModel;

@end

@implementation RecommendVCtr{
    
    ReMenuMoreVCtr *_reMenuMoreVCtr;
}


- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //self addChildViewController:<#(nonnull UIViewController *)#>
    self.automaticallyAdjustsScrollViewInsets = NO;
    [_headView removeFromSuperview];
    [_tableView setTableHeaderView:_headView];
    
   // [self.reTableViewModel configTable:_tableView];
    
    
    
    UITableView *tmpTableV = _tableView;
    // 下載資料業務
    [self.recomendViewModel loadDatafromNetWithPage:1 finishNet:^(id infoDict) {
        [tmpTableV reloadData];
    }];
}

- (void)viewDidLayoutSubviews{
    
    [super viewDidLayoutSubviews];
    [self.view bringSubviewToFront:_tableView];
    _tableView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - SysNavHeigh - SysTabBarHeigh);

}

- (IBAction)removeSelectV:(UIButton*)sender{
    
    [self.recomendViewModel deleteAdView:_isDeleteAdView headView:_headView tableview:_tableView];
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    // return [RecommendCell cellHeight];
    return 80.0;
    
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    
    return self.recomendViewModel.rowNumber;
    
}

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    RecommendCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RecommendCell"];
    if (!cell) {
        cell = [[[NSBundle mainBundle] loadNibNamed:@"RecommendCell" owner:nil options:nil] firstObject];
    }
    
     cell.messageModel = [self messageModelForRow:indexPath.row];
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
     [self.recomendViewModel pushMessageDetailIndex:indexPath viewCtr:self];
}


- (MessageModel*)messageModelForRow:(NSInteger)row{
    
    //    if (row < self.messageAry.count) {
    //        return self.messageAry[row];
    //    }else{
    //        NSLog(@"row越界messageAry:%ld--%ld", row, _messageAry.count);
    //    }
    return nil;
}


#pragma mark - lazy loading

- (RecommendViewModel*)recomendViewModel{
    
    if (!_recomendViewModel) {
        _recomendViewModel = [RecommendViewModel new];
    }
    return _recomendViewModel;
}


- (ReTableViewModel*)reTableViewModel{
    
    if (!_reTableViewModel) {
        _reTableViewModel = [ReTableViewModel new];
    }
    return _reTableViewModel;
}

Model:

#import <Foundation/Foundation.h>

@interface MessageModel : NSObject

@property (nonatomic, strong)NSString *messageId;
@property (nonatomic, strong)NSString *messageTitle;//標題
@property (nonatomic, strong)NSString *statusStr; // 是否頭條/重播/
@property (nonatomic, strong)NSString *commentCount;//評論數
@property (nonatomic, strong)NSString *fromCompany;//來自哪個 如66車訊/車視集
@property (nonatomic, assign)int layoutStyle; // 左右佈局/上下佈局


@property (nonatomic, assign)float messageHeight;

@end
#import "MessageModel.h"
@implementation MessageModel
- (float)messageHeight{
    return 80.0;
}
@end

viewModel:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class MessageModel;

typedef void(^finishLoadBlock)(id infoDict);

@interface RecommendViewModel : NSObject


@property(nonatomic, assign) NSInteger rowNumber;


- (CGFloat)messageHeightForRow:(NSInteger)row;

- (MessageModel*)messageModelForRow:(NSInteger)row;

- (NSString*)messageIdForRow:(NSInteger)row;


- (void)loadDatafromNetWithPage:(NSInteger)page finishNet:(finishLoadBlock)finishBlock;

- (void)deleteAdView:(UIView*)deleteView headView:(UIView*)headView tableview:(UITableView*)tableView;

- (void)pushMessageDetailIndex:(NSIndexPath *)indexPath viewCtr:(UIViewController*)targetVCtr;

@end
#import "RecommendViewModel.h"
#import "MessageModel.h"
#import "MessageDetailViewCtr.h"

@interface RecommendViewModel (){
    
    
}
@property (nonatomic, strong)NSMutableArray *messageAry;
@end

@implementation RecommendViewModel

/*
 
 */

- (NSInteger)rowNumber{
    
    return self.messageAry.count;
}

- (CGFloat)messageHeightForRow:(NSInteger)row{
    if (row < self.messageAry.count){
        MessageModel *messageModel = self.messageAry[row];
        return messageModel.messageHeight;
    }
    return 0;
}


- (MessageModel*)messageModelForRow:(NSInteger)row{
    
    if (row < self.messageAry.count) {
        return self.messageAry[row];
    }else{
        NSLog(@"row越界messageAry:%ld--%ld", row, _messageAry.count);
    }
    return nil;
}


- (NSString*)messageIdForRow:(NSInteger)row{
    
    if (row < self.messageAry.count) {
        MessageModel *messageModel = self.messageAry[row];
        return messageModel.messageId;
    }else{
        NSLog(@"row越界messageAry:%ld--%ld", row, _messageAry.count);
    }
    return nil;
}


- (void)deleteAdView:(UIView*)deleteView headView:(UIView*)headView tableview:(UITableView*)tableView{
    
    [deleteView removeFromSuperview];
    headView.frame = ({
        CGRect frame = headView.frame;
        frame.size.height = frame.size.height - deleteView.frame.size.height;
        frame;
    });
    [tableView setTableHeaderView:nil];
    [tableView setTableHeaderView:deleteView];
//    [tableView setTableHeaderView:headView];

}

- (void)pushMessageDetailIndex:(NSIndexPath *)indexPath viewCtr:(UIViewController*)targetVCtr{
    
    // 將MessageID傳過去,那邊好根據MessageID展示資訊詳情內容
    MessageDetailViewCtr *messageDetailVCtr = [[MessageDetailViewCtr alloc] initWithMessageID:[self messageIdForRow:indexPath.row]];
    
    [targetVCtr.navigationController pushViewController:messageDetailVCtr animated:YES];
}

- (void)loadDatafromNetWithPage:(NSInteger)page finishNet:(finishLoadBlock)finishBlock{
    for (int i = 0; i< 10; i++) {
        
        MessageModel *messageModel = [MessageModel new];
        messageModel.messageTitle = [NSString stringWithFormat:@"訊息::%d", i];
        [self.messageAry addObject:messageModel];
    }
    finishBlock(nil);
}
#pragma mark - lazy loading

- (NSMutableArray*)messageAry{
    if (!_messageAry) {
        _messageAry = [NSMutableArray array];
    }
    return _messageAry;
}

@end

tableView也能配一個viewModel:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface ReTableViewModel : NSObject<UITableViewDelegate, UITableViewDataSource>

@property(nonatomic, assign) NSInteger rowNumber;


- (void)configTable:(UITableView*)tableView;

@end
#import "ReTableViewModel.h"
#import "RecommendCell.h"

@implementation ReTableViewModel{
    UITableView *_tableview;
}


#pragma mark - tableview delegate

- (void)configTable:(UITableView*)tableView{
    
    _tableview = tableView;
    tableView.delegate = self;
    tableView.dataSource = self;
    
    
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    // return [RecommendCell cellHeight];
    return 80.0;
    
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    
    return self.rowNumber;
    
}

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    RecommendCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RecommendCell"];
    if (!cell) {
        cell = [[[NSBundle mainBundle] loadNibNamed:@"RecommendCell" owner:nil options:nil] firstObject];
    }
    
   // cell.messageModel = [self messageModelForRow:indexPath.row];
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
   // [self.recomendViewModel pushMessageDetailIndex:indexPath viewCtr:self];
}


- (MessageModel*)messageModelForRow:(NSInteger)row{
    
//    if (row < self.messageAry.count) {
//        return self.messageAry[row];
//    }else{
//        NSLog(@"row越界messageAry:%ld--%ld", row, _messageAry.count);
//    }
    return nil;
}

@end

不管什麼模式都是對業務邏輯的拆分,我們只要能夠實現對業務邏輯進行拆分就好了,沒必要強行套用某一個模式,那樣反而變得更加複雜。我們也還可以用MVCC模式,當一個控制器中,點選某個按鈕,有新的複雜的View產生的時候,我們可以將它獨立出來,看做當前控制器的一個子控制器。

路由層

路由層的功能就和路由器有點像,比如說當我們在一個導航控制器很深的一個頁面的時候,這時我們突然要回到某個首頁(經常產品有這樣的需求,跳轉到這,再跳轉到那),這時我們就需要路由層了,有了路由層,我們可以在跳轉的時候,不用匯入標頭檔案,而且路由層是完全獨立出來的,方便管理,也還能夠複用。

下面是未完善版本的路由層,等以後有時間了,完善一下再:

總的路由層:

#import <Foundation/Foundation.h>

@interface MenuR : NSObject

- (void)perferm:(id)target sel:(SEL)sel arg:(id)arg;
- (void)perferm:(id)target sel:(SEL)sel;

@end
#import "MenuR.h"

@implementation MenuR

- (void)perferm:(id)target sel:(SEL)sel arg:(id)arg{
    
    [target performSelector:sel withObject:arg];
}


- (void)perferm:(id)target sel:(SEL)sel{
    
}

@end

路由分支:

#import <Foundation/Foundation.h>

@interface MainRoute : NSObject


- (void)skipToMenu:(NSNumber*)index;

@end
#import "MainRoute.h"
#import <UIKit/UIKit.h>

@implementation MainRoute


- (void)skipToMenu:(NSNumber*)index{
    
    UINavigationController *target = (UINavigationController*)[UIApplication sharedApplication].keyWindow.rootViewController;
    target = [target.viewControllers firstObject];
    SEL sel = NSSelectorFromString(@"skipToMenuIndex:");
    
    if ([target respondsToSelector:sel]) {
        [target performSelector:sel withObject:index];
    }else{
        NSLog(@"異常 target 找不到 skipToMenuIndex:");
    }
}

@end

上面路由分支裡面skipToMenu方法中做的事情,就是判斷APP的根控制器,是否有skipToMenuIndex:這個方法,有的話就走一次那個方法,回到根控制器的某個介面。沒有的話,就丟擲一個異常,也方便找問題所在。
所以我們還需要在根控制器中實現這個方法:

#import "MainMenuTabBarVCtr.h"
#import "TabBarButtton.h"
#import "EOCBaseNavCtr.h"

@interface MainMenuTabBarVCtr ()

@end

@implementation MainMenuTabBarVCtr
- (void)skipToMenuIndex:(NSNumber*)indexN{
    
    NSInteger index = [indexN integerValue];
    [self.navigationController popToRootViewControllerAnimated:YES];
    //self.selectedIndex = index;
    [self selectMenuVC:[_eocTabBar viewWithTag:index]];
}

然後在需要跳轉的地方,如下:

使用總的路由的話:

    MenuR *mr = [MenuR new];
    [mr perferm:[NSClassFromString(@"MainRoute") new] sel:@selector(skipToMenu:) arg:[NSNumber numberWithInt:2]];

只是使用分支路由的話:

[MainRoute skipToMenu:2];

參考文章:
iOS MVVM架構

相關文章