iOS架構模式MVC、MVP、MVVM(內附demo)
MVC
MVC的實現思路是:使用者操作View,在Controller層完成業務邏輯處理,更新Model層,將資料顯示在View層。
在MVC中,每個層之間都有關聯,耦合比較緊,在大型專案中,維護起來比較費力。
View把控制權交給Controller層,自己不執行業務邏輯;Controller層執行業務邏輯並且操作Model層,但不會直接操作View層;View和Model層的同步訊息是通過觀察者模式進行,而同步操作是由View層自己請求Model層的資料,然後對檢視進行更新,觀察者模式可以做到多檢視同時更新。
MVC結構如圖所示:
模型層:
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
Person.m
#import "Person.h"
@implementation Person
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
self = [super init];
if (self) {
_firstName = firstName;
_lastName = lastName;
}
return self;
}
@end
檢視層:
TestView.h
#import <UIKit/UIKit.h>
@interface TestView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@end
TestView.m
#import "TestView.h"
@implementation TestView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.nameLabel = [[UILabel alloc] initWithFrame:self.bounds];
self.nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.nameLabel];
}
return self;
}
@end
控制器層:
ViewController.m
#import "ViewController.h"
#import "Person.h"
#import "TestView.h"
@interface ViewController ()
@property (nonatomic, strong) Person *personModel;
@property (nonatomic, strong) TestView *testView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupViews];
if (self.personModel.firstName.length > 0) {
self.testView.nameLabel.text = self.personModel.firstName;
} else {
self.testView.nameLabel.text = self.personModel.lastName;
}
}
- (void)setupViews {
self.personModel = [[Person alloc] initWithFirstName:@"" lastName:@"胡歌"];
self.testView = [[TestView alloc] initWithFrame:CGRectMake(100, 100, CGRectGetWidth(self.view.bounds)-200, 50)];
[self.view addSubview:self.testView];
}
@end
MVP
MVP的實現思路是:使用者操作View,在Presenter層完成業務邏輯處理,更新Model層,通過Presenter將資料顯示在View層,完全隔斷Model和View之間的通訊。
我們通過介面的方式來連線view和presenter層,這樣導致的問題是,如果頁面過於複雜,我們的介面就會很多,為了更好的處理類似的問題,需要定義一些基類介面,把一些公共的邏輯,比如網路請求,toast,提示框等放在裡面。
因為MVP不依賴Model,所以可以更好的進行元件化,把它從特定的場景中脫離出來,做到高度複用。MVP中的Presenter更多的作為框架的控制者,承擔了大量的邏輯操作。
MVP結構如圖:
模型層:
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
Person.m
#import "Person.h"
@implementation Person
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
self = [super init];
if (self) {
_firstName = firstName;
_lastName = lastName;
}
return self;
}
@end
檢視層:
TestView.h
#import <UIKit/UIKit.h>
@interface TestView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@end
TestView.m
#import "TestView.h"
@implementation TestView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.nameLabel = [[UILabel alloc] initWithFrame:self.bounds];
self.nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.nameLabel];
}
return self;
}
@end
Presenter層:
PersonViewProtocol.h
#import <Foundation/Foundation.h>
@protocol PersonViewProtocol <NSObject>
- (void)setNameText:(NSString *)nameText;
@end
Presenter.h
#import <Foundation/Foundation.h>
#import "PersonViewProtocol.h"
@interface Presenter : NSObject
- (void)attachView:(id <PersonViewProtocol>)view;
- (void)fetchData;
@end
Presenter.m
#import "Presenter.h"
#import "Person.h"
@interface Presenter()
@property (nonatomic, strong) Person *person;
@property (nonatomic, weak) id<PersonViewProtocol> attachView;
@end
@implementation Presenter
- (void)attachView:(id<PersonViewProtocol>)view {
self.attachView = view;
}
- (void)fetchData {
self.person = [[Person alloc] initWithFirstName:@"趙麗穎" lastName:@"胡歌"];
if (self.person.firstName.length > 0) {
[self.attachView setNameText:self.person.firstName];
} else {
[self.attachView setNameText:self.person.lastName];
}
}
@end
ViewController.m
#import "ViewController.h"
#import "PersonViewProtocol.h"
#import "Presenter.h"
#import "TestView.h"
@interface ViewController ()<PersonViewProtocol>
@property (nonatomic, strong) TestView *testView;
@property (nonatomic, strong) Presenter *presenter;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupViews];
self.presenter = [Presenter new];
[self.presenter attachView:self];
[self.presenter fetchData];
}
- (void)setupViews {
self.testView = [[TestView alloc] initWithFrame:CGRectMake(100, 100, CGRectGetWidth(self.view.bounds)-200, 50)];
[self.view addSubview:self.testView];
}
#pragma PersonViewProtocol
- (void)setNameText:(NSString *)nameText {
self.testView.nameLabel.text = nameText;
}
@end
MVVM
MVVM和MVP的最大區別是採用了雙向繫結機制,View的變動,自動反映在ViewModel上。
MVVM結構如圖:
模型層:
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
Person.m
#import "Person.h"
@implementation Person
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
self = [super init];
if (self) {
_firstName = firstName;
_lastName = lastName;
}
return self;
}
@end
檢視層:
TestView.h
#import <UIKit/UIKit.h>
@interface TestView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@end
TestView.m
#import "TestView.h"
@implementation TestView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.nameLabel = [[UILabel alloc] initWithFrame:self.bounds];
self.nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.nameLabel];
}
return self;
}
@end
ViewModel層:
PersonViewModel.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface PersonViewModel : NSObject
@property (nonatomic, readonly) Person *person;
@property (nonatomic, readonly) NSString *nameText;
- (instancetype)initWithPerson:(Person *)person;
@end
PersonViewModel.m
#import "PersonViewModel.h"
@implementation PersonViewModel
- (instancetype)initWithPerson:(Person *)person {
self = [super init];
if (self) {
_person = person;
if (_person.firstName.length > 0) {
_nameText = _person.firstName;
} else {
_nameText = _person.lastName;
}
}
return self;
}
@end
ViewController.m
#import "ViewController.h"
#import "PersonViewModel.h"
#import "TestView.h"
@interface ViewController ()
@property (nonatomic, strong) TestView *testView;
@property (nonatomic, strong) PersonViewModel *viewModel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupViews];
self.testView.nameLabel.text = self.viewModel.nameText;
}
- (void)setupViews {
Person *person = [[Person alloc] initWithFirstName:@"" lastName:@"胡歌"];
self.viewModel = [[PersonViewModel alloc] initWithPerson:person];
self.testView = [[TestView alloc] initWithFrame:CGRectMake(100, 100, CGRectGetWidth(self.view.bounds)-200, 50)];
[self.view addSubview:self.testView];
}
@end