【代码优化】调用optional delegates的最佳方法
【转载请注明出处】http://www.cnblogs.com/lexingyu/p/3932475.html
本文是以下两篇blog的综合脱水,感谢两位作者为解放码农生产力所做的深入思考=。=
Smart Proxy Delegation
Elegant Delegation
使用delegate的情境通常是这样
定义class和delegate
@protocol TestObjectDelegate <NSObject>
@optional
- (void)testObjectMethod;
- (NSString *)testObjectMethodWithReturnValue;
@end
@interface TestObject : NSObject
@property (nonatomic, weak) id<TestObjectDelegate> delegate;
- (void)print;
- (void)printWithLog;
@end
在类的内部调用delegate的方法
- (void)print
{
//call the delegate to do the real work
}
###调用的方法通常有以下两种
普通青年:
if ([self.delegate respondsToSelector:@selector(testObjectMethod)])
{
[self.delegate testObjectMethod];
}
这个办法的缺点是
1)引入了大量glue code,每个optional function都需要3行代码。尤其在开启clang的-Warc-repeated-use-of-weak时,多次使用self.delegate(通常情况下,是weak)会被警告;
所以很可能还得这么写
- (void)print
{
id <TestObjectDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(testObjectMethod)])
{
[delegate testObjectMethod];
}
}
2)调用的方法名需要写两次,很可能写错导致方法未被调用;
3)对于高频率调用的方法而言,意味着需要反复调用respondToSeletor,性能上有所影响(RunTime可能会对respondToSeletor进行缓存,因此在大部分应用上这一点不需要计入考量)。
文艺青年
先添加flag
@interface TestObject : NSObject
{
struct
{
unsigned int respond2TestObjectMethod:1;
}_flags;
}
@property (nonatomic, weak) id<TestObjectDelegate> delegate;
- (void)print;
@end
再重载setDelegate以设置flag,将respondToSeletor的结果缓存起来
- (void)setDelegate:(id<TestObjectDelegate>)delegate
{
_delegate = delegate;
BOOL respond2TestObjectMethod = [delegate respondsToSelector:@selector(testObjectMethod)];
_flags.respond2TestObjectMethod = respond2TestObjectMethod ? 1 : 0;
}
最后在print中直接使用缓存的结果
- (void)print
{
if (_flags.respond2TestObjectMethod)
{
[self.delegate testObjectMethod];
}
}
这个方法被Apple广泛采用,在SDK中随处可见。
它的优点是将respondToSeletor的结果手动缓存了起来,不需要做性能上的猜测,同时避开了
-Warc-repeated-use-of-weak的警告。
但遗憾的是,代码的冗余并没有被移除,反而更为严重(调用时仍然需要3行glue code,且在头文件和setDelegate中添加了大量代码)。当delegate中的方法名需要变动时,需要同时修改多处代码,真如噩梦一般。
嗯。。。。。。抱歉这里没有二逼青年
外国友人的想法
实际上我们真正想要的是类似于这样的东西
- (void)print
{
[self.delegateProxy testObjectMethod];
}
把glue code也好,其他额外处理也好,都放到一个统一的地方。在调用的时候,一句话简单明了,解决问题。
那么具体怎么做呢?
其实,OC的方法调用,或者准确地说,消息传递,就是这样一种机制。这里上一张自绘的图以便说明
OC中任何一次方法调用,都会从1开始走这个流程,一个步骤不行就进行下一步。若所有4个步骤走完仍然无法找到对应的impletation,则触发异常,程序crash。简单说一下各个步骤的作用
1)在类的方法表(methodList)中,根据seletor查找对应的impletation;
2) resolveInstanceMethod用于集中处理类中一些类似的方法,比如在使用core data时需要指定多个property为@dynamic,它们的setter和getter就可以集中在这个方法里做;
3)forwardingTargetForSelector,作用是将本对象无法处理的调用信息转给另一个对象处理,但不改变调用信息;
4)forwardInvocation,作用是根据methodSignatureForSelector和调用参数等信息生成的NSInvocation来指定一个对象处理本次调用,在指定时可以对调用信息做任意的修改,比如增加参数个数。
3被称为Fast message forwarding,相应地4则是Regular message forwarding,二者合在一起才是完整的Message forwarding。
C语言在调用函数时,需要知道函数的原型,以便将参数放入寄存器或压入栈中,并视情况预留返回值的空间。OC作为C语言的超集,也需要顾及这一点。函数的调用信息在OC中以NSMethodSignature的形式存在,在Regular message forwarding中由methodSignatureForSelector返回。
从以上说明不难看出,1和2的作用是在类内部寻找impletation,而3和4则是在类外部寻找合适的其他类的实例来处理调用信息。显而易见,3和4正是delegateProxy所需要的。
铺垫了这么多,终于到了正题。
用Message forwarding机制,来构建一个delegateProxy
在这里构建了一个NSProxy的派生类作为delegateProxy,像这样
@interface CDDelegateProxy : NSProxy
@property (nonatomic, weak, readonly) id delegate;
@property (nonatomic, strong, readonly) Protocol *protocol;
@property (nonatomic, strong, readonly) NSValue *defaultReturnValue;
@end
delegateProxy中分别保存了被代理的delegate对象、delegate对应的protocol和方法未找到时提供的默认值。
在.m文件中,首先将glue code放入,像这样
//供外部需要时使用
- (BOOL)respondsToSelector:(SEL)selector
{
return [_delegate respondsToSelector:selector];
}
//Fast message forwarding, 存放glue code
- (id)forwardingTargetForSelector:(SEL)selector
{
id delegate = _delegate;
return [delegate respondsToSelector:selector] ? delegate : self;
}
嗯。。。至此似乎就完事了=。=
大部分情况下确实如此。但当方法不存在又需要一个默认返回值时,比如
- (void)printWithLog
{
//这里已经用上delegateProxy了,哈哈
NSString *logInfo = [self.delegateProxy testObjectMethodWithReturnValue];
NSLog(@"%@", logInfo);
}
就需要用到Regular message forwarding了。具体做法如下
//Regular message forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
id delegate = _delegate;
NSMethodSignature *signature = [delegate methodSignatureForSelector:selector];
//若delegate未实现对应方法,则从protocol的声明中获取MethodSignature
if (!signature)
{
if (!_signatures) _signatures = [self methodSignaturesForProtocol:_protocol];
signature = CFDictionaryGetValue(_signatures, selector);
}
//此处如果return nil, 则不会触发forwardInvocation
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
//若默认返回值和invocation中指定的返回值一致,则取默认返回值
if (_defaultReturnValue
&& strcmp(_defaultReturnValue.objCType, invocation.methodSignature.methodReturnType) == 0)
{
char buffer[invocation.methodSignature.methodReturnLength];
[_defaultReturnValue getValue:buffer];
[invocation setReturnValue:&buffer];
}
}
首先由methodSignatureForSelector根据protocol中的方法声明,返回一个signature,再由forwardInvocation判断与默认的返回值是否类型一致,一致则返回预设的默认值(即刚才提到的defaultReturnValue)。
这样,delegateProxy就构建完毕了。在使用的时候,应注意delegateProxy的作用只是在类内部保持调用的简洁,对于外部代码而言,它应该是透明的。具体来说,首先应该将deleagteProxy定义在class extension中
//.m文件中
@interface SomeObject ()<TestObjectDelegate>
@property (nonatomic, strong) id<TestObjectDelegate> delegateProxy;
@end
这里将delegateProxy直接声明为id的形式,目的是使之后编码时仍然能够享有Xcode对protocol中方法的自动提示补全。
接着override delegate(真正id被定义在头文件中)
- (void)setDelegate:(id <TestObjectDelegate>)delegate
{
self.delegateProxy = delegate ? (id <TestObjectDelegate>)[[CDDelegateProxy alloc] initWithDelegate:delegate] : nil;
}
- (id <TestObjectDelegate>)delegate
{
return ((CDDelegateProxy *)self.delegateProxy).delegate;
}
这个步骤看着有些繁琐,可以通过宏来简化,比如
#define CD_DELEGATE_PROXY_CUSTOM(protocolname, GETTER, SETTER) \
- (id<protocolname>)GETTER { return ((PSTDelegateProxy *)self.GETTER##Proxy).delegate; } \
- (void)SETTER:(id<protocolname>)delegate { self.GETTER##Proxy = delegate ? (id<protocolname>)[[PSTDelegateProxy alloc] initWithDelegate:delegate conformingToProtocol:@protocol(protocolname) defaultReturnValue:nil] : nil; }
#define CD_DELEGATE_PROXY(protocolname) PST_DELEGATE_PROXY_CUSTOM(protocolname, delegate, setDelegate)
在使用的使用可以简单地
CD_DELEGATE_PROXY(id <PSPDFResizableViewDelegate>)
当然,对于比较个性化的delegate的名称,可以通过扩展这个宏来实现。
如此一来,外部访问delegate时,获取到的仍然是正确的对象。
以上,就是调用optional delegates的最佳方法,从起因到原理到解决方案的完整阐述。
文中为便于说明,使用了我自己写的一个简化版的delegateProxy,这里提供一个原作者Peter steinberger的完整实现,有不少值得学习的点哦。
终于写完啦!!!!
【代码优化】调用optional delegates的最佳方法的更多相关文章
- 编程思想—依赖注入(DI)并非实现控制反转(IOC)的最佳方法
以构造函数注入为例: public class TestClass(IClassA a,IClassB b, IClassC C,IClassD d) { public void Method1() ...
- Java 小技巧和在Java避免NullPonintException的最佳方法(翻译)
前几天就g+里面看到有人引用这篇博文.看了一下.受益颇多. 所以翻译过来,希望和大家一起学习.本人英语水平有限,假设有错,请大家指正. 原文地址(须要翻墙):http://ja ...
- Optional类包含的方法介绍及其示例
Optional类的介绍 javadoc中的介绍 这是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回> 该对象. 使用场景 用于避免 ...
- Android中点击隐藏软键盘最佳方法——Android开发之路4
Android中点击隐藏软键盘最佳方法 实现功能:点击EditText,软键盘出现并且不会隐藏,点击或者触摸EditText以外的其他任何区域,软键盘被隐藏: 1.重写dispatchTouchEve ...
- Spring.net 间接调用被AOP拦截的方法失效(无法进入aop的拦截方法)
.下面的tx要定义 <objects xmlns="http://www.springframework.net" xmlns:db="http://www.spr ...
- Flex Flash Player回声消除的最佳方法
Adobe Flash Player 已经成为音频和视频播放的非常流行的工具.实际上,目前大多数因特网视频均使用 Flash Player观看. Flash Player 通过将许多技术进行组合可以提 ...
- js调用父窗口中的方法
window.open调用父窗口中的方法 回调函数: function fun9(ex){ alert(ex); } 调用语句: window.open("RoomSelecter.htm? ...
- JQuery直接调用asp.net后台WebMethod方法
利用JQuery的$.ajax()可以很方便的调用asp.net的后台方法.[WebMethod] 命名空间 1.无参数的方法调用, 注意:1.方法一定要静态方法,而且要有[WebMethod]的 ...
- android 中activity调用本地service中的方法。
1.自定义一个接口,暴露服务中的方法 public interface IService { /**服务中对外暴露的方法 */ void methodInService();} 2.自定一 ...
随机推荐
- 基于图形学混色问题OpenGl的收获
void myDisplay(void) {glClearColor(0.0f,0.0f,0.0f,1.0f); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_B ...
- PSP 进度条 柱状图 饼状图
9号 类别 开始时间 结束时间 间隔 净时间 燃尽图 8::00 8:20 0 20分钟 站立会议 8:20 8:50 0 30分钟 读构建之法 9:20 13:20 120分钟 120分钟 四人小组 ...
- testng几种写法
testng几种写法: 1 <!--运行-类--> 2 <?xml version="1.0" encoding="UTF-8"?> 3 ...
- LR监控tomcat服务器
采用编写VuGen脚本访问Tomcat的Status页面的方式获取性能数据(利用了关联和lr_user_data_point函数),本质上还是使用tomcat自带的监控页面,只是将监控结果加到LR的a ...
- FastReport.Net 无限页高(连续纸小票)
using System; using System.Collections; using System.Collections.Generic; using System.ComponentMode ...
- java 基础 --File
1, 创建文件 File file = new File(path); file.createNewFile(); //如果路径不存在,会抛异常 file.mkdir();//如果路径不存在,返回fa ...
- react-router之代码分离
概念 无需用户下载整个应用之后才能访问访问它.即边访问边下载.因此我们设计一个组件<Bundle>当用户导航到它是来动态加载组件. import loadSomething from 'b ...
- presence_of_element_located与visibility_of_element_located区别
selenium 问题:加了显性等待后,操作元素依然出错 背景: 用WebDriverWait时,一开始用的是presence_of_element_located,我对它的想法就是他就是用来等待 ...
- Notepad++查找和替换空行/空格/换行
Notepad++查找和替换支持正则表达式,功能很强大,但比较复杂因此暂不研究 Notepad++使用正则表达式查找,首先需要勾选查找/替换窗口左下部的“正则表达式(E)”\r\n表示换行,其中\r表 ...
- Zebras CodeForces - 950C(思维)
借鉴自: https://www.cnblogs.com/SuuT/p/8619227.html https://blog.csdn.net/my_sunshine26/article/details ...