一、产生背景
1.1 传统面向对象编程(OOP)的弊端/继承的痛点:
继承的痛点:滥用继承的话,后面如果要抽离功能就会牵一发而动全身。即抽离一个功能会导致也要把继承链上涉及的功能都抽出来。显然太麻烦了。
痛点的解决:使用面向协议,将每个单独功能一个协议,复杂功能将协议组合起来即可。如此后续抽离时,抽单独协议和实现类即可(swift中可以将实现写在扩展中,可能不涉及实现类)。
我们将网络库CJNetwork使用面向协议编程后
1、定义协议CQNetworkRequestCompletionClientProtocal.h
1 | NS_ASSUME_NONNULL_BEGIN |
2、使用CQNetworkRequestCompletionClientProtocal
1 | NS_ASSUME_NONNULL_BEGIN |
1.2 什么是面向协议编程(POP)?
这个问题,我感觉比较明确的定义就是2015年Apple WWDC中说的一句话很直了的解释了:
1 | Don't start with a class. |
即在程序设计中,不要以一个类开始设计,应该从一个协议开始,应抛弃之前OOP的对象设计理念,设计协议,这样不同的继承链之间也可以使用同一个协议。可以将协议看做一个组件,哪里需要哪里继承协议即可,而且协议是可以多继承的,iOS中的类只能单继承,这也是面向协议相对面向对象的一大优势。
1.3 Objective-C 和Swift的面向协议编程区别
OC和swift面向协议编程一个最大区别是OC的 Protocol 没有默认的实现,需要依赖具体的实现类实现协议定义的方法,而Swift2.0开始提供了Protocol + Extension,协议可以再 Extension中提供默认的实现,这样上层调用可以直接调用协议的默认实现。
严谨来说,OC不是一门面向协议编程的语言,因为 Protocol 只提供定义,而不提供实现,所以叫他 面向接口编程 更合适一些。
面向协议和依赖注入
依赖注入要求我们传入的参数遵循协议,这样做的好处是:
- 我们可以在外部注入一个通用类,只要它遵循协议。
- 也可以注入一个可测试的模拟类,便于测试。
参考文章:iOS-面向协议编程(POP)
二、替代继承的方式
参考文章:
1、协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14 // 在屏幕中间弹出
@protocol ShowInCenterProtocol <NSObject>
- (void)show;
@end
// 在屏幕底部弹出
@protocol ShowInBottomProtocol <NSObject>
- (void)show;
@end
@class NormalVoicePlayer : NSObject <ShowInCenterProtocol>
@end
2、用组合替代继承
eg1:想要为一个视图做一个show操作。(设想这个show方法后面可能随时会变,比如show的位置,或者动画)
AlertUtil:使用由AlertView/PopupView+PopupAction来组合,而不使用继承AlertView+为其添加show方法。
eg2:常见的举例还有,文本框TextField的根据历史记录填充、根据@邮箱填充。
3、类别category
1 | @interface NSArray (OBJExtras) |
三、使用面向协议为视图添加抖动功能
举例:为UIImageView和UIButton等添加视图的抖动
1、方法1:添加Category方式
如果你写过 Objective-C, 你很可能会把 shake() 写到一个 UIView 的分类(Category) 中(也就是 Swift 中的拓展 (extension)):
1 | // UIViewExtension.swift |
2、方法2:面向协议方式
但在swift里,我们除了对UIView添加extension外,其实还有另一种更好的方式:创建面向协议的视图
1 | class FoodImageView: UIImageView, Shakeable { |
附上述面向协议的视图里所对的Shakeable 协议如下:
1 | // Shakeable.swift |
参考文章:
四、使用面向协议处理reuseIdentifier
tableView的cell注册的基本代码如下
1 | let foodCellNib = UINib(NibName: "FoodTableViewCell", bundle: nil) |
改进1:FoodTableViewCell字符串改成由类获得
1 | let foodCellNib = UINib(NibName: String(FoodTableViewCell), bundle: nil) |
改进2:
1 | protocol ReusableView: class {} |
附:面向对象(OOP)
面向对象的三个特性:封装、继承和多态。
所谓封装,也就是把客观事物封装成抽象的类;
所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。(或者说是不同对象以自己的方式响应相同的消息的能力叫做多态。)
继承的问题:如果架构工程师写父类,业务工程师实现子类。那么业务工程师很可能不清楚:哪些方法需要被覆盖重载,哪些不需要。如果子类没有覆重方法,而父类提供的只是空方法,就很容易出问题。如果子类在覆重的时候引入了其他不相关逻辑,那么子类对象就显得不够单纯,角色复杂了。
多态面临的四个问题:
父类有部分public的方法是不需要,也不允许子类覆重。
答:公司内部可以规定:
不允许覆盖重载父类中的方法、子类需要实现接口协议中的方法。父类有一些特别的方法是必须要子类去覆重的,在父类的方法其实是个空方法。
父类有一些方法即便被覆重,父类原方法还是要执行的。
父类有一些方法是可选覆重的,一旦覆重,则以子类为准。