iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)

fgyong發表於2019-08-07

上篇文章講了記憶體管理,這篇文章主要講解關於架構的一些思考,通過這篇文章你將瞭解到

  1. MVC
  2. MVC變種
  3. MVP
  4. MVVM
  5. 分層設計的優缺點

沒有最好的架構,只有最適合業務的架構。

MVC

蘋果版本的MVCModelVC和互動,VCView互動

  • 優點:ViewModel可以重複利用,可以獨立使用

  • 缺點:Controller的程式碼過於臃腫

iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)

程式碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadData];
}
- (void)loadData{
    self.data=[NSMutableArray array];
    for (int i = 0; i < 20; i ++) {
        FYNews *item=[FYNews new];
        item.title =[NSString stringWithFormat:@"title-%d",i];
        item.name =[NSString stringWithFormat:@"name-%d",i];
        [self.data addObject:item];
    }
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

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


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    
    // Configure the cell...
    FYNews *item =[self.data objectAtIndex:indexPath.row];
    cell.detailTextLabel.text =item.title;
    cell.textLabel.text = item.name;
    return cell;
}

//model

@interface FYNews : NSObject
@property (nonatomic,copy) NSString *title;
@property (nonatomic,copy) NSString *name;
@end
複製程式碼

這裡是VC中組裝了tableviewmodel的資料在VC中在view中顯示出來,當需要另外的資料的時候,只需要將model改成需要的model而無需更改tableview的程式碼相容性較好。

MVC變種

MVC變種,其實就是將modelview建立了聯絡,view依據Model來展示資料,VC組裝Model,組裝展示是在view中實現。

  • 優點:對Controller進行瘦身,將View的內部細節封裝起來了,外界不知道View內部的具體實現

  • 缺點:view依賴於Model

iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)

程式碼實現

//.h
@class FYItemModel;
@interface FYAppleView : UIView
@property (nonatomic,strong) FYItemModel *model;
@end

//.m
@interface FYAppleView()
@property (nonatomic,strong) UILabel *nameLabel;
@end

@implementation FYAppleView
-(instancetype)initWithFrame:(CGRect)frame{
    if (self =[super initWithFrame:frame]) {
        _nameLabel=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 30)];
        [self addSubview:_nameLabel];
    }
    return self;
}
/*
  mvc的變種
 */
- (void)setModel:(FYItemModel *)model{
    _model = model;
    _nameLabel.textColor = model.bgColor;
    _nameLabel.text = model.name;
}
@end

//FYItemModel
@interface FYItemModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) UIColor *bgColor;
@end


//ViewController
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadViewOtherMVC];
}
//變種MVC 把View和Model建立起連線
//等以後更新view資料只需要 view.model = item;Controllr少了許多程式碼
- (void)loadViewOtherMVC{
    FYAppleView * view =[[FYAppleView alloc]initWithFrame:CGRectMake(200, 200, 100, 30)];
    FYItemModel *item=[[FYItemModel alloc]init];
    item.name = @"校長來了";
    item.bgColor = [UIColor redColor];
    view.model = item;
    [self.view addSubview:view];
}
@end
複製程式碼

可以看到model組裝到view展示內容是在view實現的,外部不知道細節,只需要將modelview即可,但是隻能傳輸過來model或者他子類,業務更改的話,需要修改view的內部model才能將變更過的資料重新展示出來。

想要監聽view的點選事件來做一些操作,那麼我們可以使用代理和block,這裡id是實現了FYAppleViewProtocol協議的,weak修飾防止迴圈引用,使用協議實現了和VC的通訊。

@class FYAppleView;
@protocol FYAppleViewProtocol <NSObject>
- (void)FYAppleViewDidClick:(FYAppleView*)view;
@end

@class FYItemModel;
@interface FYAppleView : UIView
@property (nonatomic,strong,readonly) UILabel *nameLabel;
@property (nonatomic,weak) id<FYAppleViewProtocol> delegate;
@property (nonatomic,strong) FYItemModel *model;
@end
複製程式碼

