原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、项目架构
- 1、架构方案选择
- 2、搭建目录结构
- 3、Pods管理第三方库
- 4、pch头文件
- 二、六大设计原则
- 1、单一职责原则
- 2、开闭原则
- 3、里氏替换原则
- 4、接口隔离原则
- 5、 依赖倒置原则
- 6、迪米特法则
- 三、创建型模式:创建对象
- 1、抽象工厂模式
- 2、建造者模式
- 3、工厂方法模式(Factory Method)🧐
- 4、原型模式(Prototype)🧐
- 5、单例模式(Singleton)🧐
- 四、结构型模式:处理类或对象的组合
- 1、适配器模式 🧐
- 2、桥接模式 🧐
- 3、组合模式
- 4、装饰模式
- 5、外观模式
- 6、享元模式
- 7、代理模式
- 8、状态模式
- 五、行为型模式:类或对象的交互和分配职责
- 1、职责链模式(Chain of Responsibility)🧐
- 2、命令模式 🧐
- 3、解释器模式
- 4、迭代器模式(Iterator)🧐
- 5、中介者模式 🧐
- 6、备忘录模式(Memento)🧐
- 7、观察者模式(Observer)🧐
- 8、状态模式
- 9、策略模式
- 10、模板方法模式
- 11、访问者模式
- Demo
- 参考文献
一、项目架构
架构原则:易读性、易维护性、易扩展性。
原因
许多创业项目为了赶时间上线,前期没有框架设计,没有代码规范,每个人随意发挥,不出几个月就会出现:产品体验差、崩溃率飙升、开发进度缓慢、读起来十分费劲、Bug
定位比较困难等问题,不得不进行重构。
在战略角度上也许是对的,先占坑再完善,但在架构角度这是不可取的,还是要严格遵循“高内聚,低耦合”的理念,确保框架由底层服务到顶层业务,各模块分工明确,各司其职,相对独立,模块间通过接口调用,严禁在A里直接使用B,B里直接使用C,这样会使得各模块藕断丝连难舍难分,后期只会越来越乱。
1、架构方案选择
a、MVC
❶ Model模块
数据模型,一般为API接口数据或本地DB数据,负责将原始数据转成本地模型
❷ View模块
用于信息展示(数据来源于Model
),负责接收/转发事件(不处理事件)
-
UIWindow对象:位于
view
层次结构中的最顶层 -
UIView对象
- 通过
addSubview
和removeFromSuperview
等方法管理view
的层次结构 - 使用
layoutSubviews
、layoutIfNeeded
和setNeedsLayout
等方法布局view
的层次结构 - 当你发现系统提供
view
已经满足不了你想要的外观需求时,可以重写drawRect
方法或通过layer
属性来构造复杂的图形外观和动画 -
使用Core Animation框架的Layer对象:渲染
view
外观和构建复杂的动画
- 通过
-
UIControl对象:用户交互的
View
,常用的有button
、switch
、textfield
等
❸ Controller模块
负责连接View
和Model
,处理事件,承载大部分逻辑
-
UIApplication对象:用户与iOS设备交互时产生的事件(
Multitouch Events
,Motion Event
,Remote Control Event
)交由UIApplication
对象来分发给control objects
(UIControl
)对应的target objects
来处理并且管理整个事件循环。 -
App delegate对象:遵循
UIApplicationDelegate
协议,响应app运行时重要事件(app启动、app内存不足、app终止、切换到另一个app、切回app),主要用于app在启动时初始化一些重要数据结构;例如,初始化UIWindow
,设置一些属性,为window
添加rootViewController
。 -
View Controller对象
-
view
层次结构中的根view
,你可以添加子view
来构建复杂的view
-
controller
有一些viewDidLoad
、viewWillAppear
等方法来管理view
的生命周期 - 由于它继承
UIResponder
,所有还会响应和处理用户事件。
-
b、MVVM
MVC
模式还是没有干净、很好地分割模块,在用户点击、网络请求和JSON
解析数据这些方面,会有交叉重叠的地方。
Service模块:管理网络请求
ViewModel模块:获取数据后的数据解析
Controller模块:不再直接持有Model
,Controller
持有ViewModel
,Model
被交给ViewModel
管理。
2、搭建目录结构
-
AppDelegate:应用入口
- AppDelegate.h
- AppDelegate + AppService.h
-
Modlues:功能模块
- Home(首页)
- Model
- View
- Controller
- Service
- Resource
- Logic
- PersonCenter(个人中心)
- ......
- Home(首页)
-
Manager:管理模块
- AppManager.h
- UserManager
- UserManager.h
- UserInfo.h
- .......
-
Tools:工具类
- AdPage
- Network
- Category
- Toast
- ......
-
Base:基类
- TabbarController
- RootViewController
- NavigationController
- WebViewController
- .......
- ThridParty:将第三方框架进行二次封装
-
Define:全局宏定义
- UtilsMacros.h
- URLMacros.h
- FontAndColorMacros.h
- ThirdMacros.h
- Resource:资源文件夹
a、AppDelegate
AppDelegate
是应用的代理,应用级的事件都委托它处理,包含启动退出、推送等事件,以及IM
、支付等第三方的回调,这使得AppDelegate
内代码庞大,错综复杂,十分不利于阅读和维护,因此可以新增一个AppDelegate+AppService
类别,用来处理生命周期之外的业务,AppDelegate
作为事件入口,具体实现直接调用类别里的方法。
b、Modlues
Modules
包含了应用内的功能模块,根据底部Tab
栏划分,每个模块内使用的是MVC
模式,有人会问为什么多了Resource
、Logic
和Service
文件夹,MVC
只是一种设计思想,并非死套路就只有三个文件夹,根据实际需求适当增加,目的是解放VC。
在这里选择了在Service
封装数据请求。至于Resource
为什么在这,个人认为当功能模块层级较多时,每个大功能模块都对应许多资源,对应到模块内用起来方便,当然也可以放到最外层的Resource
文件夹里,建立对应的模块名称,在这里选择把公共的放到最外层Resource
里,功能相关的放到模块里的Resource
文件夹内。
ViewController
顾名思义是视图控制器,不应做太多与其不相关的工作,将逻辑处理交给对应这个VC
的Logic
类,Logic
承担着逻辑处理和Service
的调用拿到数据并解析,通过delegate
回调给VC
,VC
拿到已经处理完毕的数据,去渲染视图。这样做的话,VC
内只剩下与Logic
的交互,还有管理View
的代码,必然清晰很多。
c、Manager
Manager
通常使用类方法或者单例来实现,作用是提供全局基础服务,例如网络状态监听,APP介绍,用户快速登录退出操作以及登录状态的获取等。
d、Tools
包含全局通用工具,来源于对三方框架的二次封装,或是自己写的工具类,比如封装了带AES
加密网络请求工具,全局Toast
提示,广告页等。
e、Base
存放项目的基类,包含一些定制化的内容,例如页面样式,空数据页面等。使用基类来实现,可以统一控制,利于维护,减少冗余。
f、ThridParty
存放一些第三方的类库和对第三方封装,比如第三方登录、支付、IM等。
g、Define
存放全局通用宏。
-
UtilsMacros
定义的是一些工具宏,比如获取屏幕宽高,系统版本,数据类型验证等。 -
URLMacros
定义服务器接口地址以及环境开关。 -
FontAndColorMacros
定义全局用的色值、字体大小,这里建议跟设计师共同维护一个设计规范,例如:定义一个主色调宏MainColor
,色值是0x333333
,我们全局使用MainColor
宏作为背景颜色,当某天App
改版,色值改变,我们只需要去更改0x333333
即可,其他代码不需要动,同时也能一定程度约束设计师,不要随便增加一种颜色,非常接近的颜色应当使用一个。如果设计师不愿意维护这个规范,你可以尝试打一架,打不过的话,就只能自己维护了。 -
ThirdMacros
包含第三方框架相关的定义,例如keySecret
等。
h、Resource
这里存放了全局的一些资源文件,功能模块的放到了模块内的Resource
文件夹内,看个人喜好。
3、Pods管理第三方库
- AFNetworking:网络请求框架
- Masonry:纯代码布局框架
- MBProgressHUD:Toast提示控件
- MJRefresh:上下拉刷新控件
- YYKit:高性能组件库
- SDWebImage:图片下载缓存框架
首先要分析自己的应用,都用得着哪些框架,在同一类型的框架里选择的宗旨是——符合自身且维护及时,超过一年没更新的就要慎重了。
a、AFNetworking
网络请求是一款APP必须的,大家通常都会选择AFNetworking
或者YTKNetwork
作为基础网络框架,但这只是个基础框架,虽说可以直接调用请求数据,但如果有一些其他需求,例如加密或者加公共参数等,想要满足就比较费劲了,所以大多数开发者会对其进行二次封装,目的是为了自定义一些需求,可以自己掌控并处理请求和返回数据,也为将来如果更换网络框架,减少代码改动量。
b、Masonry
三种布局方式,分别是:代码计算frame
、Masonry
代码约束,SB/xib
直拖约束。在不同的场景下,使用最合适的方式,才能达到最佳效果。
c、MJRefresh
大部分应用都会有TableView
或CollectionView
,上下拉刷新是比较常用的,MJRefresh
提供的功能比较强大,支持自定义,提供样式齐全,更新及时。
d、YYKit
选择这个框架的原因是功能和性能都比较强大,用一个框架就可以做很多事,包含了解析数据,缓存,图像处理,文本处理,异步绘制等组件。而且YYKit的设计思想是category
,几乎没有入侵性,使用起来也非常方便。但是YYWebImage
这个高性能异步图像加载框架可能有点过时,因为其使用的是NSURLConnection
请求,而SDWebImage
已替换成了URLSession
,所以图像异步加载上,还是选择更加专业的SDWebImage
。
-
YYModel
— 高性能的 iOS JSON 模型框架。 -
YYCache
— 高性能的 iOS 缓存框架。 -
YYImage
— 功能强大的 iOS 图像框架。 -
YYWebImage
— 高性能的 iOS 异步图像加载框架。 -
YYText
— 功能强大的 iOS 富文本框架。 -
YYKeyboardManager
— iOS 键盘监听管理工具。 -
YYDispatchQueuePool
— iOS 全局并发队列管理工具。 -
YYAsyncLayer
— iOS 异步绘制与显示的工具。 -
YYCategories
— 功能丰富的Category
类型工具库
4、pch头文件
pch
头文件的内容能被项目中的其他所有源文件共享和访问,是一个预编译文件。
- 存放一些整个项目中都用得上的宏
- 用来包含一些整个项目中都用得上的头文件
- 能自动打开或者关闭日志输出功能
以前创建一个新工程xcode
会在Supporting files
文件夹下面自动创建一个“工程名-Prefix.pch
”文件,但是苹果发现大家为了省事把大量的头文件和宏定义放到pch
里边,导致编译时间过长,苹果去掉他可能是要加快编译时间增加用户体验。那么需要的时候如何在Xcode中添加pch
文件?
步骤一:Command+N
,打开新建文件窗口,创建一个pch
文件。
步骤二:在工程的TARGETS
里边Building Setting
中搜索Prefix Header
,然后把Precompile Prefix Header
右边的NO
改为Yes
,这样预编译后的pch文件会被缓存起来,可以提高编译速度。然后在Prefix Header
右边双击,添加刚刚创建的pch
文件的工程路径,添加格式:“$(SRCROOT)/项目名称/pch文件名
” ,$(SRCROOT)
的意思就是工程根目录的意思。如果还不太清楚的话可以右键pch
文件,然后show in finder
找到路径。添加完成后,他会自动帮你变成你工程所在的路径。
二、六大设计原则
1、单一职责原则
含义:一个类只负责一件事
系统使用范例:UIView
只负责事件的传递和响应,CALayer
负责动画的渲染
Demo演示
设计一个订单列表,列表分为待支付、待收货、已收货等列表,那我们是写一个类,使用if
判断是哪个类型,然后请求相应的数据,还是写多个类,分别执行各自的功能呢。
很多人会觉的写一个类比较省事,但是过多的判断条件,各种职责冗余到一个类中真的好吗,如果待支付列表需要加一些特殊的功能呢,待收货也需要加一些功能呢,那这个类是不是变得条件判断异常的多。所以还是写成多个类,实现各自的逻辑比较好。
其实另外我们写列表的Cell
,也是一个道理,分成几种类型的Cell
去写,而不是一个Cell
实现几种类型。
import Foundation
class OrderList: NSObject //订单列表
{
var waitPayList: WaitPayList? //待支付
var waitGoodsList: WaitGoodsList? //待收货
var receivedGoodsList: ReceivedGoodsList? //已收货
}
class WaitPayList: NSObject
{
}
class WaitGoodsList: NSObject
{
}
class ReceivedGoodsList: NSObject
{
}
2、开闭原则
含义:类、模块、函数,可以去扩展,但不要去修改。如果要修改代码,尽量用继承或组合的方式来扩展类的功能,而不是直接修改类的代码。当然,如果能保证对整个架构不会产生任何影响,那就没必要搞的那么复杂,直接改这个类吧。
Demo演示
设计支付功能的时候,会用到不同的支付方式,我们可以选择在支付的时候使用判断支付条件然后使用不同的支付方式,然而这种设计真的好吗。如果我们添加了一个支付方法或者删除了一个支付方法是不是要改动pay
方法的逻辑,那每一次的调整都要改动pay
方法的逻辑是不是不合理了。
import Foundation
class PayHelper
{
func pay(send: PaySendModel) -> Void
{
if send.type == 0
{
//支付宝支付
}
else if send.type == 1
{
//微信支付
}
}
}
class PaySendModel
{
var type: Int = 0
var info: [String: AnyHashable]?
}
依据开闭原则具体做法应该是设计扩展支付方式来实现不同的支付。
import Foundation
class PayHelper
{
var processors: [Int: PayProcessor]?
func pay(send: PaySendModel) -> Void
{
guard let processors = processors else {return}
guard let payProcessor: PayProcessor = processors[send.type] else {return}
payProcessor.handle(send: send)//支付
}
}
class PaySendModel
{
var type: Int = 0
var info: [String: AnyHashable]?
}
protocol PayProcessor
{
func handle(send: PaySendModel)
}
class AliPayProcessor: PayProcessor
{
func handle(send: PaySendModel)
{
}
}
class WeChatPayProcessor: PayProcessor
{
func handle(send: PaySendModel)
{
}
}
可以看到修改之后的支付,扩展起来是不是很方便,增加支付方式只需要继承PayProcessor
就行了,不需要更改pay
方法了。
3、里氏替换原则
含义:父类可以被子类无缝替换,且原有功能不受影响。子类可以扩展父类的方法,但不应该复写父类的方法。
Demo演示
例如:KVO
实现原理,调用addObserver
方法,系统在动态运行时候为我们创建一个子类,我们虽然感受到的是使用原有的父类,实际上是子类。
我们定义汽车的基类,基类里面有行驶的方法,现在我们有个宝马车,宝马车继承汽车基类,也有行驶方法。现在我们想知道宝马车的行驶速度是多少,该怎么设计呢。
import Foundation
class Car
{
func run()
{
print("汽车跑起来了")
}
}
class BaoMaCar: Car
{
override func run()
{
super.run()
print("当前行驶速度是80Km/h")
}
}
可以看到我们重写了run
方法,增加了汽车行驶速度的逻辑,这样是不满足的里氏替换原则的。因为所有基类Car
替换成子类BaoMaCar
,run
方法的行为跟以前不是一模一样了。所以修改名称:
import Foundation
class Car
{
func run()
{
print("汽车跑起来了")
}
}
class BaoMaCar: Car
{
func showSpeed()
{
print("当前行驶速度是80Km/h")
}
}
4、接口隔离原则
含义:使用多个专门的协议,而不是一个臃肿庞大的协议,每个协议中的方法尽量少。
系统使用:UITableViewDelegate
专门负责处理回调事件,UITableViewDataSource
专门负责处理数据源
Demo演示
定义一个汽车接口,要求实现run
等方法。
protocol CarProtocol
{
func run()
func showSpeed()
func playMusic()
}
class MyCar: CarProtocol
{
func run()
{
print("汽车跑起来了")
}
func showSpeed()
{
print("当前行驶速度是80Km/h")
}
func playMusic()
{
print("播放音乐")
}
}
可以看到我们定义MyCar
实现了CarProtocol
的接口,但是并不是每个车都有播放音乐的功能的,这样对于一般的低端车没有这个功能,对于他们来说,这个接口的设计就是冗余的。
protocol ProfessionalCar //具备一般功能的车
{
func run()
func showSpeed()
}
protocol EntertainingCar //具备娱乐功能的车
{
func run()
func showSpeed()
func playMusic()
}
class SangTaNaCar: ProfessionalCar //桑塔纳轿车
{
func run()
{
print("汽车跑起来了")
}
func showSpeed()
{
print("当前行驶速度是80Km/h")
}
}
class BMWCar: EntertainingCar //宝马轿车
{
func run()
{
print("汽车跑起来了")
}
func showSpeed()
{
print("当前行驶速度是80Km/h")
}
func playMusic()
{
print("播放音乐")
}
}
5、 依赖倒置原则
含义:高层模块不应该依赖低层模块,抽象不应该依赖于具体实现,具体实现可以依赖于抽象。定义增删改查的数据方法的时候,上层只管直接调用感知不到其内部具体实现,比如是用文件、plist
还是数据库,不应该把具体实现暴露给使用方。
Demo演示
class CarGas
{
func refuel(_ gaso: Gasoline90)
{
print("加90号汽油")
}
func refuel(_ gaso: Gasoline93)
{
print("加93号汽油")
}
}
class Gasoline90
{
}
class Gasoline93
{
}
可以看到CarGas
高层模块依赖了底层模块Gasoline90
和Gasoline93
,这样写是不符合依赖倒置原则的。
class CarGas
{
func refuel(_ gaso: Gasoline)
{
print("加\(gaso.name)汽油")
}
}
protocol Gasoline
{
var name: String { get }
}
class Gasoline90: Gasoline
{
var name: String = "90号"
}
class Gasoline93: Gasoline
{
var name: String = "93号"
}
修改之后我们高层模块Car
依赖了抽象IGasoline
,底层模块Gasoline90
和Gasoline93
也依赖了抽象IGasoline
,这种设计是符合依赖倒置原则的。
6、迪米特法则
含义:一个对象对另一个对象了解得越多,那么,它们之间的耦合性也就越强,当修改其中一个对象时,对另一个对象造成的影响也就越大。一个对象尽量不应该知道其他对象的方法和变量,做到高内聚,低耦合。
Demo演示
实现一个给汽车加油的设计,使得我们可以随时保证加油的质量过关。
class Person
{
var car: GasolineCar?
func refuel(_ gaso: GasolineSecond)
{
if gaso.isQuality == true //如果汽油质量过关,我们就给汽车加油
{
car?.refuel(gaso)
}
}
}
class GasolineCar
{
func refuel(_ gaso: GasolineSecond)
{
print("加\(gaso.name)汽油")
}
}
protocol GasolineSecond
{
var name: String { get }
var isQuality: Bool { get }
}
class Gasoline90Second: GasolineSecond
{
var name: String = "90号"
var isQuality: Bool = false
}
class Gasoline93Second: GasolineSecond
{
var name: String = "93号"
var isQuality: Bool = true
}
可以看到上面有个问题,我们作为车主怎么知道汽油的质量是否过关呢,即时我们知道,加油判断油的质量这个事情也不应该由我们来做。
class Person //给车加油的人
{
var car: GasolineCar?
func refuel(_ worker: WorkerInPetrolStation, _ gaso: GasolineSecond)
{
guard let car = car else {return}
worker.refuel(car, gaso)
}
}
class WorkerInPetrolStation //加油站工作人员
{
func refuel(_ car: GasolineCar, _ gaso: GasolineSecond)
{
if gaso.isQuality == true //如果汽油质量过关,我们就给汽车加油
{
car.refuel(gaso)
}
}
}
class GasolineCar
{
func refuel(_ gaso: GasolineSecond)
{
print("加\(gaso.name)汽油")
}
}
protocol GasolineSecond
{
var name: String { get }
var isQuality: Bool { get }
}
class Gasoline90Second: GasolineSecond
{
var name: String = "90号"
var isQuality: Bool = false
}
class Gasoline93Second: GasolineSecond
{
var name: String = "93号"
var isQuality: Bool = true
}
可以看到这样我们就实现了低耦合,我们作为车主只需要知道有加油站工作人员和要加的汽油就行了,不需要知道太多汽油相关的知识,以及加油相关的操作流程,这些都交给了工作人员,这样是符合我们的迪米特原则的。
三、创建型模式:创建对象
设计模式是对反复出现的各种问题所提出的解决方案,目的是为了代码的复用、可维护。
1、抽象工厂模式
a、概念
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
提供一个接口,用于创建与某些对象相关或依赖于某些对象的类家族,而又不需要指定它们的具体类。通过这种模式可以去除客户代码和来自工厂的具体对象细节之间的耦合关系。
类簇是一种把一个公共的抽象超类下的一些私有的具体子类组合在一起的架构。抽象超类负责声明创建私有子类实例的方法,会根据被调用方法的不同分配恰当的具体子类,每个返回的对象都可能属于不同的私有子类。
b、系统使用
Cocoa
将类簇限制在数据存储可能因环境而变的对象生成上。Foundation
框架为NSString
、NSData
、NSDictionary
、NSSet
、和NSArray
对象定义了类簇。公共超类包括上述的不可变类和与其相互补充的可变类NSMutableString
、NSMutableData
、NSMutableDictionary
、NSMutableSet
、和NSMutableArray
。
+(instancetype)buttonWithType:(UIButtonType)buttonType;
[NSNumber numberWithBool:YES]
[NSNumber numberWithInt:1]
c、Demo演示
import Foundation
class ChengDuCity//成都市
{
//有两个啤酒厂
var abstractFactory1: AbstractFactory?
var abstractFactory2: AbstractFactory?
}
protocol AbstractFactory//抽象工厂
{
//生产两种产品
func createProductA() -> Product
func createProductB() -> Product
}
protocol Product
{
//产品名称
var name: String { get }
}
class BearProduct: Product
{
//啤酒产品
var name: String = "啤酒"
}
class ConcreteFactory1: AbstractFactory //啤酒工厂1
{
//生产产品A
func createProductA() -> Product
{
return BearProduct()
}
//生产产品B
func createProductB() -> Product
{
return BearProduct()
}
}
class ConcreteFactory2: AbstractFactory //啤酒工厂2
{
//生产产品A
func createProductA() -> Product
{
return BearProduct()
}
//生产产品B
func createProductB() -> Product
{
return BearProduct()
}
}
2、建造者模式
a、概念
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
b、Demo演示
class BeijingSiheyuanProduct: BuilderProduct //北京四合院
{
var name: String = "北京四合院"
}
class ConcreteBuilder: Builder //建筑工
{
func createProduct() -> BuilderProduct
{
return BeijingSiheyuanProduct()
}
}
class Director //建筑师
{
var builder: ConcreteBuilder?
func construct() //指导生产
{
guard let product = builder?.createProduct() else {return}
print("修建房屋:" + product.name)
}
}
3、工厂方法模式(Factory Method)
a、概念
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method
使一个类的实例化延迟到其子类。
b、Demo演示
import Foundation
protocol Creator// 生产
{
func factoryMethod() -> Product
}
protocol Product// 产品
{
var name: String { get }
}
class ConcreteProduct: Product// 实际产品
{
var name: String = "啤酒"
}
class ConcreteCreator: Creator// 生产者
{
func factoryMethod() -> Product
{
return ConcreteProduct()
}
}
4、原型模式(Prototype)
a、概念
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,可被理解为一种深复制。
b、系统使用
在Objective-C
中使用原型模式, 首先要遵循NSCoping
协议(OC中一些内置类遵循该协议,例如NSArray
、NSMutableArray
等)
c、Demo演示
import Foundation
protocol Prototype
{
func clone() -> Product
}
protocol Product
{
var name: String { get }
}
class ConcreteProduct: Product, Prototype
{
var name: String = "啤酒"
func clone() -> Product
{
let p = ConcreteProduct()
p.name = name
return p
}
}
class Client
{
var prototype: Prototype!
func operation() -> Product
{
return prototype.clone()
}
}
5、单例模式(Singleton)
a、单例模式的定义
一个单例类,在整个程序中只有一个实例,并且提供一个类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,到程序(APP)退出时由系统自动释放这部分内存。
b、系统为我们提供的单例类有哪些?
UIApplication(应用程序实例类)
NSNotificationCenter(消息中心类)
NSFileManager(文件管理类)
NSUserDefaults(应用程序设置)
NSURLCache(请求缓存类)
NSHTTPCookieStorage(应用程序cookies池)
c、重复初始化单例类会怎样?
[[UIApplication alloc] init];
// 程序直接崩溃,并报了下面的错
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'There can only be one UIApplication instance.'
d、在哪些地方会用到单例模式
经常调用的类,如工具类、公共跳转类等。
e、单例类的生命周期
内存布局包括:栈、堆、全局区域、常量、代码区
一个单例类在程序中只能初始化一次,为了保证在使用中始终都是存在的,所以单例是在存储器的全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,在APP结束后由系统释放这部分内存内存。
g、单例模式的优缺点
优点:
- 在整个程序中只会实例化一次,所以在程序如果出了问题,可以快速的定位问题所在
- 由于在整个程序中只存在一个对象,节省了系统内存资源,提高了程序的运行效率
缺点: - 不能被继承,不能有子类
- 不易被重写或扩展(可以使用分类)
- 由于单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源
f、单例模式的创建方式
同步锁 :NSLock
@synchronized(self) {}
信号量控制并发:dispatch_semaphore_t
条件锁:NSConditionLock
dispatch_once_t
苹果官方推荐开发者使用dispatch_once_t
来创建单例,以下是实现单例模式的常用代码:
-h
@interface ShareApp : NSObject
+ (instancetype)sharedApp;
@end
-m
+ (instancetype)sharedApp {
static ShareApp *app = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
app = [[ShareApp alloc] init];
});
return app;
}
还有更加安全的实现方法,主要是防止通过allocWithZone
和copyWithZone
来意外地创建了新对象,造成了并非只有一个对象,单例模式失效。
#import <Foundation/Foundation.h>
@interface Mooc : NSObject
+ (id)sharedInstance;
@end
#import "Mooc.h"
@implementation Mooc
+ (id)sharedInstance
{
// 静态局部变量
static Mooc *instance = nil;
// 通过dispatch_once方式 确保instance在多线程环境下只被创建一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 创建实例
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
// 重写方法【必不可少】
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
// 重写方法【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
return self;
}
@end
四、结构型模式:处理类或对象的组合
1、适配器模式
a、概念
老文件相对成熟和稳定,当作出修改时候就会产生风险,这时可以将被适配对象(年代久远的类)作为成员变量放到新建的适配对象中,在其中调用了年代久远的类中必不可少的方法之后再实现自己新添的功能即可,代码实现如下:
被适配对象(年代久远的类),operation
方法即是那个无法割舍掉的老方法,也就是传说中的祖传代码,改了就会有神坑的方法。
b、Demo演示
#import <Foundation/Foundation.h>
@interface Target : NSObject
- (void)operation;
@end
#import "Target.h"
@implementation Target
- (void)operation
{
// 原有的具体业务逻辑
}
@end
新建的适配对象,核心实现是将古董类作为成员变量持有,这样就可以在新建的方法中调用古董类中的祖传代码,之后再添加自己的新配方:
#import "Target.h"
// 适配对象
@interface CoolTarget : NSObject
// 被适配对象
@property (nonatomic, strong) Target *target;
// 对原有方法包装
- (void)request;
@end
#import "CoolTarget.h"
@implementation CoolTarget
- (void)request
{
// 额外处理
[self.target operation];
// 额外处理
}
@end
c、二次封装接口
使用适配器模式,把UITableView
的接口进行二次封装,统一对外回调我们关心的接口,比如点击cell
的事件回调等,大大简化了接口的复杂度。
class ListAdaper<T>: UITableViewDelegate, UITableViewDataSource
{
var cellClick: ((_ obj: T) -> Void)?
init(_ tableView: UITableView)
{
tableView.delegate = self
}
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
cellClick?(datas[indexPath.row])
}
}
2、桥接模式
a、概念
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
b、Demo演示:三套数据
一个列表有三套不同的数据,需要根据具体场景使用其中某套数据,这时候可以将列表可以看成类A的一个具体子类如A2,3套数据可以抽象出一个父类B,3套数据分别对应类B的三个子类B1、B2、B3,而抽象类A有一个成员变量是抽象类B。类A、类B都有3个具体的子类,具体关系可以这样表示:ClassA(A1、A2、A3)——>ClassB(B1、B2、B3)
。通过代码实现如下:
抽象类B类似这样,提供了一个抽象方法fetchData
给不同子类重写实现不同功能(这里的场景是获取不同的数据):
#import <Foundation/Foundation.h>
@interface BaseObjectB : NSObject
- (void)fetchData;
@end
#import "BaseObjectB.h"
@implementation BaseObjectB
- (void)fetchData
{
// override to subclass
}
@end
B的三个子类B1、B2、B3都是类似这样的,重写了父类的fetchData
方法做具体的逻辑处理:
#import "BaseObjectB.h"
@interface ObjectB1 : BaseObjectB
@end
#import "ObjectB1.h"
@implementation ObjectB1
- (void)fetchData{
// 具体的逻辑处理
}
@end
抽象类A类似这样,核心实现是抽象类B作为抽象类A的成员变量而存在,提供了一个抽象方法handle
提供给子类重新来处理调用抽象类B,在运行时变为具体类B1、B2、B3提供的不同套数据:
#import <Foundation/Foundation.h>
#import "BaseObjectB.h"
@interface BaseObjectA : NSObject
// 桥接模式的核心实现
@property (nonatomic, strong) BaseObjectB *objB;
// 获取数据
- (void)handle;
@end
#import "BaseObjectA.h"
@implementation BaseObjectA
/*
A1 --> B1、B2、B3 3种
A2 --> B1、B2、B3 3种
A3 --> B1、B2、B3 3种
*/
- (void)handle
{
// override to subclass
[self.objB fetchData];
}
@end
A的3个具体的子类都是类似这样的,实现handle
方法,通过调用[super handle];
来调用[self.objB fetchData];
获取具体数据,可以完全获取数据之前和之后的不同操作。
#import "BaseObjectA.h"
@interface ObjectA2 : BaseObjectA
@end
#import "ObjectA2.h"
@implementation ObjectA2
- (void)handle
{
// before 业务逻辑操作
[super handle];
// after 业务逻辑操作
}
@end
桥接模式搭建好了,然后就是具体的使用,即创建一个具体的ClassA
和ClassB
,并将ClassB
作为ClassA
的成员变量,最后调用ClassA
的处理数据方法即可,ClassA
和ClassB
总共有9种组合方式:
#import <Foundation/Foundation.h>
@interface BridgeDemo : NSObject
- (void)fetch;
@end
#import "BridgeDemo.h"
#import "BaseObjectA.h"
#import "BaseObjectB.h"
#import "ObjectA1.h"
#import "ObjectA2.h"
#import "ObjectA3.h"
#import "ObjectB1.h"
#import "ObjectB2.h"
#import "ObjectB3.h"
@interface BridgeDemo()
@property (nonatomic, strong) BaseObjectA *objA;
@end
@implementation BridgeDemo
/*
根据实际业务判断使用那套具体数据
A1 --> B1、B2、B3 3种
A2 --> B1、B2、B3 3种
A3 --> B1、B2、B3 3种
*/
- (void)fetch
{
// 创建一个具体的ClassA
_objA = [[ObjectA1 alloc] init];
// 创建一个具体的ClassB
BaseObjectB *b1 = [[ObjectB1 alloc] init];
// 将一个具体的ClassB1 指定给抽象的ClassB
_objA.objB = b1;
// 获取并处理数据
[_objA handle];
}
@end
b、Demo演示:牵牛花和蜜蜂
protocol AbstractInsect
{
func bloomImp()
}
class Butterfly: AbstractInsect
{
func bloomImp()
{
print("蝴蝶来了")
}
}
class Bee: AbstractInsect
{
func bloomImp()
{
print("蜜蜂来了")
}
}
protocol AbstractFlower
{
var insect: AbstractInsect? { get }
func bloom()
}
class QianniuHua: AbstractFlower
{
var insect: AbstractInsect?
func bloom()
{
print("牵牛花开了")
insect?.bloomImp()
}
}
class MudanHua: AbstractFlower
{
var insect: AbstractInsect?
func bloom()
{
print("牡丹花开了")
insect?.bloomImp()
}
}
class Bridage
{
func bridage()
{
let qianniu = QianniuHua.init()
qianniu.insect = Bee.init()
qianniu.bloom()
let mudan = MudanHua.init()
mudan.insect = Butterfly.init()
mudan.bloom()
}
}
3、组合模式
a、概念
将互相关联的对象合成为树结构,以表现部分-全部的层次结构,优点是节点可以自由增加,且调用节点方便。
b、Demo演示
class Composite: NSObject
{
var subComposites: NSMutableArray = {NSMutableArray()}()
var parentComposite: Composite?
func addComposite(comp: Composite)
{
subComposites.add(comp)
comp.parentComposite = self
}
func removeCompositeAtIndex(index:Int)
{
subComposites.remove(index)
}
func removeComposite(comp: Composite)
{
subComposites.remove(comp)
}
func removeFromParent()
{
if (self.parentComposite != nil)
{
self.parentComposite?.removeComposite(comp: self)
}
}
}
4、装饰模式
a、概念
在进行功能扩展时,装饰是子类化之外的一种灵活的备选方法。和子类化一样,采纳装饰模式可以加入新的行为,而又不必修改已有的代码。装饰模式表达了这样的设计原则:类应该接纳扩展,但避免修改。
b、系统使用
Cocoa
在实现某些类时用到了装饰模式,包括NSAttributedString
、NSScrollView
、和NSTableView
。后面两个类是复合视图的例子,它们将其它一些视图类的对象组合在一起,然后协调它们之间的交互。
通过类别实现装饰模式。类别是Objective-C
的特性,它可以添加类的行为,而不用进行子类化,通过类别添加的方法不会影响类原来的方法,类别也成为类的一部分,并可由其子类继承。
5、外观模式
a、概念
为子系统中的一组接口提供统一的接口,通过减少复杂度和隐藏子系统之间的通讯和依赖性,使子系统更加易于使用。在生活中很多地方也用到外观模式,比如购买基金,我们从基金机构那里购买基金,然后他们帮我们管理我们的基金,去操作和运行,我们只管购买和卖出就行了,而不用去管他们内部的操作。
b、系统使用
NSImage
类为装载和使用基于位图(比如JPEG
、PNG
、或者TIFF
格式)或向量(EPS
或PDF
格式)的图像提供统一的接口。NSImage
可以为同一个图像保持多个表示,不同的表示对应于不同类型的NSImageRep
对象。NSImage
可以自动选择适合于特定数据类型和显示设备的表示。同时,它隐藏了图像操作和选择的细节,使客户代码可以交替使用很多不同的表示。
c、Demo演示
//交易
protocol Deal
{
var dealName: String {get}
mutating func shell()
mutating func buy()
}
extension Deal
{
mutating func shell()
{
print("\(dealName)卖出")
}
mutating func buy()
{
print("\(dealName)买入")
}
}
//股票类
class Stock1: Deal
{
var dealName: String
{
return "股票一"
}
}
class Stock2: Deal
{
var dealName: String
{
return "股票二"
}
}
class Stock3: Deal
{
var dealName: String
{
return "股票三"
}
}
class NationalDebt: Deal
{
var dealName: String
{
return "国债"
}
}
class Realty: Deal
{
var dealName: String
{
return "房地产"
}
}
// 基金类
class Fund
{
var gu1 = Stock1()
var gu2 = Stock2()
var gu3 = Stock3()
var nd = NationalDebt()
var rt = Realty()
public func buyFund()
{
print("买入基金")
gu1.buy()
gu2.buy()
gu3.buy()
nd.buy()
rt.buy()
}
public func shellFund()
{
print("\n卖出基金")
gu1.shell()
gu2.shell()
gu3.shell()
nd.shell()
rt.shell()
}
}
//使用
class Appearance
{
func use()
{
let jijin = Fund()
// 基金购买
jijin.buyFund()
// 基金赎回
jijin.shellFund()
}
}
6、享元模式
a、概念
一个应用程序使用了大量的对象,造成很大的存储开销,可以用相对较少的共享对象取代很多组对象。
b、Demo演示
typedef enum
{
kAnemone,
kCosmos,
kGerberas,
kHollyhock,
kJasmine,
kZinnia,
kTotalNumberOfFlowerTypes
} FlowerType;
@implementation ShareModeViewController
#pragma mark - 使用普通模式
- (void)viewDidLoad
{
[super viewDidLoad];
for (int i = 0; i < 100000; I++)
{
@autoreleasepool
{
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
CGRect area = CGRectMake(x, y, size, size);
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
//新建对象
UIImageView *imageview = [self flowerViewWithType:flowerType];
imageview.frame = area;
[self.view addSubview:imageview];
}
}
}
- (UIImageView *)flowerViewWithType:(FlowerType)type
{
UIImageView *flowerView = nil;
UIImage *flowerImage;
switch (type)
{
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.png"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.png"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.png"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.png"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.png"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.png"];
break;
default:
break;
}
flowerView = [[UIImageView alloc]initWithImage:flowerImage];
return flowerView;
}
@end
运行效果如下,可以看到使用普通模式,每次要新建一个flowerView
添加到self.view
视图上,这样做会造成内存占用很大,特别是生成大量的对象的时候。
其实只有6种花放在不同的位置而已,那我们可以利用享元模式的思想,复用这6种花,然后绘制到不同位置,而不是增加对象添加到视图上。
typedef enum
{
kAnemone,
kCosmos,
kGerberas,
kHollyhock,
kJasmine,
kZinnia,
kTotalNumberOfFlowerTypes
} FlowerType;
@interface ShareModeViewController : UIViewController
@end
@interface FlowerView : UIImageView
- (void) drawRect:(CGRect)rect;
@end
@interface FlyweightView : UIView
@property (nonatomic, retain) NSArray *flowerList;
@end
@interface FlowerFactory : NSObject
{
@private NSMutableDictionary *flowerPool_;
}
- (UIImageView *)flowerViewWithType:(FlowerType)type;
@end
#import "ShareModeViewController.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
@implementation FlowerView
- (void) drawRect:(CGRect)rect
{
[self.image drawInRect:rect];
}
@end
@implementation FlyweightView
extern NSString *FlowerObjectKey, *FlowerLocationKey;
- (void)drawRect:(CGRect)rect
{
for (NSDictionary *dic in self.flowerList)
{
NSValue *key = (NSValue *)[dic allKeys][0];
FlowerView *flowerView = (FlowerView *)[dic allValues][0];
CGRect area = [key CGRectValue];
[flowerView drawRect:area];
}
}
@end
@implementation FlowerFactory
- (UIImageView *)flowerViewWithType:(FlowerType)type
{
if (flowerPool_ == nil)
{
flowerPool_ = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowerTypes];
}
UIImageView *flowerView = [flowerPool_ objectForKey:[NSNumber numberWithInt:type]];
if (flowerView == nil)
{
UIImage *flowerImage;
switch (type)
{
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.jpg"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.jpg"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.jpeg"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.jpg"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.jpeg"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.jpg"];
break;
default:
break;
}
flowerView = [[FlowerView alloc]
initWithImage:flowerImage];
[flowerPool_ setObject:flowerView
forKey:[NSNumber numberWithInt:type]];
}
return flowerView;
}
@end
@implementation ShareModeViewController
{
@private NSMutableDictionary *flowerPool_;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// 使用普通模式
// [self useNormalMode];
// 使用享元模式
[self useShareMode];
}
#pragma mark - 使用享元模式
- (void)useShareMode
{
FlowerFactory *factory = [[FlowerFactory alloc] init];
NSMutableArray *flowerList = [[NSMutableArray alloc] initWithCapacity:500];
for (int i = 0; i < 10000; ++i)
{
@autoreleasepool
{
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
//重复利用对象
UIImageView *flowerView = [factory flowerViewWithType:flowerType];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
CGRect area = CGRectMake(x, y, size, size);
NSValue *key = [NSValue valueWithCGRect:area];
//新建对象
NSDictionary *dic = [NSDictionary dictionaryWithObject:flowerView forKey:key];
[flowerList addObject:dic];
}
}
FlyweightView *view = [[FlyweightView alloc] initWithFrame:self.view.bounds];
view.flowerList = flowerList;
self.view = view;
}
@end
运行效果如下,可以看到内存已经降下来了,我们只是生成了对象flowerView
,但是并没有add
到FlyweightView
上,[self.image drawInRect:rect];
使用image
重新绘制了一个新的位置去显示。
7、代理模式
a、概念
协议:定义代理和委托的共同接口(方法)
委托:根据指定的协议,委托代理去完成实现指定接口(方法)
代理:根据指定的协议,实现委托需要实现的接口(方法)
b、Demo演示
protocol Proxy //协议
{
func charge()
}
class A //委托
{
var delegate: Proxy?
func askProxy()
{
delegate?.charge()
}
}
class B: Proxy //代理
{
func charge()
{
print("A委托B充值,B实现了代理方法charge")
}
}
五、行为型模式:类或对象的交互和分配职责
1、职责链模式(Chain of Responsibility)
a、概念
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。可以解决需求变更顺序的调整问题,只需要调整NextResponder
的指向即可。
b、系统使用
iOS事件的传递和响应就是职责链模式的实现。
Application Kit
框架中包含一个称为响应者链的架构。该链由一系列响应者对象(就是从NSResponder
继承下来的对象)组成,事件(比如鼠标点击)或者动作消息沿着链进行传递并(通常情况下)最终被处理。如果给定的响应者对象不处理特定的消息,就将消息传递给链中的下一个响应者。响应者在链中的顺序通常由视图的层次结构来决定,从层次较低的响应者对象向层次较高的对象传递,顶点是管理视图层次结构的窗口对象,窗口对象的委托对象,或者全局的应用程序对象。
c、Demo演示
代码实现中核心是把类自身作为其成员变量持有,代表的是下一个响应对象,这样就可以调用其自身的响应方法看下一个对象能否处理:
#import <Foundation/Foundation.h>
@class BusinessObject;
typedef void(^CompletionBlock)(BOOL handled);
typedef void(^ResultBlock)(BusinessObject *handler, BOOL handled);
@interface BusinessObject : NSObject
// 下一个响应者(响应链构成的关键)
@property (nonatomic, strong) BusinessObject *nextBusiness;
// 响应者的处理方法
- (void)handle:(ResultBlock)result;
// 各个业务在该方法当中做实际业务处理
- (void)handleBusiness:(CompletionBlock)completion;
@end
直到找到能处理的响应者,否则返回处理对象为nil
和是否处理为NO
:
#import "BusinessObject.h"
@implementation BusinessObject
// 责任链入口方法
- (void)handle:(ResultBlock)result
{
CompletionBlock completion = ^(BOOL handled){
// 当前业务处理掉了,上抛结果
if (handled)
{
result(self, handled);
}
else
{
// 沿着责任链,指派给下一个业务处理
if (self.nextBusiness)
{
[self.nextBusiness handle:result];
}
else
{
// 没有业务处理, 上抛
result(nil, NO);
}
}
};
// 当前业务进行处理
[self handleBusiness:completion];
}
- (void)handleBusiness:(CompletionBlock)completion
{
/*
业务逻辑处理
如网络请求、本地照片查询等
*/
}
@end
Swift
版本的代码示例如下:
class DutyHandle : NSObject
{
/// 下一个
var next : DutyHandle?
/// 处理请求操作
func handleRequest(str:String)
{
/// 如果可以则直接处理
if (self.canDealWithRequest(str: str))
{
print(str)
}
else
{
/// 否则如果有下一个,则下一个进行处理判断
if ((next) != nil)
{
next?.handleRequest(str: str)
}
}
}
/// 判断能否处理请求
func canDealWithRequest(str:String) -> Bool
{
return false
}
}
2、命令模式
a、概念
命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
上面👆那是人说的话吗?鬼才看得懂。简单来说就如同看电视,遥控器是命令管理者,遥控器按钮每次按下都是创建一个命令发给接收器。 每个电视都有一个一个接收器。接受器 和电视关联并且可以对电视做部分的操作。
命令模式角色一:接收者->Receiver
(接收器)
角色二:命令接口->CommandProtocol(协议)
角色三:具体命令->ConcrateCommand
通过执行 CommandProtocol
来处理
角色四:请求者->Invoker(遥控器)负责生成命令rollBack
操作
角色五:客户类->Client
(电视机对应 viewController.view
)
b、系统使用
命令模式将发出请求的对象和接收及执行请求的对象区分开来。
在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。简单来说,就是解耦请求发送者与请求接收者,例如苹果的Target-Action
调用机制已经实现了命令模式。
调用对象
NSInvocation
类的实例用于封装Objective-C
消息。一个调用对象中含有一个目标对象、一个方法选择器、以及方法参数。您可以动态地改变调用对象中消息的目标及其参数,一旦消息被执行,您就可以从该对象得到返回值。通过一个调用对象可以多次调用目标或参数不同的消息。
创建NSInvocation
对象需要使用NSMethodSignature
对象,该对象负责封装与方法参数和返回值有关系的信息。NSMethodSignature
对象的创建又需要用到一个方法选择器NSInvocation
。NSInvocation
的实现还用到Objective-C
运行时的一些函数。
目标-动作
目标-动作机制使控件对象——也就是像按键或文本输入框这样的对象——可以将消息发送给另一个可以对消息进行解释并将它处理为具体应用程序指令的对象。接收对象,或者说是目标,通常是一个定制的控制器对象。消息——也被称为动作消息——由一个选择器来确定,选择器是一个方法的唯一运行时标识。典型情况下,控件拥有的单元对象会对目标和动作进行封装,以便在用户点击或激活控件时发送消息。目标-动作机制之所以能够基于选择器(而不是方法签名),是因为Cocoa
规定动作方法的签名和选择器名称总是一样的。
当您用Interface Builder
构建程序的用户界面时,可以对控件的动作和目标进行设置。您因此可以让控件具有定制的行为,而又不必为控件本身书写任何的代码。动作选择器和目标连接被归档在nib
文件中,并在nib
文件被解档时复活。您也可以通过向控件或它的单元对象发送setTarget:
和setAction:消
息来动态地改变目标和动作。
目标-动作机制经常用于通知定制控制器对象将数据从用户界面传递给模型对象,或者将模型对象的数据显示出来。Cocoa
绑定技术则可以避免这种用法。
Demo演示
首先是建立命令类,提供了执行、取消、和完成命令的方法,这只是一个抽象类,子类重载提供具体命令实现:
#import <Foundation/Foundation.h>
@class Command;
typedef void(^CommandCompletionCallBack)(Command* cmd);
@interface Command : NSObject
@property (nonatomic, copy) CommandCompletionCallBack completion;
- (void)execute;
- (void)cancel;
- (void)done;
@end
其对应实现文件如下,实现了执行、完成、取消方法:
#import "Command.h"
#import "CommandManager.h"
@implementation Command
- (void)execute
{
//override to subclass;
[self done];
}
- (void)cancel
{
self.completion = nil;
}
- (void)done
{
dispatch_async(dispatch_get_main_queue(), ^{
if (_completion) {
_completion(self);
}
//释放
self.completion = nil;
[[CommandManager sharedInstance].arrayCommands removeObject:self];
});
}
@end
使用该命令类的管理者类,以单例方式呈现,用数组容纳了一系列的命令组合,提供执行具体某个命令和取消具体某个命令的方法:
#import <Foundation/Foundation.h>
#import "Command.h"
@interface CommandManager : NSObject
// 命令管理容器
@property (nonatomic, strong) NSMutableArray <Command *> *arrayCommands;
// 命令管理者以单例方式呈现
+ (instancetype)sharedInstance;
// 执行命令
+ (void)executeCommand:(Command *)cmd completion:(CommandCompletionCallBack)completion;
// 取消命令
+ (void)cancelCommand:(Command *)cmd;
@end
管理者类的具体实现文件如下,通过调用命令类中每个命令的执行和取消方法,实现了管理者类中的执行和取消方法:
#import "CommandManager.h"
@implementation CommandManager
// 命令管理者以单例方式呈现
+ (instancetype)sharedInstance
{
static CommandManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
// 【必不可少】
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
// 【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
return self;
}
// 初始化方法
- (id)init
{
self = [super init];
if (self) {
// 初始化命令容器
_arrayCommands = [NSMutableArray array];
}
return self;
}
+ (void)executeCommand:(Command *)cmd completion:(CommandCompletionCallBack)completion
{
if (cmd) {
// 如果命令正在执行不做处理,否则添加并执行命令
if (![self _isExecutingCommand:cmd]) {
// 添加到命令容器当中
[[[self sharedInstance] arrayCommands] addObject:cmd];
// 设置命令执行完成的回调
cmd.completion = completion;
//执行命令
[cmd execute];
}
}
}
// 取消命令
+ (void)cancelCommand:(Command *)cmd
{
if (cmd) {
// 从命令容器当中移除
[[[self sharedInstance] arrayCommands] removeObject:cmd];
// 取消命令执行
[cmd cancel];
}
}
// 判断当前命令是否正在执行
+ (BOOL)_isExecutingCommand:(Command *)cmd
{
if (cmd) {
NSArray *cmds = [[self sharedInstance] arrayCommands];
for (Command *aCmd in cmds) {
// 当前命令正在执行
if (cmd == aCmd) {
return YES;
}
}
}
return NO;
}
@end
3、解释器模式
class Explain: NSObject
{
func add(a: Double, b: Double) -> Double
{
return a + b
}
func multiply(a: Double, b: Double) -> Double
{
return a * b
}
}
4、迭代器模式(Iterator)
a、概念
这种模式提供一种顺序访问聚合对象(也就是一个集合)中的元素,而又不必暴露潜在表示的方法。迭代器模式将访问和遍历集合元素的责任从集合对象转移到迭代器对象。迭代器定义一个访问集合元素的接口,并对当前元素进行跟踪。不同的迭代器可以执行不同的遍历策略。
b、系统使用
iOS的Block
迭代、数组迭代都是迭代器模式的典型实现。
Foundation
框架中的NSEnumerator
类实现了迭代器模式。NSEnumerator
抽象类的私有具体子类返回的枚举器对象可以顺序遍历不同类型的集合——数组、集合、字典(值和键)——并将集合中的对象返回给开发者。
NSDirectoryEnumerator
是一个不紧密相关的类,它的实例可以递归地枚举文件系统中目录的内容。
像NSArray
、NSSet
、和NSDictionary
这样的集合类都包含相应的方法,可以返回与集合的类型相适用的枚举器。所有的枚举器的工作方式都一样。您可以在循环中向枚举器发送nextObject
消息,如果该消息返回nil
,而不是集合中的下一个对象,则退出循环。
c、Demo演示
实现普通迭代器
/// 普通迭代器
class EnumIterator: NSObject
{
private(set) lazy var allObjects = NSArray()
private lazy var index = 0
/// 初始化
init(allObjects : NSArray)
{
super.init()
self.allObjects = allObjects
}
/// 下个元素
func nextObject() -> Any
{
if index >= allObjects.count
{
return NSNull()
}
let object = allObjects[index]
index += 1
return object
}
}
实现栈的迭代
/// 堆栈迭代器
class StackIterator: NSObject
{
private lazy var stack = NSMutableArray()
///压入新元素
func push(object :Any)
{
stack.add(object)
}
///弹出顶部元素
func pop() -> Any
{
let object = readStackRear
if empty()
{
stack.remove(object)
}
return object
}
/// 读取栈尾
func readStackRear() -> Any {
if empty()
{
return NSNull()
}
let object = stack.lastObject
return object!
}
///元素个数
func count() -> Int
{
return stack.count
}
///空栈
func empty() -> Bool
{
return stack.count == 0
}
}
实现队列的迭代
/// 队列迭代器
class QueueIterator: NSObject
{
private lazy var quene = NSMutableArray()
/// 入队
func inQuene(object :Any)
{
quene.add(object)
}
/// 出队
func outQuene() -> Any
{
let object = readQueneHead()
if empty() == false
{
quene.remove(object)
}
return object
}
/// 读取队首
func readQueneHead() -> Any
{
if empty()
{
return NSNull()
}
let object = quene.firstObject
return object!
}
/// 元素个数
func count() -> Int
{
return quene.count
}
/// 空队
func empty() -> Bool
{
return quene.count == 0
}
}
5、中介者模式
a、概念
中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
b、系统使用
中介者模式很好的处理了业务中组件化方案的强耦合的问题,我们iOS当中组件化的实现都是基于中介者的模式的。其中的Mediator
起到至关重要的作用,Mediator
就是我们封装的组件化的框架。
c、Demo演示
使用中介之前如下图
使用中介之后如下图
6、备忘录模式(Memento)
a、概念
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。在iOS常用的实现备忘录模式的模块有归档、序列化、CoreData
等。
b、归档
归档将一个程序中的对象以及对象的属性(包括属性和关系)存储到档案上,使之可以保存到文件系统中,或者在不同的处理器和网络间传递。档案将程序的对象图保存为独立于架构的字节流,对象的标识和对象之间的关系都会被保留。由于对象的类型和它的数据一起被存储,从归档的字节流解码出来的对象会被正常实例化,实例化所用的类与原来编码的类相同。
通常情况下,您希望将程序中需要保存状态的对象归档。模型对象几乎总是属于这个范畴。您通过编码将对象写入到档案中,而通过解码将对象从档案中读取出来。通过NSCoder
对象可以执行编解码操作,在编解码过程中最好使用键化的归档技术(需要调用NSKeyedArchiver
和NSKeyedUnarchiver
类的方法)。被编解码的对象必须遵循NSCoding
协议,该协议的方法在归档过程中会被调用。
c、属性列表的序列化
属性列表是一个简单的、具有一定结构的对象图序列,它仅使用下面这些类的对象:NSDictionary
、NSArray
、NSString
、NSData
、NSDate
、和NSNumber
,这些对象通常也被称为属性列表对象。Cocoa
中有几个框架类提供了序列化属性列表对象,以及定义录写对象内容及其层次关系的特殊数据流格式的方法。
NSPropertyListSerialization
类就提供了将属性列表对象序列化为XML或其它优化的二进制格式的类方法。
如果对象图中包含的是简单对象,则在捕捉和外部化对象及其状态时,属性列表序列化是一种灵活的、可移植的、而又非常适当的工具。然而,这种形式的序列化有它的限制,它不保留对象的全部类标识,而只保留一些一般的类型(数组、字典、字符串、等等)。这样,从属性列表恢复出来的对象可能和原来的类不同,特别是当对象的可变性可能发生变化时,这就会带来问题。属性列表序列化也不跟踪在同一对象中被多次引用的对象,这可能导致反向序列化时产生多个实例,而在原来的对象图中却只有一个实例。
d、Core Data
Core Data
是一个管理对象图,并使其留存的框架和架构。正是第二种能力——对象的留存能力——使Core Data
成为备忘录模式的一种适配形式。
在Core Data
架构中,中心的对象称为被管理对象上下文,负责管理应用程序对象图中的模型对象。在被管理对象上下文下面是该对象图的持久栈,也就是一个框架对象的集合,负责协调模型对象和外部数据存储,比如XML
文件或关系数据库。持久栈对象负责建立存储中的数据和被管理对象上下文中的对象之间的映射关系,在有多个数据存储的时候,持久栈对象将这些存储表现为被管理对象上下文中的一个聚合存储。
Core Data
的设计也在很大程度上受到模型-视图-控制器以及对象建模模式的影响。
7、观察者模式(Observer)
a、概念
一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。iOS中的KVO
、NSNotication
都是观察者模式。
b、通知
Cocoa
的通知机制实现了一对多的消息广播,其实现方式符合观察者模式。在这种机制中,程序里的对象将自己或其它对象添加到一或多个通知的观察者列表中,每个通知由一个全局的字符串(即通知的名称)标识。希望向其它对象发送通知的对象——也就是被观察的对象——负责创建一个通知对象,并将它发送到通知中心。通知中心则负责确定通知的观察者,并通过消息将通知发送给观察者对象。通知消息激活的方法必须遵循特定的参数签名格式,方法的参数就是通知对象,包含通知的名称、被观察的对象、以及一个含有补充信息的字典。
使用通知可以有很多原因。例如,借助通知机制,您可以根据程序中其它地方发生的事件改变用户界面元素显示信息的方式。或者,您可以用通知来保证文档中的对象在文档窗口关闭之前保存自己的状态。通知的一般目的是将事件通知给其它程序对象,使它们可以做出恰当的反应。
但是,通知的接收对象只能在事件发生之后进行反应,这和委托机制有显著的不同。被委托的对象有机会拒绝或修改委托对象希望进行的操作。另一方面,观察者对象不能直接影响一个即将发生的操作。
与通知有关的类有NSNotification
(通知对象)、NSNotificationCenter
(用于发送通知和添加观察者)、NSNotificationQueue
(负责管理通知队列)、和NSDistributedNotificationCenter
。很多Cocoa
框架都发布和发送通知,其它对象都可以成为这些通知的观察者。
c、KVO
键-值观察是使对象可以在其它对象的具体属性发生变化时得到通知的一种机制。它基于名为NSKeyValueObserving
的非正式协议。被观察的属性可以是简单的属性、一对一的关系、或者一对多的关系。键-值观察在模型-视图-控制器模式中特别重要,因为它使视图对象-通过控制器层-可以观察到模型对象的变化,因此是Cocoa
绑定技术的必要组件。Cocoa
为很多NSKeyValueObserving
方法提供了缺省的“自动”实现,使所有遵循该协议的对象都具有属性-观察的能力。
键-值观察和通告机制类似,但在一些重要的方面也有不同。在键-值观察中,没有为所有观察者提供变化通告的中心对象,而是将变化的通告直接传递给观察对象。还有,键-值观察直接和具体的对象属性相关联。而通告机制则更广泛地关注事件。
需要遵循KVC
的方法(也就是存取方法),键-值编码是一个与自动获取和设置对象属性值有关的机制。
您可以禁用自动的观察者通告,并通过NSKeyValueObserving
非正式协议及相关范畴中的方法实现手工通告。
8、状态模式
a、概念
允许一个对象在其内部状态改变时改变它的行为。
b、Demo演示
protocol State
{
func handle()
}
class ConcreteStateA: State
{
func handle()
{
print("状态A")
}
}
class ConcreteStateB: State
{
func handle()
{
print("状态B")
}
}
class Context
{
var state: State?
func request()
{
state?.handle()
}
}
9、策略模式
a、概念
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
b、Demo演示
// 接口
protocol ContextInterface
{
func quickSort()
func insertSort()
}
class SortContext: ContextInterface
{
var quickStrategy: QuickSortStrategy?
var insertStrategy: InsertSortStrategy?
func quickSort()
{
quickStrategy?.sort()
}
func insertSort()
{
insertStrategy?.sort()
}
}
// 排序策略
protocol Strategy
{
func sort()
}
// 快排策略
class QuickSortStrategy: Strategy
{
func sort()
{
print("快排策略")
}
}
// 插排策略
class InsertSortStrategy: Strategy
{
func sort()
{
print("插排策略")
}
}
10、模板方法模式
a、概念
Cocoa
类的编程接口通常包括一些需要被子类重载的方法。
b、Demo演示
class AbstractClass
{
func templateMethod()
{
print("执行当前逻辑...")
//推迟留给子类处理逻辑...
primitiveOperation1()
primitiveOperation2()
}
func primitiveOperation1()
{
assert(false, "此方法需要继承")
}
func primitiveOperation2()
{
assert(false, "此方法需要继承")
}
}
class ConcreteClass: AbstractClass
{
override func primitiveOperation1()
{
print("执行operation1逻辑")
}
override func primitiveOperation2()
{
print("执行operation2逻辑")
}
}
class TemplateClient
{
var operationC: AbstractClass?
func operation()
{
//执行模版方法
operationC?.templateMethod()
}
}
11、访问者模式
访问元素增加 acceptVisitor(visitor)
方法(接收访问者),访问者增加visitA(A)
、visitB(B)
、visitC(C)
。
通过访问元素调用访问者中的事件,在访问元素的 acceptVisitor
的实现方法中调用 [visitor visitX:self]
执行方法。
class VisitorClient: NSObject
{
func begin()
{
let visit1 = VisitorA()
let visit2 = VisitorB()
let element1 = VisitElementA()
let element2 = VisitElementA()
let element3 = VisitElementA()
let element4 = VisitElementB()
let element5 = VisitElementB()
let array = [element1,element2,element3,element4,element5]
for element in array
{
let number = arc4random()
if number%2 == 0
{
element.acceptVisit(visit: visit1)
}
else
{
element.acceptVisit(visit: visit2)
}
}
}
}
class Visitor: NSObject
{
/// 访问元素A
func visitA(element :VisitElementA)
{
}
/// 访问元素B
func visitB(element :VisitElementB)
{
}
}
class VisitorA: Visitor
{
override func visitA(element: VisitElementA)
{
NSLog("No1 Visit1 %@", element)
/// 用 element 做某些操作
}
override func visitB(element: VisitElementB)
{
NSLog("No1 Visit2 %@", element)
/// 用 element 做某些操作
}
}
class VisitorB: Visitor
{
override func visitA(element: VisitElementA)
{
NSLog("No2 Visit1 %@", element)
/// 用 element 做某些操作
}
override func visitB(element: VisitElementB)
{
NSLog("No2 Visit2 %@", element)
/// 用 element 做某些操作
}
}
class VisitElement: NSObject
{
func acceptVisit(visit :Visitor)
{
}
}
class VisitElementA: VisitElement
{
override func acceptVisit(visit :Visitor)
{
visit.visitA(element: self)
}
}
class VisitElementB: VisitElement
{
override func acceptVisit(visit :Visitor)
{
visit.visitB(element: self)
}
}
Demo
Demo在我的Github上,欢迎下载。
DesignPatternsDemo
网友评论