iOS 编写高质量Objective-C代码(四)
作者:QiShare
链接:https://www.jianshu.com/p/1d5377b0325b
前言:
这几篇文章是小编在钻研《Effective Objective-C 2.0》的知识产出,其中包含作者和小编的观点,以及小编整理的一些demo。希望能帮助大家以简洁的文字快速领悟原作者的精华。
在这里,QiShare团队向原作者Matt Galloway表达诚挚的敬意。
文章目录如下:
本篇的主题是:协议与分类(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 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~
1.声明一个结构体DelegateFlags:
@interface QiAnimationButton () {
struct DelegateFlags {
int doWillStartAnimation : 1;
int doDidStartAnimation : 1;
int doWillStopAnimation : 1;
int doDidStopAnimation : 1;
int doDidRevisedAnimation : 1;
};
}
2.声明一个属性:
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
3.重写delegate的set方法:将是否实现该协议方法的信息缓存起来
- (void)setDelegate:(id)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:)];
}
4.直接通过_delegateFlags缓存的值判断能否回调
if (_delegateFlags.doWillStartAnimation) {
[self.delegate animationButton:self willStartAnimationWithCircleView:_circleView];
}
/* .... */
if (_delegateFlags.doDidStartAnimation) {
[self.delegate animationButton:self didStartAnimationWithCircleView:_circleView];
}
二、把复杂类的实现代码分散到便于管理的数个分类之中
使用分类机制,把一些很复杂的类“瘦身”,划分成各个易于管理的分类。
把私有方法作为一个单独的分类,已隐藏实现细节。
好处:
把复杂的类拆成小块,解耦。易于维护,易于管理。
便于调试:遇到问题能快速定位是哪个分类。
小编看法:视具体情况而定,拆分的同时,也会多出很多文件。如果一个类过于臃肿(比如有几千行代码),可以考虑给他瘦身,拆分成多个分类。
三、总是为第三方分类的名称加前缀
分类机制最大的功能:就是为不能修改源码的既有类中添加新的功能。
这时候我们要:
在分类类名前,加上专有前缀。
在分类方法名前,加上专有前缀。
最大限度上避免重名可能带来的bug,而且这种bug很难排查。
原因在于:分类的方法会直接添加在类中,而分类是在运行期把方法加入主类。这时候,如果出现方法重名,后一个写入的分类方法会把前一个覆盖掉。多次覆盖的结果总以最后一个分类为准。所以我们要加前缀,尽量避免重名带来的bug。
四、勿在分类中声明属性
不要在分类中声明属性,但可以在类扩展(extension)中声明属性,这样属性就不会暴露在外面。
举个例子:(类扩展)
// QiShare.m
@interface QiShare ()
/* 属性可以声明在这里 */
@end
@implementation QiShare
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 1 习近平G20里约峰会展现大国担当 7977553
- 2 一个金镯子省出1200元 金价真跌了 7916451
- 3 胖东来:员工不许靠父母买房买车 7866838
- 4 二十国集团里约峰会将会卓有成效 7789966
- 5 俄导弹击中乌水电站大坝 7627867
- 6 孙颖莎王艺迪不敌日本削球组合 7541990
- 7 高三女生酒后被强奸致死?检方回应 7472945
- 8 第一视角记录虎鲨吞下手机全程 7340745
- 9 中国一捕捉宇宙幽灵粒子装置建成 7200182
- 10 智慧乌镇点亮数字经济新未来 7187470