稍作更改還是apple-MVC

// .h
@class FYItemModel;
@interface FYAppleView : UIView
@property (nonatomic,strong,readonly) UILabel *nameLabel;
@end
複製程式碼

View屬性nameLabel暴露出來,但是不允許外界進行更改,去掉model則是MVC

MVP

MVPMVC很像,只是將VC換成了PresentervcPresent做的事情基本一致,將viewModel通訊改到了都和Presenter通訊。

iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)
程式碼

//MVP
//.h
@interface FYNewsPresenter : NSObject

@property (nonatomic,weak) UIViewController *vc;
//初始化
- (void)setup;
@end

.m
#import "FYNewsPresenter.h"
@interface FYNewsPresenter()<FYAppleViewProtocol>
@end

@implementation FYNewsPresenter
- (void)setup{
	FYAppleView * view =[[FYAppleView alloc]initWithFrame:CGRectMake(200, 200, 100, 30)];
	FYItemModel *item=[[FYItemModel alloc]init];
	item.name = @"校長來了";
	item.bgColor = [UIColor redColor];
	view.model = item;
	[self.vc.view addSubview:view];
}
- (void)FYAppleViewDidClick:(FYAppleView *)view{
	NSLog(@"點選了我");
}
@end


//VC中
@interface ViewController ()
@property (nonatomic,strong) FYNewsPresenter *presenter;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
	_presenter=[FYNewsPresenter new];
	_presenter.vc = self;
	[_presenter setup];
}
@end
複製程式碼

再次對VC進行了瘦身,將更多的業務邏輯搬到了FYNewsPresenter處理,其實全部搬過去,意義比不大,FYNewsPresenter也會臃腫,也會出現和VC一樣的困惑。

MVVM

MVVM是將FYNewsPresenter都搬到了FYNewsViewModel中,然後對FYNewsViewModelView進行了一個雙向繫結,雙向繫結可以使用代理,block或者KVO實現。

iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)
程式碼實現

@interface FYNewsViewModel : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) UIColor *bgColor;

@property (nonatomic,weak) UIViewController *vc;

- (instancetype)initWithController:(UIViewController *)vc;
@end



#import "FYNewsViewModel.h"
@interface FYNewsViewModel()<FYAppleViewProtocol>


@end
@implementation FYNewsViewModel
- (instancetype)initWithController:(UIViewController *)vc{
    if (self =[super init]) {
        self.vc = vc;
        
        FYAppleView * view =[[FYAppleView alloc]initWithFrame:CGRectMake(100, 200, 100, 50)];
        //    view.model = item;
        view.delegate = self;
        view.viewModel = self; //建立kvo
        
        view.backgroundColor = [UIColor lightGrayColor];
        [vc.view addSubview:view];
        
        
        
        FYItemModel *item=[[FYItemModel alloc]init];
        item.name = @"校長來了";
        item.bgColor = [UIColor redColor];
        
        self.name = item.name;
        self.bgColor = item.bgColor;
    }
    return self;
}
- (void)FYAppleViewDidClick:(FYAppleView *)view{
	NSLog(@"點選了我");
}
@end
複製程式碼

view實現

@class FYAppleView,FYNewsViewModel;
@protocol FYAppleViewProtocol <NSObject>

- (void)FYAppleViewDidClick:(FYAppleView*)view;

@end

@class FYItemModel;

@interface FYAppleView : UIView
@property (nonatomic,strong,readonly) UILabel *nameLabel;

@property (nonatomic,weak) id<FYAppleViewProtocol> delegate;
@property (nonatomic,weak) FYNewsViewModel *viewModel;

@property (nonatomic,strong) FYItemModel *model;
@end


