iOS 编写高质量Objective-C代码(四)
级别: ★☆☆☆☆
标签:「OC分类」「Category」
作者: MrLiuQ
审校: Xs·H
前言:
这几篇文章是小编在钻研《Effective Objective-C 2.0》的知识产出,其中包含作者和小编的观点,以及小编整理的少量demo。希望能帮助大家以简洁的文字快速领悟原作者的精华。
在这里,QiShare团队向原作者Matt Galloway表达诚挚的敬意。
文章目录如下:
iOS 编写高质量Objective-C代码(一)
iOS 编写高质量Objective-C代码(二)
iOS 编写高质量Objective-C代码(三)
iOS 编写高质量Objective-C代码(四)
iOS 编写高质量Objective-C代码(五)
本篇的主题是:协议与分类(protocol
& category
)
先简单详情一下今天的主角:协议 与 分类
- 协议(
protocol
):OC中的协议与Java里的接口(interface
)相似,OC不支持多继承,但是可以通过协议来实现委托模式。 - 分类(
category
):分类可以为既有类增加新的功能。分类是把“双刃剑”,使用得好可以发挥OC的高动态性
,使用的不好则会留下很多坑。而本文就是对category
的少量研究。
一、通过委托与数据源协议进行对象间通信
委托模式(又称代理商):某对象将一类方法(任务)交给另一个对象帮忙完成。
相似于:老板把一类任务交给某个leader去完成。
举例来说,当某对象要从另一个对象获取数据时,即可以用委托模式。通过实现协议来获取数据,这样的协议一般被称为“数据源协议”(Data Source Protocol
)。相似于UITableView
的UITableViewDataSource
。
再举例来说,当一个对象要有少量事件响应时,即可以用委托模式。通过实现一个协议(一般称为delegate
),让代理商对象帮助该对象解决事件响应。相似于UITableView
的UITableViewDelegate
。
请看图解:
- 好处:通过协议来降低代码的耦合性。(解耦)
必要的时候协议还可以替代继承。由于遵守同一个协议的类可以有很多,不肯定要继承。
百说不如一Demo:这是小编整理的关于Button动画的例子
- QiCircleAnimationView.h:
@class QiAnimationButton;@protocol QiAnimationButtonDelegate <NSObject>@optional- (void)animationButton:(QiAnimationButton *)button willStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;- (void)animationButton:(QiAnimationButton *)button didStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;- (void)animationButton:(QiAnimationButton *)button willStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;- (void)animationButton:(QiAnimationButton *)button didStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;- (void)animationButton:(QiAnimationButton *)button didRevisedAnimationWithCircleView:(QiCircleAnimationView *)circleView;@end@interface QiAnimationButton : UIButton@property (nonatomic, weak) id<QiAnimationButtonDelegate> delegate;- (void)startAnimation;//!< 开始动画- (void)stopAnimation;//!< 结束动画@end
- QiAnimationButton.m中:
即可以通过这样的方式回调
if ([self.delegate respondsToSelector:@selector(animationButton:willStartAnimationWithCircleView:)]) { [self.delegate animationButton:self willStartAnimationWithCircleView:_circleView];}/* .... */if ([self.delegate respondsToSelector:@selector(animationButton:didStartAnimationWithCircleView:)]) { [self.delegate animationButton:self didStartAnimationWithCircleView:_circleView];}
这种形式的例子很多,所以,就会写出很多相似于这样格式的代码:
if ([self.delegate respondsToSelector:@selector(xxxFunction)]) { [self.delegate xxxFunction];}
解释:由于该协议内的方法是@optional
修饰的,所以遵守协议的Class
可以选择性地
实现协议里的方法。因而,代理商对象在调使用回调方法时,需要先检查一下Class
有没有实现该协议里的方法?假如实现了,就回调;假如没有实现,就接着往下走。
考虑性能优化:
大家设想一下,这样一个场景:回调方法被频繁回调。也就是说,某回调方法被调使用的频率很高。那么每调使用一次回调方法都要去查一下
Class
有没有实现该回调方法。所以性能上会变差。
处理方案:实现一个含有位段的结构体,把委托对象是否响应某个协议方法的信息缓存起来,以优化程序执行效率。
百说不如一Demo,下面请看小编整理的Demo~
- 公告一个结构体
DelegateFlags
:
@interface QiAnimationButton () { struct DelegateFlags { int doWillStartAnimation : 1; int doDidStartAnimation : 1; int doWillStopAnimation : 1; int doDidStopAnimation : 1; int doDidRevisedAnimation : 1; };}
- 公告一个属性:
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
- 重写
delegate
的set
方法:将能否实现该协议方法的信息缓存起来
- (void)setDelegate:(id<QiAnimationButtonDelegate>)delegate { _delegate = delegate; _delegateFlags.doWillStartAnimation = [delegate respondsToSelector:@selector(animationButton:willStartAnimationWithCircleView:)]; _delegateFlags.doDidStartAnimation = [delegate respondsToSelector:@selector(animationButton:didStartAnimationWithCircleView:)]; _delegateFlags.doWillStopAnimation = [delegate respondsToSelector:@selector(animationButton:willStopAnimationWithCircleView:)]; _delegateFlags.doDidStopAnimation = [delegate respondsToSelector:@selector(animationButton:didStopAnimationWithCircleView:)]; _delegateFlags.doDidRevisedAnimation = [delegate respondsToSelector:@selector(animationButton:didRevisedAnimationWithCircleView:)];}
- 直接通过
_delegateFlags
缓存的值判断是否回调
if (_delegateFlags.doWillStartAnimation) { [self.delegate animationButton:self willStartAnimationWithCircleView:_circleView];}/* .... */if (_delegateFlags.doDidStartAnimation) { [self.delegate animationButton:self didStartAnimationWithCircleView:_circleView];}
二、把复杂类的实现代码分散到便于管理的数个分类之中
- 用分类机制,把少量很复杂的类“瘦身”,划分成各个易于管理的分类。
- 把私有方法作为一个单独的分类,已隐藏实现细节。
好处:
1. 把复杂的类拆成小块,解耦。易于维护,易于管理。
2. 便于调试:遇到问题能快速定位是哪个分类。
小编看法:视具体情况而定,拆分的同时,也会多出很多文件。假如一个类过于臃肿(比方有几千行代码),可以考虑给他瘦身,拆分成多个分类。
三、总是为第三方分类的名称加前缀
- 分类机制最大的功能:就是为不能修改源码的既有类中增加新的功能。
这时候我们要:
- 在分类类名前,加上专有前缀。
- 在分类方法名前,加上专有前缀。
最大限度上避免重名可能带来的bug,而且这种bug很难排查。
起因在于:分类的方法会直接增加在类中,而分类是在运行期把方法加入主类。这时候,假如出现方法重名,后一个写入的分类方法会把前一个覆盖掉。屡次覆盖的结果总以最后一个分类为准。所以我们要加前缀,尽量避免重名带来的bug。
四、勿在分类中公告属性
不要在分类中公告属性,但可以在类扩展(extension)中公告属性,这样属性就不会暴露在外面。
举个例子:(类扩展)
// QiShare.m@interface QiShare ()/* 属性可以公告在这里 */@end@implementation QiShare/* ... */@end
- 不能在分类中直接公告属性。假如公告了,编译时会报如下警告:
Property 'name' requires method 'setName:' to be defined - use @dynamic or provide a method implementation in this category
解释:分类无法合成相关的实例变量,需要开发者为该属性实现存取方法(get和set)。由于没有生成实例变量,set方法行不通。get方法可以返回固定值。或者者用@dynamic公告(即不会公告实例变量和存取方法)。
- 通过关联对象,为分类增加属性。(介绍见第二篇 – 第5条)
所以,
1. 建议把属性都放在主类中。
2. 不到迫不得已,尽量不要在分类中通过关联对象增加属性。由于关联对象的内存管理问题上很容易出错,用时需要重点提防。
五、用“class-continuation分类”隐藏实现细节
这里的“class-continuation分类” 指的就是 类扩展(extension)。
我们可以把少量私有的属性公告在类扩展里,这样在导入.h文件时,看不到类扩展公告的属性。
目的:把公共接口中向外暴露的内容最小化,隐藏少量属性和实现细节。
这里补充一个小知识点:大家都知道Objective-C,但听说过Objective-C++吗?
Objective-C++是Objective-C和C++的混编,编译时会生成.mm
文件。
这时候会遇到一个问题:由于只有类的.mm
文件才能同时编译OC和C++。所以,当一个类所导入所有文件树中包含C++文件,此类的.m文件就会被编译成.mm
文件。
那么,OC怎样处理呢?使用类扩展
。
举个例子:
#import "OCClass.h"#import "CppClass.cpp"@interface OCClass () { SomeCppClass *_cppClass;}@end@implementation OCClass/* ... */@end
这样,.h
文件中就没有C++代码了,假如只看头文件甚至都不知道底层有C++的代码。其实,我们的系统也是这样做的。比方WebKit、CoreAnimation等,很多底层代码都是通过C++写的。
小结:类扩展的应使用场景
1. 向类中新添加实例变量或者属性
2. 在.h
文件中把属性公告为“只读”,而类的内部又想修改此属性,可以在类扩展中重公告为“可读写”。
3. 私有方法的原型可以公告在类扩展里。
4. 假如不想让外部知道类中遵守了哪些协议,可以在类扩展中遵守协议。
六、通过协议提供匿名对象
- 可以通过协议提供匿名对象,例如:
id<someProtocol> delegate
。delegate对象的类型不限,只需能遵从这个协议的对象都可以。协议里规定了对象所需要实现的方法。 - 用匿名对象来隐藏类型名称和类名。
- 对象只需实现协议里的方法就可(
@optional
修饰的可以选择性实现),其他的实现细节都被隐藏起来了。
最后,特别致谢:《Effective Objective-C 2.0》第四章
关注我们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)
推荐文章:
糖是甜的,你也是: 致 async
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS 编写高质量Objective-C代码(四)