《编写高质量OC代码》已顺利完成一二三四五六七八篇!
附上连接:
iOS 编写高质量Objective-C代码(一)—— 简介
iOS 编写高质量Objective-C代码(二)—— 面向对象
iOS 编写高质量Objective-C代码(三)—— 接口和API设计
iOS 编写高质量Objective-C代码(四)—— 协议与分类
iOS 编写高质量Objective-C代码(五)—— 内存管理机制
iOS 编写高质量Objective-C代码(六)—— block专栏
iOS 编写高质量Objective-C代码(七)—— GCD专栏
iOS 编写高质量Objective-C代码(八)—— 系统框架缓存
本篇的主题是:协议与分类(protocol
& category
)性能优化
先简单介绍一下今天的主角:协议 与 分类bash
protocol
):OC中的协议与Java里的接口(interface
)相似,OC不支持多继承。可是能够经过协议来实现委托模式。category
):分类能够为既有类添加新的功能。分类是把“双刃剑”,用得好能够发挥OC的高动态性
;用的很差,会留下不少坑。因此,经过这篇文章让咱们一块儿研究OC的一项语言特性:category
。委托模式(又称代理):某对象将一类方法(任务)交给另外一个对象帮忙完成。 ~相似于:老板把一类任务交给某个leader去完成。(固然多类任务就会对应多个leader去完成)。~框架
举例来讲,当某对象要从另外一个对象获取数据时,就可使用委托模式。经过实现数据源协议来获取数据,这种作法被称为“数据源协议”(Data Source Protocol
)。相似于UITableView
的UITableViewDataSource
。ide
再举例来讲,当一个对象要有一些事件响应时,就可使用委托模式。经过实现一个协议(通常称为delegate
),让代理对象帮助该对象处理事件响应。相似于UITableView
的UITableViewDelegate
。性能
请看图解:优化
百说不如一Demo:这是小编整理的关于Button动画的例子动画
@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;//!< 结束动画
- (void)reverseAnimation;//!< 最后的修改动画
@end
复制代码
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];
}
复制代码
等等等...ui
因此,就会写出不少相似于这样格式的代码:this
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分类” 指的就是 类扩展(extension)。
咱们能够把一些私有的属性声明在类扩展里,这样在导入.h文件时,看不到类扩展声明的属性。 目的:把公共接口中向外暴露的内容最小化,隐藏一些属性和实现细节。
这里补充一个小知识点:你们都知道Objective-C,但据说过Objective-C++吗?
Objective-C++是Objective-C和C++的混编,编译时会生成.mm
文件。 这时候会遇到一个问题:只要导入含有C++的.h
,都会编译成.mm
文件。由于只有.mm
文件才能同时编译OC和C++。 那么,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》第四章