面试题引发的思考:
Q: 用过哪些设计模式?
- iOS中主要使用单例模式、代理模式、观察者模式(通知、KVO)。
Q: 描述对MVC、MVP、MVVM模式的理解?
- 分析如下:
1. 架构
架构(Architecture)
- 软件开发中的设计方案;
- 用来处理类与类之间、模块与模块之间、客户端与服务端之间的关系。
架构相关名词:
- MVC、MVP、MVVM
- 三层架构、四层架构
- ......
(1) MVC(Model-View-Controller)
MVC模式如下图:
-
Controller
创建并持有View
,并且把View
添加到窗口上显示; -
View
通知Controller
处理事件;
(比如View
内部的点击事件、滚动事件,通知Controller
去处理这些业务逻辑。) -
Controller
发送网络请求或解析数据库加载数据,并且Controller
拥有和管理Model
; -
Model
发生改变,Controller
会将最新的Model
显示到View
上面去。
由以上分析可知:
Controller
是Model
和View
的桥梁,Model
和View
相互独立。
MVC模式最经典的案例是UITableView
,案例如下:
// TODO: ----------------- MYShopModel类 -----------------
@interface MYShopModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *price;
@end
@implementation MYShopModel
@end
// TODO: ----------------- MYShopViewController类 -----------------
@interface MYShopViewController : UITableViewController
@end
@interface MYShopViewController ()
@property (nonatomic, strong) NSMutableArray *shopArray;
@end
@implementation MYShopViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 加载数据
[self loadshopArray];
}
- (void)loadshopArray {
self.shopArray = [[NSMutableArray alloc] init];
for (int i = 0; i < 20; i++) {
MYShopModel *shopModel = [[MYShopModel alloc] init];
shopModel.name = [NSString stringWithFormat:@"商品-%d", i];
shopModel.price = [NSString stringWithFormat:@"¥19.%d", i];
[self.shopArray addObject:shopModel];
}
}
#pragma mark -
#pragma mark - tableView代理方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.shopArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewsCell" forIndexPath:indexPath];
MYShopModel *shopModel = self.shopArray[indexPath.row];
cell.detailTextLabel.text = shopModel.price;
cell.textLabel.text = shopModel.name;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath");
}
@end
-
MYShopViewController
继承于UITableViewController
,UITableViewController
拥有并自动创建了UITableView
添加到内部,所以①符合; -
UITableView
点击事件是MYShopViewController
内部的didSelectRowAtIndexPath
方法处理的,所以②符合; -
MYShopViewController
加载MYShopModel
,并且保存到MYShopViewController
里面,所以③符合; - 当数据发生改变时,
MYShopViewController
会刷新cellForRowAtIndexPath
更新UITableView
的信息展示,所以④符合。
由以上分析可知:
MVC的优缺点:
- 优点:
View
、Model
可以重复利用,可以独立使用;- 缺点:
Controller
的代码过于臃肿。
(2) MVC(Model-View-Controller)变种
MVC变种是为了解决MVC中Controller
的代码过于臃肿的问题。
MVC变种模式如下图:
// TODO: ----------------- MYAppModel类 -----------------
@interface MYAppModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *image;
@end
@implementation MYAppModel
@end
// TODO: ----------------- MYAppView类 -----------------
@class MYAppView;
@protocol MYAppViewDelegate
@optional
- (void)appViewClicked:(MYAppView *)appView;
@end
@interface MYAppView : UIView
// TODO: 变种之后:View里面有个Model
@property (nonatomic, strong) MYAppModel *appModel;
@property (nonatomic, weak) id delegate;
@end
@interface MYAppView ()
// TODO: 变种之后:不暴露属性
@property (nonatomic, weak) UIImageView *iconView;
@property (nonatomic, weak) UILabel *nameLabel;
@end
@implementation MYAppView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIImageView *iconView = [[UIImageView alloc] init];
iconView.frame = CGRectMake(0, 0, 100, 100);
[self addSubview:iconView];
_iconView = iconView;
UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.frame = CGRectMake(0, 100, 100, 30);
nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:nameLabel];
_nameLabel = nameLabel;
}
return self;
}
// TODO: 变种之后:封装内部赋值逻辑
- (void)setAppModel:(MYAppModel *)appModel {
_appModel = appModel;
self.iconView.image = [UIImage imageNamed:appModel.image];
self.nameLabel.text = appModel.name;
}
// TODO: 使用代理将点击事件传递给控制器
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self.delegate respondsToSelector:@selector(appViewClicked:)]) {
[self.delegate appViewClicked:self];
}
}
@end
// TODO: ----------------- ViewController类 -----------------
@end@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建view
MYAppView *appView = [[MYAppView alloc] init];
appView.frame = CGRectMake(100, 100, 100, 150);
appView.delegate = self;
[self.view addSubview:appView];
// 加载模型数据
MYAppModel *appModel = [[MYAppModel alloc] init];
appModel.name = @"QQ";
appModel.image = @"QQ";
// TODO: 变种之后:设置数据到view上
appView.appModel = appModel;
}
#pragma mark -
#pragma mark - MYAppViewDelegate
- (void)appViewClicked:(MYAppView *)appView {
NSLog(@"控制器监听到了appView的点击");
}
@end
MVC变种:
- 一个
View
对应一个Model
;View
的控件不再暴露,给View
赋值的逻辑被封装在View
内部;- 其他类使用
View
只需给View
传入一个Model
即可。
由以上分析可知:
MVC变种的优缺点:
- 优点:对
Controller
进行瘦身,封装View
内部的细节,外界不知道View
内部的具体实现;- 缺点:
View
依赖于Model
,不能独立使用。
(3) MVP(Model-View-Presenter)
MVP模式如下图:
MVP:
- 相当于用
Presenter
代替了MVC的Controller
;Model
和View
相互独立。
// TODO: ----------------- MYAppModel类 -----------------
@interface MYAppModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *image;
@end
@implementation MYAppModel
@end
// TODO: ----------------- MYAppView类 -----------------
@class MYAppView;
@protocol MYAppViewDelegate
@optional
// 本来交给Controller来做的事情,现在交给Presenter来做了
- (void)appViewClicked:(MYAppView *)appView;
@end
@interface MYAppView : UIView
// TODO: View不拥有Model,又不想暴露控件,所以使用方法更新数据
- (void)setName:(NSString *)name image:(NSString *)image;
@property (nonatomic, weak) id delegate;
@end
@interface MYAppView ()
@property (nonatomic, weak) UIImageView *iconView;
@property (nonatomic, weak) UILabel *nameLabel;
@end
@implementation MYAppView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIImageView *iconView = [[UIImageView alloc] init];
iconView.frame = CGRectMake(0, 0, 100, 100);
[self addSubview:iconView];
_iconView = iconView;
UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.frame = CGRectMake(0, 100, 100, 30);
nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:nameLabel];
_nameLabel = nameLabel;
}
return self;
}
// TODO: 更新数据
- (void)setName:(NSString *)name image:(NSString *)image {
self.iconView.image = [UIImage imageNamed:image];
self.nameLabel.text = name;
}
// TODO: 使用代理将点击事件传递给控制器
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self.delegate respondsToSelector:@selector(appViewClicked:)]) {
[self.delegate appViewClicked:self];
}
}
@end
// TODO: ----------------- MYAppPresenter类 -----------------
@interface MYAppPresenter : NSObject
- (instancetype)initWithController:(UIViewController *)controller;
@end
@interface MYAppPresenter()
// 拥有weak类型的控制器
@property (nonatomic, weak) UIViewController *controller;
@end
@implementation MYAppPresenter
- (instancetype)initWithController:(UIViewController *)controller {
self = [super init];
if (self) {
self.controller = controller;
// 创建View
MYAppView *appView = [[MYAppView alloc] init];
appView.frame = CGRectMake(100, 100, 100, 150);
appView.delegate = self;
[controller.view addSubview:appView];
// 加载模型数据
MYAppModel *appModel = [[MYAppModel alloc] init];
appModel.name = @"QQ";
appModel.image = @"QQ";
// 赋值数据
[appView setName:appModel.name image:appModel.image];
}
return self;
}
#pragma mark -
#pragma mark - MYAppViewDelegate
- (void)appViewClicked:(MYAppView *)appView {
NSLog(@"presenter监听到了appView的点击");
}
@end
// TODO: ----------------- ViewController类 -----------------
@interface ViewController ()
// TODO: 这里只有一个MYAppView,如果有新的View,就用新的presenter来处理它的业务逻辑。
@property (nonatomic, strong) MYAppPresenter *presenter;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建presenter
self.presenter = [[MYAppPresenter alloc] initWithController:self];
}
@end
MVP:
- 本来交给
Controller
来做的事情,现在交给Presenter
来做了;- 控制器和
presenter
互相拥有,但presenter
拥有weak
类型的控制器,防止循环引用;View
不拥有Model
,又不想暴露控件,所以使用方法更新数据;View
的点击事件也交给presenter
处理了。- 示例只有一个
MYAppView
,如果有新的View
,就用新的presenter
来处理它的业务逻辑;
由以上分析可知:
MVP的优缺点:
- 优点:
Presenter
代替了MVC中的Controller
;
View
、Model
可以重复利用,可以独立使用;- 缺点:
Presenter
和View
的耦合性太高;
每个View
对应一个Presenter
,导致类太多。
(4) MVVM(Model-View-ViewModel)
MVVM模式如下图:
// TODO: ----------------- MYAppModel类 -----------------
@interface MYAppModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *image;
@end
@implementation MYAppModel
@end
// TODO: ----------------- MYAppView类 -----------------
@class MYAppView, MYAppViewModel;
@protocol MYAppViewDelegate
@optional
- (void)appViewClicked:(MYAppView *)appView;
@end
@interface MYAppView : UIView
@property (nonatomic, weak) MYAppViewModel *viewModel;
@property (nonatomic, weak) id delegate;
@end
@interface MYAppView ()
@property (nonatomic, weak) UIImageView *iconView;
@property (nonatomic, weak) UILabel *nameLabel;
@end
@implementation MYAppView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIImageView *iconView = [[UIImageView alloc] init];
iconView.frame = CGRectMake(0, 0, 100, 100);
[self addSubview:iconView];
_iconView = iconView;
UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.frame = CGRectMake(0, 100, 100, 30);
nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:nameLabel];
_nameLabel = nameLabel;
}
return self;
}
// TODO: 更新数据
- (void)setViewModel:(MYAppViewModel *)viewModel {
_viewModel = viewModel;
// 给ViewModel的name属性添加监听,监听到改变就更新View
__weak typeof(self) waekSelf = self;
[self.KVOController observe:viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary * _Nonnull change) {
waekSelf.nameLabel.text = change[NSKeyValueChangeNewKey];
}];
// 给ViewModel的image属性添加监听,监听到改变就更新View
[self.KVOController observe:viewModel keyPath:@"image" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary * _Nonnull change) {
waekSelf.iconView.image = [UIImage imageNamed:change[NSKeyValueChangeNewKey]];
}];
}
// TODO: 使用代理将点击事件传递给控制器
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self.delegate respondsToSelector:@selector(appViewClicked:)]) {
[self.delegate appViewClicked:self];
}
}
@end
// TODO: ----------------- MYAppViewModel类 -----------------
@interface MYAppViewModel : NSObject
// 本来交给Controller来做的事情,现在交给ViewModel来做了
- (instancetype)initWithController:(UIViewController *)controller;
@end
@interface MYAppViewModel()
@property (nonatomic, weak) UIViewController *controller;
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *image;
@end
@implementation MYAppViewModel
- (instancetype)initWithController:(UIViewController *)controller {
self = [super init];
if (self) {
self.controller = controller;
// 创建View
MYAppView *appView = [[MYAppView alloc] init];
appView.frame = CGRectMake(100, 100, 100, 150);
appView.delegate = self;
appView.viewModel = self;
[controller.view addSubview:appView];
// 加载模型数据
MYAppModel *appModel = [[MYAppModel alloc] init];
appModel.name = @"QQ";
appModel.image = @"QQ";
// 设置数据到自己属性上面去,模型的每个属性都保存在ViewModel的属性里面了,留着给View监听。
self.name = appModel.name;
self.image = appModel.image;
// 当自己的属性改变时会被View监听到,然后View更新数据
//self.name = @"我改变了";
}
return self;
}
#pragma mark -
#pragma mark - MYAppViewDelegate
- (void)appViewClicked:(MYAppView *)appView {
NSLog(@"viewModel监听到了appView的点击");
}
@end
// TODO: ----------------- ViewController类 -----------------
// 控制器只管理ViewModel
@property (strong, nonatomic) MYAppViewModel *viewModel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建viewModel
self.viewModel = [[MYAppViewModel alloc] initWithController:self];
}
@end
MVVM+RAC是最佳搭配,可以监听ViewModel
里面属性的改变,但是RAC比较重,谨慎使用。这里我们使用了FaceBook的FBKVOController框架来监听。
MVP和MVVM:
- 共同点:对
Controller
进行瘦身;
将View
和Model
的一些业务逻辑放在Presenter或ViewModel中;- 不同点:属性监听绑定;
View
拥有ViewModel
并监听ViewModel
内部属性的改变,当属性改变时会更新View
。
由以上分析可知:
MVVM的优缺点:
- 优点:对
Controller
进行瘦身,实现双向绑定;- 缺点:类会变多、bug不便调试。
(5) 分层设计
分层设计如下:
MVC、MVP、MVVM属于界面层,分层设计时不同层级分别处理所在层级的任务。
新闻业务举例,代码如下:
// TODO: ----------------- ViewController类 -----------------
// TODO: 界面层,控制器直接加载新闻数据
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 加载新闻数据
[MYNewsService loadNews:@{} success:^(NSArray * _Nonnull newsData) {
} failure:^(NSError * _Nonnull error) {
}];
}
@end
// TODO: ----------------- MYNewsService类 -----------------
// TODO: 业务层,负责新闻相关的业务,包括数据的加载等等
@interface MYNewsService : NSObject
+ (void)loadNews:(NSDictionary *)params success:(void (^)(NSArray *newsData))success failure:(void (^)(NSError *error))failure;
@end
@implementation MYNewsService
+ (void)loadNews:(NSDictionary *)params success:(void (^)(NSArray *newsData))success failure:(void (^)(NSError *error))failure {
// 先取出本地数据
[MYDBTool loadLocalData];
// 如果没有本地数据,就加载网络数据
[MYHTTPTool GET:@"xxxx" params:nil success:^(id result) {
} failure:failure];
}
@end
// TODO: ----------------- MYHTTPTool类 -----------------
// TODO: 网络层,负责网络数据的请求
@interface MYHTTPTool : NSObject
+ (void)GET:(NSString *)URL params:(NSDictionary *)params success:(void (^)(id result))success failure:(void (^)(NSError *error))failure;
@end
@implementation MYHTTPTool
+ (void)GET:(NSString *)URL params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure {
// 调用AFN
}
@end
// TODO: ----------------- MYDBTool类 -----------------
// TODO: 本地数据层,负责加载本地数据
@interface MYDBTool : NSObject
+ (void)loadLocalData;
@end
@implementation MYDBTool
+ (void)loadLocalData {
}
@end
以上架构模式Demo代码点击自取。
2. 架构与设计模式的区别
架构一般比设计模式大,架构层面的问题包括:
- 整个应用程序分为多少层架构;
- 将类分成很多角色(M、V、C、P、VM等等);
- ......
设计模式(Design Pattern):
- 是一套被反复使用、代码设计经验的总结;
- 可重用代码、更方便他人理解、保证代码可靠性;
- 一般与编程语言无关,是一套比较成熟的编程思想。
设计模式可以分为三大类:
创建型模式:对象实例化的模式,用于解耦对象的实例化过程;
单例模式、工厂方法模式,等等。结构型模式:把类或对象结合在一起形成一个更大的结构;
代理模式、适配器模式、组合模式、装饰模式,等等。行为型模式:类或对象之间如何交互,及划分责任和算法;
观察者模式、命令模式、责任链模式,等等。iOS中主要使用单例模式、代理模式、观察者模式。