作用一: 代理
想要拦截自定义CPTabBarController(UITabBarController子类)中tabbar的点击事件,最简单的就是直接设置自己为自己的代理self.delegate=self,再实现代理方法tabBarController:shouldSelectViewController。这样设置代理方法一看就不爽,寻求此类事件的解决方法。
NSProxy是专门为消息转发而设计的抽象类,稍微设计下就可以达到此目的。
@interface CPWeakProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
#import "CPWeakProxy.h"
@implementation CPWeakProxy{
__weak id _target;
}
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[CPWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
@end
使用时在CPTabBarController的viewDidLoad方法中
_proxy = [CPWeakProxy proxyWithTarget:self];
self.delegate = _proxy;
这里必须将CPWeakProxy对象设置为控制器的属性强引用避免释放。
这里要注意的是CPWeakProxy对象必须实现respondsToSelector方法,因为UITabBarController的实现中肯定有诸如下面的代码
- (void)tabbarDidSelect{ // tabbarItem被点击时调用
if (self.delegate && [self.delegate respondsToSelector:@selector(xxx)]) {
[self.delegate xxx];
}
}
CPWeakProxy是一个抽象类,respondsToSelector并未有默认实现,需要自行实现,否则会找不到方法崩溃。
tabbaritem点击时,调用代理CPWeakProxy的xxx方法,肯定找不到,触发消息转发进入CPWeakProxy的forwardingTargetForSelector方法,返回_target(就是我们自己写的CPTabBarController),调用回控制器中实现的代理方法,解决问题。
作用二:NSTimer的target
NSProxy还可以用来转发NSTimer的消息,避免NSTimer强引用控制器无法销毁。常规使用如下
[NSTimer scheduledTimerWithTimeInterval:1 target:[CPWeakProxy proxyWithTarget:self] selector:@selector(xxx) userInfo:nil repeats:YES];
用上面方法后控制器能正常释放,但是程序崩溃了。。。
追查原因,是NSTimer会使CPWeakProxy对象无法释放,控制器销毁时没有销毁NSTimer,定时器继续回调CPWeakProxy的xxx方法,而消息转发方法forwardingTargetForSelector返回nil,转发失败程序崩溃。
结论,在控制器中NSTimer使用CPWeakProxy转发消息可以避免控制器无法销毁,但是控制器销毁时必须手动销毁定时器来保证CPWeakProxy对象正常释放。
设计的转发类继承自NSProxy而不是NSObject,是因为给NSProxy发送消息时只会在当前类中查找方法,一旦找不到就执行消息转发操作,相比NSObject少去了递归父类查找方法等流程,效率更高