今天聊聊iOS中一种常用的消息传递的方式 也就是我们常常会用到的代理设计模式 通过这种方式我们可以来传递一些参数
一、iOS中不同的传递模式
1.通知:在iOS中由通知中心进行消息接收和消息广播 是一种一对多的消息传递方式
2.代理:是一种通用的设计模式 iOS中对代理支持很好 由代理对象、委托者、协议三部分组成
3.block:block是iOS4.0种引入的一种回调方法 可以将回调处理的代码直接写在block代码块中 看起来逻辑清晰 条理清楚
4.target action:通过将对象传递到另一个类中将该对象当作target的方式 来调用该对象的方法 从内存的角度来说跟代理类似
5.kvo:NSObject的Category-NSKeyValueObserving 通过属性监听的方式来监测某个值的变化 当值发生改变的时候调用kvo的回调方法
今天主要先聊聊代理
-------------------------------------我是分割线------------------------------------
一、代理的基本使用方法
代理是一种通用的设计模式 在iOS中对代理设计模式支持的很好 有特定的语法来实现代理模式 oc语言可以通过@Protocol实现协议
代理的三个部分是:
协议:用来指定代理双方可以做什么 必须做什么 可以选择去做什么不去做什么
代理:根据指定的协议 完成委托方需要实现的功能
委托:根据指定的协议 指定代理去完成什么功能
1.1-Protocol概念
在实际应用中通过协议来规定代理双方的行为 协议中的内容一般都是方法列表 也可以定义属性
协议是公共的定义 如果只是某个类使用 我们通常做的就是写在某个类中 如果是多个类都是用同一个协议 创建一个Protocol文件会比较好 在这个文件中定义协议 遵循的协议可以被继承 比如比较常用的UITableView 由于继承自UIScrollView的缘故 所以也将UIScrollViewDelegate继承过来 可以通过代理的方法获取UITableView偏移量等状态参数
协议只能定义公用的一套接口 类似于一个约束代理双方的作用 但是不能提供具体的实现方法 实现方法需要代理对象去实现 协议可以继承其他协议 并且可以继承多个协议 在iOS中对象是不支持多继承的 但是协议是可以多继承的
当前协议继承了三个协议 其他三个协议中的方法列表都会被继承过来协议有两个修饰符@optional和@required 创建一个协议中如果没有声明 默认是@required状态的 这两个修饰符只是约定代理是否强制需要遵守协议 如果@required状态方法代理没有遵守 就会出现黄色警告 总的来说它只是起一个约束的作用 没有其他的功能
值得注意的是 不论是@required还是@optional 在委托方调用代理方法都需要做一个判断 判断代理是否实现当前方法 否则hi导致崩溃
iOS中一个代理可以有很多个委托方 而一个委托方也可以又很多个代理 比如我想喝果汁 指定一个APP定个果汁外卖 还想吃垃圾食品 在指定一个麦当劳等多个代理去完成我想做的事 委托方也可以为多个代理服务
代理对象在很多情况下是可以复用的 可以创建多个代理对象为多个委托方服务
大概思路图代理对象在很多情况下是可以复用的 可以创建多个代理对象为多个委托方服务 下面将会通过代码来解释控制器代理的复用
定义委托类 这里简单实现了一个用户登录功能 将用户登录后的账号密码传递出去 有代理来处理具体登录的细节
当前类是委托类 用户登录后 让代理对象去实现登录的具体细节 委托类不需要知道其中实现的具体细节
定义一个协议类 来定义公共协议 代理方实现具体的登录流程 委托方不需要知道实现的细节 代理实现的流程二、代理实现的流程
在iOS代理的本质就是代理对象内存的传递和操作 我们在委托类设置代理对象后实际上只是用一个id类型的指针将代理对象进行一个弱引用 委托方让代理方执行操作 实际上是在委托类中向这个id类型指针指向的对象发送消息 而这个id类型指针指向的对象 就是代理对象
其实委托方的代理属性本质上就是代理对象自身 设置委托代理就是代理属性指针指向代理对象 相当于代理对象只是在委托方中调用自己的方法 如果方法没有实现就会导致崩溃
而协议只是一种语法 是声明委托方中的代理属性可以调用协议中声明的方法 而协议中方法的实现还是由代理方完成 但是协议方和委托方都不知道代理方有没有完成 也不必知道要怎么完成
三、代理内存管理
有没有过为什么设置代理属性都使用weak呢?
我们定义的指针默认都是__strong类型的 而属性本质上也是一个成员变量和set、get方法构成的
strong类型的指针会造成强引用 必定会影响一个对象的生命周期 这也就回形成循环引用
循环引用会造成最终两个对象都无法正常释放
那么设置为弱引用属性 好处就是在代理对象生命周期存在时 可以正常为我们工作 如果代理对象被释放 委托方和代理对象都不会因为内存释放的问题导致Crash
下面介绍两种方式都是弱引用代理对象
@property (nonatomic,weak)id delegate;
@property (nonatomic,assign)id delegate;
第一种在代理对象被释放后不会导致崩溃 但是第二种会导致崩溃
weak和assign是一种非拥有关系的指针 通过这两种修饰符修饰的指针变量 都不会改变被引用对象的引用计数 但是在一个对象被释放后 weak会自动将指针指向nil 而assign不会
在iOS中 向nil发送消息时不会导致崩溃 所以assign就回导致野指针的错误
错误:unrecognized selector sent to instance
总结:如果修饰代理属性 用weak会比较安全
四、为什么要使用代理对象
随着项目越来越复杂 控制器也会跟着功能的增加变得越来越臃肿 对于这种情况 很多人想到的是MVVM设计模式 但是对于一个大中型的项目来说 动框架这层东西 并没有那么方便
在项目中用到比较多的控件应该就有UITableView 有的页面往往UITableView的处理逻辑很多 导致控制器臃肿 那么通过代理对象的方式给控制器瘦身是一个比较好的方法
如果我们将UITableView的delegate和DataSource单独拿出来 由一个代理对象类进行控制 只将必须控制器处理的逻辑传递给控制器处理
UITableView的数据处理 展示逻辑和简单的逻辑都交互都由代理对象去处理 和控制器相关的逻辑处理传递出来 交由控制器处理 这样控制器的工作会少了很多 并且耦合度也会大大降低 (耦合度:是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系 包括控制关系、调用关系、数据传递关系。模块间联系越多 其耦合性越强 同时表明其独立性越差。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准 划分模块的一个准则就是高内聚低耦合。)
下面是解释一下用代理的思想为控制器瘦身
在.h文件声明代理对象的协议 代理对象在.m文件中的实现 外界控制器的调用以上就是 代理实现为控制器瘦身
五、非正式协议
在iOS2.0之前还没有引用@protocol正式协议之前 实现协议的功能主要是通过给NSObject添加Category的方式 这种通过Category的方式 相对于iOS2.0之后引入的@protocol 就叫非正式协议
正如上面所说的 非正式协议一般都是NSObject的Category 所以所有基于NSObject的子类 都接受了所定义的非正式协议 对于@protocol来说编译器会在编译期间检查语法错误 而非正式协议则不会检查是否实现
非正式协议没有@protocol的@optional和@required之分 和@protocol一样在调用 需要判断方法是否实现
非正式协议本质上就Category
例如CALayerDelegate 就是非正式协议的一种实现
@protocol CALayerDelegate<NSObject>
- (void)displayLayer:(CALayer *)layer;
/* If defined, called by the default implementation of -drawInContext: */
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
/* If defined, called by the default implementation of the -display method. * Allows the delegate to configure any layer state affecting contents prior * to -drawLayer:InContext: such as `contentsFormat' and `opaque'. It will not * be called if the delegate implements -displayLayer. */
- (void)layerWillDraw:(CALayer *)layer CA_AVAILABLE_STARTING (10.12, 10.0, 10.0, 3.0);
/* Called by the default -layoutSublayers implementation before the layout * manager is checked. Note that if the delegate method is invoked, the * layout manager will be ignored. */
- (void)layoutSublayersOfLayer:(CALayer *)layer;
/* If defined, called by the default implementation of the * -actionForKey: method. Should return an object implementating the * CAAction protocol. May return 'nil' if the delegate doesn't specify * a behavior for the current event. Returning the null object (i.e. * '[NSNull null]') explicitly forces no further search. (I.e. the * +defaultActionForKey: method will not be called.) */
- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;
@end
六、代理和block
我在上一篇介绍了block 在iOS中回调的方法有很多 而代理和block功能更加相似 都是直接进行回调 那么我们应该怎么选择呢
其实这两种消息回调的方式没有哪个更好 哪个不好这么说
应该以什么时候用什么来区分
当有多个消息传递 应该使用delegate 再有多个消息传递 应该使用delegate 看起来比较清晰 这时候用block不利于维护
如果想要委托对象调用多个代理的回调 用block会比较好
单例对象最好不要使用delegate 单例对象始终都是同一个对象 如果使用delegate 就会造成delegate属性被重新赋值的问题 最终只有一个对象能正常响应代理方法 这时候比较适合使用block
NSOperationQueue创建了一个新的队列 将最大并发数设置为10 创建一个100的循环例如 在多线程情况下测试单例在block是否正常使用 得出结论:是可行的
但是需要注意的是 在block必要的地方加锁 防止资源抢夺的问题发生
从性能上说 block的性能消耗药略大于delegate 因为block会涉及到栈区向堆区等操作 事件和空间上的消耗都会大于代理
代理只是定义了一个方法列表在遵守协议对象的objc_protocol_list 中添加一个节点 在运行时向遵守协议对象发送消息即可
以上 待续
网友评论