@interface FYAppleView()
@property (nonatomic,strong) UILabel *nameLabel;
@end
@implementation FYAppleView
-(instancetype)initWithFrame:(CGRect)frame{
    if (self =[super initWithFrame:frame]) {
        _nameLabel=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 30)];
        [self addSubview:_nameLabel];
    }
    return self;
}
/*
  mvc的變種
 */
- (void)setModel:(FYItemModel *)model{
    _model = model;
    _nameLabel.textColor = model.bgColor;
    _nameLabel.text = model.name;
	
 
}

- (void)setViewModel:(FYNewsViewModel *)viewModel{
    _viewModel = viewModel;
   [_viewModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
   //使用FBKVO實現 或者自己使用KVO實現
//    __weak typeof(self) waekSelf = self;
//    [self.KVOController observe:viewModel keyPath:@"name"
//                        options:NSKeyValueObservingOptionNew
//                          block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
//        waekSelf.nameLabel.text = change[NSKeyValueChangeNewKey];
//    }];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"name"]) {
        self.nameLabel.text = change[NSKeyValueChangeNewKey];
    }
}

//新增點選事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	if ([self.delegate respondsToSelector:@selector(FYAppleViewDidClick:)]) {
		[self.delegate FYAppleViewDidClick:self];
	}
}

-(void)dealloc{
    [_viewModel removeObserver:self
                    forKeyPath:@"name"];
}
@end
複製程式碼

使用KVO或者FBKVO或者RAC都是可以的,本章節例子給出了FBKVO或者自己使用KVO的實現。

分層設計

三層架構:

三層架構(3-tier architecture) 通常意義上的三層架構就是將整個業務應用劃分為:介面層(User Interface layer)、業務邏輯層(Business Logic Layer)、資料訪問層(Data access layer)。區分層次的目的即為了“高內聚低耦合”的思想。在軟體體系架構設計中,分層式結構是最常見,也是最重要的一種結構。微軟推薦的分層式結構一般分為三層,從下至上分別為:資料訪問層、業務邏輯層(又或稱為領域層)、表示層

  • 目的: “高內聚,低耦合”的思想

  • 優點: 降低層與層之間的依賴 標準化

  • 缺點: 系統架構複雜,不適合小型專案

三層原理

3個層次中,系統主要功能和業務邏輯都在業務邏輯層進行處理。 所謂三層體系結構,是在客戶端與資料庫之間加入了一個中間層,也叫元件層。這裡所說的三層體系,不是指物理上的三層,不是簡單地放置三臺機器就是三層體系結構,也不僅僅有B/S應用才是三層體系結構,三層是指邏輯上的三層,即把這三個層放置到一臺機器上。

三層體系的應用程式將業務規則、資料訪問、合法性校驗等工作放到了中間層進行處理。通常情況下,客戶端不直接與資料庫進行互動,而是通過COM/DCOM通訊與中間層建立連線,再經由中間層與資料庫進行互動。

三層架構中主要功能與業務邏輯一般要在業務邏輯層進行資訊處理和實現,其中三層體系架構中的客戶端和資料庫要預設中間層,成為組建層。三層架構中的三層具有一定的邏輯性,即是將三層設定到同一個計算機系統中,把業務協議、合法校驗以及資料訪問等程式歸置到中間層進行資訊處理,一般客戶端無法和資料庫進行資料傳輸,主要是利用COM/DCOM通訊和中間層構建銜接通道,實現中間層與資料庫的資料傳輸,進而實現客戶端與是資料庫的互動

MVCMVVMMVP屬於介面層, 當業務複雜,網路請求和db操作達到了一個新的高度,介面複雜到需要好多人來做,那麼介面、業務、資料需要分層了

分層之後,得到了一個三層架構或四層架構

三層架構

資料層也可以分為兩層,分為網路請求和db層。

四層架構

具體在工程中我們通常這樣體現

iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)

vc中獲取資料

@interface ViewController ()
@property (nonatomic,strong) FYDBPool *db;
@property (nonatomic,strong) FYHttpPool *http;
@end

