「iOS」————设计架构
iOS学习
- 架构模式
- MVC模式
- MVVM模式(Model-View-ViewModel)
- MVVM中数据的双向绑定
- ReactiveCocoa
- MVP模式
- MV(X)的区别
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- 单例模式
- 委托模式
- 观察者模式
- 工厂方法模式
- 策略模式
- 组合模式
- 设计原则
架构模式
iOS开发中,常用的架构模式有以下几种:
-
MVC(Model-View-Controller)模式:是 iOS 开发中最常见的架构模式。在 MVC 模式中,Model 负责数据处理和业务逻辑,View 负责界面展示,Controller 负责协调 Model 和 View 之间的交互。虽然 MVC 模式简单易懂,但在复杂项目中可能导致 Controller 过于臃肿,难以维护。
-
MVVM(Model-View-ViewModel)模式:MVVM 是一种基于数据绑定的架构模式,将 View 和 Model 之间的耦合度降低。ViewModel 负责处理业务逻辑和数据转换,通过数据绑定将数据展示在 View 上。iOS 中常用的 MVVM 框架有 ReactiveCocoa 和 RxSwift。
-
MVP(Model-View-Presenter)模式:MVP 模式将 View 和 Model 解耦,Presenter 负责处理业务逻辑和更新 View。Presenter 与 View 之间通过接口进行通信,降低了 View 对业务逻辑的依赖。MVP 模式通常用于需要单元测试的项目中。
此外还有两种架构模式:
-
VIPER(View-Interactor-Presenter-Entity-Routing)模式:VIPER 是一种更加复杂的架构模式,将应用拆分为多个模块,每个模块分别对应于 VIPER 中的不同角色。View 负责展示界面,Interactor 负责业务逻辑和数据处理,Presenter 负责协调 View 和 Interactor,Entity 是数据模型,Routing 负责页面之间的导航。VIPER 模式适用于大型、复杂的项目,能够提高代码的可维护性和可测试性。
-
Clean Architecture:Clean Architecture 是一种关注业务逻辑和数据流的架构模式,将应用分为不同的层级,包括 Entity、UseCase、Repository、Presenter、ViewModel 等。Clean Architecture 提倡将业务逻辑和框架相关的代码分离,使得代码更具可测试性和可维护性。
MVC模式
由三部分组成:
- 模型(Model):定义数据结构、数据加工与存储。
- 视图(View):视图内容展示、Touch事件交互。
- 控制器(Controller):处理业务逻辑、页面生命周期管理。
特点:
-
Model和View两者不直接通信,通过C层(Controller)实现信息传递。
-
Model 通过 Notification 和 KVO 机制通知给 Controller。(当Model的变化频繁时。)
- 进行网络请求等操作
-
View 通过 Target/Action、delegate 和 dataSource 三种机制与 Controller 进行通信。
- 只设置UI,注意如果cell的话,也是在View层定义,然后用Model在View层设置数据,但是Model是Controller给的,View不涉及Model的获取,只负责暂时。
对于一些静态的数据,C层可以直接操作Model。但是为了View层的解耦,View层建议通过上述三种机制进行传递。
简而言之:MVC就是Controller拿Model的数据区更新View,同时Model和View层之间是不互相通信的,它们之间的联系是通过Controller进行桥接的。
MVVM模式(Model-View-ViewModel)
由于MVC进过不断迭代,Controller会显得十分臃肿,为了给Controller瘦身,衍生出了新的架构模式MVVM架构。
-
Model: 数据层,业务逻辑处理、数据控制(本地数据、网络加载数据)。
-
ViewController/View: 展示层,显示用户可见得视图控件、与用户交互事件。界面的生命周期控制和业务间切换控制。(这一次与MVC的View相同)
- MVVM将ViewController视作View
-
ViewModel: 数据模型,是组织生成和维护的视图数据层。在这一层开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
通讯关系:
Model和View层不通讯,ViewModel作为桥梁,沟通ViewController和Model。
- 通过这张图,我们可以看出:当View|Controller有用户交互,就通过响应告诉ViewModel,然后Model改变,而ViewModel会监听到Model的改变然后通过Success回调来更新UI。
注意事项:
- View 引用ViewModel ,但反过来不行 (即不要在ViewModel中引入
#import UIKit.h
,任何视图本身的引用都不应该放在ViewModel中) - ViewModel 可以引用Model,但反过来不行
MVVM中数据的双向绑定
双向绑定是 MVVM 架构的核心特性,指的是 View 和 ViewModel 之间的自动同步机制:
正向绑定:View->viewModel
- 使用Block传值等方式完成正向的数据绑定
反向绑定:viewModel->View
- 可以使用KVO等反向传值方式。
MVVM模式中心思想是数据实现双向绑定,业务逻辑与视图的分离。
MVVM的Model
和View
没有交互,交互移步到ViewModel
;View
持有ViewModel
,ViewModel
持有Model
,反过来持有的话View
容易直接跟Model
容易产生耦合,这样就失去了架构的意义;
MVVM 在使用当中,通常还会利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。所以,MVVM 模式有些时候又被称作:model-view-binder 模式。
- 数据流动方向
-
Model → ViewModel → View
-
Model 层数据变化(如 name/email/age 改变)
-
ViewModel 通过 KVO 监听 Model 属性,自动更新自己的 displayXXX 属性
-
View 通过 KVO 监听 ViewModel 的 displayXXX 属性,自动刷新 UI
-
-
View → ViewModel → Model
-
用户在 View 上操作(如输入、点击按钮)
-
View 调用 ViewModel 的方法(如 updateUserName:)
-
ViewModel 修改 Model 属性
-
触发上面 Model → ViewModel → View 的链式自动更新
-
2. 双向绑定的本质
-
View 只和 ViewModel 交互,不直接操作 Model
-
ViewModel 作为桥梁,既能响应 Model 的变化,也能响应 View 的操作
-
KVO(或其他数据绑定机制)实现自动同步
缺点:
- 数据绑定使得Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
- 对于过大的项目,数据绑定需要花费更多的内存。
ReactiveCocoa
函数式编程(Functional Programming)和响应式编程(React Programming)也是当前很火的两个概念,它们的结合可以很方便地实现数据的绑定。于是,在 iOS 编程中,ReactiveCocoa 横空出世了,它的概念都非常 新,包括:
- 函数式编程(Functional Programming),函数也变成一等公民了,可以拥有和对象同样的功能,例如当成参数传递,当作返回值等。看看 Swift 语言带来的众多函数式编程的特性,就你知道这多 Cool 了。
- 响应式编程(React Programming),原来我们基于事件(Event)的处理方式都弱了,现在是基于输入(在 ReactiveCocoa 里叫 Signal)的处理方式。输入还可以通过函数式编程进行各种 Combine 或 Filter,尽显各种灵活的处理。
- 无状态(Stateless),状态是函数的魔鬼,无状态使得函数能更好地测试。
- 不可修改(Immutable),数据都是不可修改的,使得软件逻辑简单,也可以更好地测试。
MVP模式
MVP(Model-View-Presenter)是一种在 iOS 开发中常用的架构模式,类似于 MVC 和 MVVM,但在 MVP 中,视图和模型之间的通信通过 Presenter 层进行,从而实现视图和模型的解耦。它由三部分组成:
- 模型(Model):
- 负责管理数据和业务逻辑
- 视图(View):
- 视图负责展示数据和用户交互,与MVC中的视图类似
- 视图不包含业务逻辑,只负责展示数据和向Presenter发送用户操作的信息。
- 主持人(Presenter):
- Presenter 是 MVP 中的关键部分,负责处理视图的逻辑和用户交互。
- Presenter 从模型中获取数据,并将数据转换为视图可用的格式,然后更新视图。
- Presenter 不依赖于视图,通过接口与视图进行通信。
就是Presenter作为Model和View的中间人,从model层获取数据之后传给view,使得view和model没有耦合。
主要用法为:View层抽象为协议,然后Presenter层继承这个协议,Controller中引入Presenter实现这个协议的具体方法。Controller为View的延伸,只展示这个View,和使用时间交互逻辑。具体处理逻辑在Presenter中
使用中:
每个功能模块都有presenter这个文件夹,对每个模块的presenter都是为这个模块服务,我们可以把请求、储存数据的活动放在这里。并且在这层presenter中处理model数据。为了使controller得到的数据能直接使用,可以多写一个protocol,来承上启下,HomeViewProtocol就为了这个产生。
总结
MVP 架构下,Presenter 是连接 View 和 Model 的桥梁,所有业务逻辑都在 Presenter,Model 负责数据,View 只负责展示。
上述作为MV(X)的设计模式,都把一个应用分为三类:
- Models–负责主要的数据或者操作数据的数据访问层,可以想象 Perspn 和 PersonDataProvider 类。
- Views–负责展示层(GUI),对于iOS环境可以联想一下以 UI 开头的所有类。
- Controller/Presenter/ViewModel–负责协调 Model 和 View,通常根据用户在View上的动作在Model上作出对应的更改,同时将更改的信息返回到View上。
MV(X)的区别
-
MVC:Controller 负责业务逻辑,View 和 Model 直接交互较多,耦合度高。
-
MVP:Presenter 负责业务逻辑,View 只暴露接口,Model 只管数据,三者解耦更彻底。
-
MVVM:ViewModel 负责业务逻辑和数据转换,View 通过数据绑定自动更新。
设计模式
设计模式分为三大类:创建式模式、结构型模式、行为型模式
创建型模式
这类模式关注于对象的创建过程,提供了创建对象的不同方式,以降低耦合度并提高代码的灵活性。
-
单例模式:确保一个类只有一个实例,并提供一个全局访问点
-
工厂模式:提供一个创建对象的接口,但让子类决定实例化哪一个类。
-
抽象工厂模式:为创建一系列相关或相互依赖的对象提供一个接口,而无需指定它们具体的类。
-
建造者模式:分步骤构建复杂对象,用户只需指定类型和内容,不需知道内部构造细节。
-
原型模式:通过克隆已有对象来创建新对象,减少创建成本。
结构型模式
这类模式关注于如何组合类和对象以获得更大的结构,同时保持结构的灵活和高效。
-
适配器模式:将一个类的接口转换成客户期望的另一个接口。
-
桥接模式:将抽象部分与实现部分分离,使它们可以独立变化。
-
装饰模式:动态地给一个对象添加一些额外的职责,而不改变其结构。
-
组合模式:将对象组合成树形结构以表示“整体-部分”层次结构。
-
外观模式:为一组复杂的子系统提供一个统一的接口,以简化高层模块的使用。
行为型模式
这类模式关注于对象间的通信,以及职责的分配。
-
责任链模式:将请求沿着链传递,直到有对象处理它。
-
命令模式:将请求封装成对象,以便使用不同的请求、队列请求、日志请求等。
-
迭代器模式:提供一种方法顺序访问聚合对象的元素,而又不暴露其内部表示。
-
观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
-
策略模式:定义一系列算法,将它们一个个封装起来,并使它们可以相互替换。
-
模板方法模式:定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现。
-
访问者模式:在不改变数据结构的前提下,增加作用于这些元素的新操作。
以下是iOS设计中常用的六种模式:
单例模式
确保一个类只有一个实例,并提供一个全局访问方法。
分为懒汉模式(使用时才加载)和饿汉模式(一开始就加载)
一般使用加锁或者GCD实现,用GCD实现更好。
委托模式
“委托模式是一种常用的回调机制。委托方(如 UITableView)定义协议并持有 delegate 属性,代理方(如
ViewController)实现协议方法并将自己赋值为
delegate。这样,委托方在需要时通过协议回调代理方,实现事件的解耦和灵活扩展。”
允许一个对象(委托方)将某些任务交给另一个对象(委托对象)处理。这是一种对象间通信的模式。用于事件回调或数据请求。
实现步骤:
- 定义委托协议(Protocol)
- 在委托方中声明一个弱引用(weak)的delegate属性
- 在需要时向delegate发送信息
- 委托对象实现协议方法。
// 定义协议
@protocol TaskDelegate <NSObject>
- (void)taskDidComplete;
@end
// 委托方
@interface TaskHandler : NSObject
@property (nonatomic, weak) id<TaskDelegate> delegate;
- (void)startTask;
@end
@implementation TaskHandler
- (void)startTask {// 任务完成后回调[self.delegate taskDidComplete];
}
@end
// 委托对象
@interface ViewController : UIViewController <TaskDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];TaskHandler *handler = [[TaskHandler alloc] init];handler.delegate = self;
}
- (void)taskDidComplete {NSLog(@"Task completed!");
}
@end
观察者模式
定义对象间的一种一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都得到通知并自动更新。
通过KVO或者通知实现。
- KVO (Key-Value Observing): 内建于NSObject,用于监听属性变化。
- NSNotificationCenter: 广播机制,任意对象可以发送通知,多个观察者可以监听。
示例:
KVO
// 被观察对象
@interface ObservedObject : NSObject
@property (nonatomic, strong) NSString *name;
@end
// 观察者
[self.observedObject addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// 实现回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {if ([keyPath isEqualToString:@"name"]) {NSLog(@"Name changed to: %@", change[NSKeyValueChangeNewKey]);}
}
// 移除观察者(在dealloc中)
- (void)dealloc {[self.observedObject removeObserver:self forKeyPath:@"name"];
}
NSNotificationCenter示例:
// 发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"DataUpdated" object:nil];
// 监听通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDataUpdated:) name:@"DataUpdated" object:nil];
// 处理通知
- (void)handleDataUpdated:(NSNotification *)notification {// 更新UI或数据
}
// 移除监听(在dealloc中)
- (void)dealloc {[[NSNotificationCenter defaultCenter] removeObserver:self];
}
工厂方法模式
定义一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。
不指定具体类也能创建一系列相关或依赖的对象。
在属性那块使用到,set方法
// 抽象产品
@interface Product : NSObject
@end
// 具体产品A
@interface ProductA : Product
@end
// 具体产品B
@interface ProductB : Product
@end
// 工厂基类
@interface Factory : NSObject
+ (Product *)createProduct;
@end
@implementation Factory
+ (Product *)createProduct {// 默认实现,或者抛出异常(因为子类必须重写)[NSException raise:@"Abstract Method" format:@"Subclasses must override"];return nil;
}
@end
// 具体工厂A
@interface FactoryA : Factory
@end
@implementation FactoryA
+ (Product *)createProduct {return [[ProductA alloc] init];
}
@end
// 具体工厂B
@interface FactoryB : Factory
@end
@implementation FactoryB
+ (Product *)createProduct {return [[ProductB alloc] init];
}
@end
// 使用
Product *productA = [FactoryA createProduct];
Product *productB = [FactoryB createProduct];
策略模式
定义一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式让算法独立于使用它的客户端而变化。
使用场景:需要动态切换算法或行为(如排序算法、支付方式)。
实现:
// 策略接口
@protocol PaymentStrategy <NSObject>
- (void)payAmount:(NSInteger)amount;
@end
// 具体策略:信用卡支付
@interface CreditCardPayment : NSObject <PaymentStrategy>
@end
@implementation CreditCardPayment
- (void)payAmount:(NSInteger)amount {NSLog(@"Paid %ld via Credit Card", amount);
}
@end
// 具体策略:支付宝支付
@interface AlipayPayment : NSObject <PaymentStrategy>
@end
@implementation AlipayPayment
- (void)payAmount:(NSInteger)amount {NSLog(@"Paid %ld via Alipay", amount);
}
@end
// 上下文(使用策略)
@interface PaymentContext : NSObject
@property (nonatomic, strong) id<PaymentStrategy> strategy;
- (void)executePayment:(NSInteger)amount;
@end
@implementation PaymentContext
- (void)executePayment:(NSInteger)amount {[self.strategy payAmount:amount];
}
@end
// 使用
PaymentContext *context = [[PaymentContext alloc] init];
context.strategy = [[CreditCardPayment alloc] init];
[context executePayment:100]; // 使用信用卡支付
context.strategy = [[AlipayPayment alloc] init];
[context executePayment:200]; // 使用支付宝支付
组合模式
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
使用场景:表示树形结构(如UIView层次、菜单树)。
// 组件接口
@protocol Component <NSObject>
- (void)operation;
@end
// 叶子节点(无子节点)
@interface Leaf : NSObject <Component>
@end
@implementation Leaf
- (void)operation {NSLog(@"Leaf operation");
}
@end
// 复合节点(可包含子节点)
@interface Composite : NSObject <Component>
@property (nonatomic, strong) NSMutableArray<id<Component>> *children;
- (void)addComponent:(id<Component>)component;
- (void)removeComponent:(id<Component>)component;
@end
@implementation Composite
- (instancetype)init {if (self = [super init]) {_children = [NSMutableArray array];}return self;
}
- (void)addComponent:(id<Component>)component {[self.children addObject:component];
}
- (void)removeComponent:(id<Component>)component {[self.children removeObject:component];
}
- (void)operation {// 先执行自身操作NSLog(@"Composite operation");// 再执行所有子节点的操作for (id<Component> child in self.children) {[child operation];}
}
@end
// 使用
Composite *root = [[Composite alloc] init];
Composite *branch1 = [[Composite alloc] init];
Leaf *leaf1 = [[Leaf alloc] init];
Leaf *leaf2 = [[Leaf alloc] init];
[root addComponent:branch1];
[root addComponent:leaf1];
[branch1 addComponent:leaf2];
[root operation]; // 递归调用所有节点的operation方法
设计原则
- 单一职责原则
一个类只负责一项职责(功能),不要把不相关的功能都塞进一个类。也就是说一个类应当专注于做好一件事情。
例子:单例,UIVIew和CALayer单一职能。
- 开放封闭原则
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。 也就是当需要改变行为时,应该通过扩展而不是修改已有的代码来实现。
优点:允许在不修改原有代码的情况下扩展新功能,减少引入bug的风险
例子:UITableVIew里面的自定义cell,不改动系统代码,通过扩展修改
- 里氏替换原则
子类对象必须能够替换父类对象,并且程序逻辑不变。同理,父类对象可以无差别地使用子类对象。
例子:子类重写父类方法时,不能改变原有方法的语义。UIView 的子类都能安全地替换 UIView。
- 接口隔离原则
不应该强迫客户依赖它们不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。接口应该尽量细化,客户端只依赖它实际需要的办法。
例子:iOS 的 delegate 协议通常拆分为多个小协议(如 UITableViewDelegate、UITableViewDataSource)。
- 依赖倒置原则
高层模块不应该依赖低层模块,二者都应该依赖于抽象(接口/协议);抽象不应该依赖细节,细节应该依赖抽象。
例子:通过协议(interface)编程,而不是直接依赖具体类。iOS 的数据源和代理都是通过协议实现的。
- 迪米特原则/最小知识原则
一个对象应该对其他对象有尽可能少的了解(只与直接朋友通信)。
不要链式访问(a.b.c.d),使用方法访问。
- 合成复用原则
优先使用对象组合(has-a),而不是类继承(is-a)来实现代码复用。
合成
合成是指一个总体对依托他而存在的关系,如一个人对他的房子和家具。该关系依赖性不强,比如人没了,这个关系就自然消失了。
聚合
聚合是比合成关系更强的一种依赖关系,如有一台汽车,汽车对引擎、轮胎的关系就是聚合关系。这些关系就是带有聚合性质的。车没了,该车的引擎和轮胎自然也没了。在我们的设计中,这种关系不应该频繁出现,因为这样会增大设计的耦合度。
工厂模式介绍
简单工厂模式就是用协议,一个init方法判断传入的字符串
工厂方法模式就是继承,用不同具体子类初始化。
抽象工厂模式:
类似于上面两种模式的合成。首先通过一个根类工厂,使用简单工厂模式判断,进入不同的工厂。在到具体的工厂,用工厂方法模式,初始化不同的工件。