iOS底层原理 - 设计模式与架构

面试题引发的思考:

Q: 用过哪些设计模式?

  • iOS中主要使用单例模式、代理模式、观察者模式(通知、KVO)。

Q: 描述对MVC、MVP、MVVM模式的理解?

  • 分析如下:
MVC模式
MVC模式变种
MVP模式
MVVM模式

1. 架构

架构(Architecture)

  • 软件开发中的设计方案;
  • 用来处理类与类之间、模块与模块之间、客户端与服务端之间的关系。

架构相关名词:

  • MVC、MVP、MVVM
  • 三层架构、四层架构
  • ......

(1) MVC(Model-View-Controller)

MVC模式如下图:

MVC模式
  1. Controller创建并持有View,并且把View添加到窗口上显示;
  2. View通知Controller处理事件;
    (比如View内部的点击事件、滚动事件,通知Controller去处理这些业务逻辑。)
  3. Controller发送网络请求或解析数据库加载数据,并且Controller拥有和管理 Model
  4. Model发生改变,Controller会将最新的Model显示到View上面去。

由以上分析可知:

ControllerModelView的桥梁,ModelView相互独立。

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
  1. MYShopViewController继承于UITableViewControllerUITableViewController拥有并自动创建了UITableView添加到内部,所以①符合;
  2. UITableView点击事件是MYShopViewController内部的didSelectRowAtIndexPath方法处理的,所以②符合;
  3. MYShopViewController加载MYShopModel,并且保存到MYShopViewController里面,所以③符合;
  4. 当数据发生改变时,MYShopViewController会刷新cellForRowAtIndexPath更新UITableView的信息展示,所以④符合。

由以上分析可知:

MVC的优缺点:

  • 优点:ViewModel可以重复利用,可以独立使用;
  • 缺点:Controller的代码过于臃肿。

(2) MVC(Model-View-Controller)变种

MVC变种是为了解决MVC中Controller的代码过于臃肿的问题。

MVC变种模式如下图:

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 <NSObject>
@optional
- (void)appViewClicked:(MYAppView *)appView;
@end

@interface MYAppView : UIView
// TODO: 变种之后:View里面有个Model
@property (nonatomic, strong) MYAppModel *appModel;
@property (nonatomic, weak) id<MYAppViewDelegate> 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<UITouch *> *)touches withEvent:(UIEvent *)event {
    if ([self.delegate respondsToSelector:@selector(appViewClicked:)]) {
        [self.delegate appViewClicked:self];
    }
}
@end

// TODO: -----------------  ViewController类  -----------------
@end@interface ViewController () <MYAppViewDelegate>
@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模式

MVP:

  • 相当于用Presenter代替了MVC的Controller
  • ModelView相互独立。
// 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 <NSObject>
@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<MYAppViewDelegate> 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<UITouch *> *)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() <MYAppViewDelegate>
// 拥有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
    ViewModel可以重复利用,可以独立使用;
  • 缺点:PresenterView的耦合性太高;
    每个View对应一个Presenter,导致类太多。

(4) MVVM(Model-View-ViewModel)

MVVM模式如下图:

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 <NSObject>
@optional
- (void)appViewClicked:(MYAppView *)appView;
@end

@interface MYAppView : UIView
@property (nonatomic, weak) MYAppViewModel *viewModel;
@property (nonatomic, weak) id<MYAppViewDelegate> 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<NSKeyValueChangeKey,id> * _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<NSKeyValueChangeKey,id> * _Nonnull change) {
        waekSelf.iconView.image = [UIImage imageNamed:change[NSKeyValueChangeNewKey]];
    }];
}
// TODO: 使用代理将点击事件传递给控制器
- (void)touchesBegan:(NSSet<UITouch *> *)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() <MYAppViewDelegate>
@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进行瘦身;
    ViewModel的一些业务逻辑放在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中主要使用单例模式、代理模式、观察者模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345