@implementation ViewController
- (void)viewDidLoad {
	[super viewDidLoad];

	//當有業務層
	[[FYNewsService new] loadNewsWithInfo:nil success:^(NSArray * _Nonnull) {
		
	} fail:^{
		
	}];
	//當沒有有業務層
	self.db=[FYDBPool new];
	self.http=[FYHttpPool new];
	[self.db loadNewsWithInfo:@{} success:^(NSArray * _Nonnull ret) {
		if ([ret count]) {
			NSLog(@"資料獲取成功");
		}else{
			[self.http loadNewsWithInfo:@{} success:^(NSArray * _Nonnull ret) {
				NSLog(@"資料獲取成功");
			} fail:^{
				NSLog(@"資料獲取失敗");
			}];
		}
	} fail:^{
		[self.http loadNewsWithInfo:@{} success:^(NSArray * _Nonnull ret) {
			NSLog(@"資料獲取成功");
		} fail:^{
			NSLog(@"資料獲取失敗");
		}];
	}];
}

複製程式碼

在業務層

@interface FYNewsService ()
@property (nonatomic,strong) FYDBPool *db;
@property (nonatomic,strong) FYHttpPool *http;

@end
@implementation FYNewsService
-(instancetype)init{
	if (self = [super init]) {
		self.db=[FYDBPool new];
		self.http=[FYHttpPool new];
	}
	return self;
}
- (void)loadNewsWithInfo:(NSDictionary *)info
				 success:(succcessCallback )succblock
					fail:(dispatch_block_t)failBlock{
	[self.db loadNewsWithInfo:info success:^(NSArray * _Nonnull ret) {
		if ([ret count]) {
			succblock(ret);
		}else{
			[self.http loadNewsWithInfo:info success:^(NSArray * _Nonnull ret) {
				succblock(ret);
			} fail:failBlock];
		}
	} fail:^{
		[self.http loadNewsWithInfo:info success:^(NSArray * _Nonnull ret) {
			succblock(ret);
		} fail:failBlock];
	}];
}
@end
複製程式碼

在db層

typedef void(^succcessCallback)(NSArray *);
@interface FYDBPool : NSObject
- (void)loadNewsWithInfo:(NSDictionary *)info
				 success:(succcessCallback )succblock
					fail:(dispatch_block_t)failBlock;
@end
複製程式碼

在網路請求層

typedef void(^succcessCallback)(NSArray *);
@interface FYHttpPool : NSObject
- (void)loadNewsWithInfo:(NSDictionary *)info
				 success:(succcessCallback )succblock
					fail:(dispatch_block_t)failBlock;
@end
複製程式碼

分層目的是瘦身,邏輯清晰,業務清晰,降低耦合,當某一塊足夠複雜時候,都可以進行分層,不侷限於網路或db,當db足夠複雜,也需要進行一個分層來解決複雜呼叫和處理的問題。 不同的人來處理不同的分層,相互影響也比較小,降低耦合。

當邏輯層足夠完善,則UI層如何變動都不需要更改邏輯層。

後記

優雅的程式碼總是伴隨著各種傳統設計模式的搭配

設計模式

設計模式(Design Pattern) 是一套被反覆使用、程式碼設計經驗的總結 使用設計模式的好處是:可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性 一般與程式語言無關,是一套比較成熟的程式設計思想

設計模式可以分為三大類

  1. 建立型模式:物件例項化的模式,用於解耦物件的例項化過程 單例模式、工廠方法模式,等等

  2. 結構型模式:把類或物件結合在一起形成一個更大的結構 代理模式、介面卡模式、組合模式、裝飾模式,等等

  3. 行為型模式:類或物件之間如何互動,及劃分責任和演算法 觀察者模式、命令模式、責任鏈模式,等等

總結

  • 適合專案的才是最好的架構

資料參考

資料下載


最怕一生碌碌無為,還安慰自己平凡可貴。

廣告時間

iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)

相關文章