NSProxy
在看一些开源项目的时候,发现有使用NSProxy这个类
什么是NSProxy
:
NSProxy
是一个抽象的基类,是根类,与NSObject
类似NSProxy
和NSObject
都实现了<NSObject>
协议- 提供了消息转发的通用接口
如何使用NSProxy
来转发消息?
1.需要继承NSProxy
2.重写如下的2个方法:
methodSignatureForSelector:
方法,返回的是一个NSMethodSignature类型,来描述给定selector的参数和返回值类型。返回nil,表示proxy不能识别指定的selector。所有的NSObject
也响应此消息。
forwardInvocation:
方法将消息转发到对应的对象上
用法
NSProxy实现多继承
参考:
避免循环引用
一些开源项目中使用NSProxy
来避免循环引用,用在NSTimer
或者CADisplayLink
中
以下内容来自NSProxy与消息转发机制,也可参考:
NSTimer
是一个需要添加到Runloop
里的类,对于一个不会自动停止的Timer,你需要调用invalidate
方法来手动断开这个Timer。否则,引用Timer的Controller或者其他类,就会出现循环引用而无法释放掉。
举个例子,在Controller中,添加Timer很常见,比如
#import "SecondViewController.h"
@interface SecondViewController ()
@property (strong,nonatomic)NSTimer * timer;
@end
@implementation SecondViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1
target:self
selector:@selector(timerInvoked:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerInvoked:(NSTimer *)timer{
NSLog(@"1");
}
- (void)dealloc{
NSLog(@"Dealloc");
}
@end
假如我Push这样一个SecondViewController
,然后pop。
你会发现Controller没有被释放,timer也没有被取消。
我们可以在dealloc中,调用Timer取消吗?比如
- (void)dealloc{
[self.timer invalidate];
NSLog(@"Dealloc");
}
当然不行,因为Controller根本没有被释放,dealloc方法根本不会调用。
当然,破坏这种循环引用的方式有很多种。本文主要讲解如何用NSProxy
来破坏。
我们写一个WeakProxy来实现弱引用
@interface WeakProxy : NSProxy
@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end
@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return [[self alloc] initWithTarget:target];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.target methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.target respondsToSelector:aSelector];
}
@end
然后,这样创建Timer
self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerInvoked:) userInfo:nil repeats:YES];
你会发现可以释放了。
原理如下:
我们把虚线处变成了弱引用。于是,Controller就可以被释放掉,我们在Controller的dealloc中调用invalidate,就断掉了Runloop对Timer的引用,于是整个三个淡蓝色的就都被释放掉了。
延迟初始化
使用场景:
- 第一种情况,在
[SomeClass lazy]
之后调用 doSomthing,首先进入forwardingTargetForSelector
,_object
为 nil 并且不是 init 开头的方法的时候会调用 init 初始化对象,然后将消息转发给代理对象_object
; - 第二种情况,在
[SomeClass lazy]
之后调用initWithXXX:
,首先进入forwardingTargetForSelector
返回 nil,然后进入methodSignatureForSelector:
和forwardInvocation:
保存自定义初始化方法的调用,最后调用 doSomthing,进入forwardingTargetForSelector
,_object 为 nil 并且不是 init 开头的方法的时候会调用自定义初始化方法,然后将消息转发给代理对象 _object;
SomeClass *object = [SomeClass lazy];
// other thing ...
[object doSomething];// 在这里,object 才会调用初始化方法,然后调用 doSomething
具体实现(代码来源:Lazy Initialization for Objective-C):
LazyInitialization.m
#import "LazyInitialization.h"
@interface LazyProxy : NSProxy
- (instancetype)initWithClass:(Class)class;
@end
@implementation NSObject (LazyInitialization)
+ (instancetype)lazy {
return (id)[[LazyProxy alloc] initWithClass:[self class]];
}
@end
@implementation LazyProxy {
id _object;// 代理对象
Class _objectClass;// 代理类
NSInvocation *_initInvocation;// 自定义 init 调用
}
- (instancetype)initWithClass:(Class)cls {
_objectClass = cls;
return self;
}
- (void)instantiateObject {
_object = [_objectClass alloc];
if (_initInvocation == nil) {// 允许一些类 [SomeClass lazy] (没有调用 init)
_object = [_object init];
} else {// 调用自定义 init 方法
[_initInvocation invokeWithTarget:_object];
[_initInvocation getReturnValue:&_object];
_initInvocation = nil;
}
}
- (id)forwardingTargetForSelector:(SEL)selector {
if (_object == nil) {// _object 没有初始化
if (![NSStringFromSelector(selector) hasPrefix:@"init"]) {// 调用 init 开头之外的方法前进行 _object 初始化
[self instantiateObject];
}
}
return _object;// 将消息转发给 _object
}
// 调用自定义 init 方法会进入这个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [_objectClass instanceMethodSignatureForSelector:selector];
return signature;
}
// 保存自定义 init 方法的调用
- (void)forwardInvocation:(NSInvocation *)invocation {
_initInvocation = invocation;
}
先还是理解如下的内容
NSMethodSignature&NSInvocation
以下内容来自iOS 使用NSMethodSignature和 NSInvocation进行 method 或 block的调用
iOS中用三种方式调用方法:
如下的printStr1
方法
- (void)printStr:(NSString*)str{
NSLog(@"printStr %@",str);
}
1.直接调用
[self printStr:@"hello world 1"];
2.使用performSelector:withObject:
[self performSelector:@selector(printStr:) withObject:@"hello world 2"];
3.使用NSMethodSignature&NSInvocation
//获取方法签名
NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr:)];
//获取方法签名对应的invocation
NSInvocation *invocationOfPrintStr = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
/**
设置消息接受者,与[invocationOfPrintStr setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等价
*/
[invocationOfPrintStr setTarget:self];
/**设置要执行的selector。与[invocationOfPrintStr setArgument:@selector(printStr1:) atIndex:1] 等价*/
[invocationOfPrintStr setSelector:@selector(printStr:)];
//设置参数
NSString *str = @"hello world 3";
[invocationOfPrintStr setArgument:&str atIndex:2];
//开始执行
[invocationOfPrintStr invoke];
NSMethodSignature
NSMethodSignature用于描述method的类型信息:返回值类型,及每个参数的类型。
使用NSMethodSignature
对象转发接收对象(receiving object)不响应的消息。
创建NSMethodSignature
:
1.使用NSObject
的methodSignatureForSelector:创建NSMethodSignature
对象
2.使用signatureWithObjCTypes:方法
[NSMethodSignature signatureWithObjCTypes:"v@:"];
一般使用NSMethodSignature
对象来创建NSInvocation
对象,传递给forwardInvocation:
方法
一个方法的签名由多个字符组成,首先是返回值类型,接着是隐式参数self
和_cmd
的字符编码,接着是一个或多个显示参数。
NSInvocation
NSInvocation作为对象呈现的Objective-C消息。(An Objective-C message rendered as an object.)
NSInvocation
对象主要用于存储和转发对象之间和应用程序之间的消息,主要是NSTimer
对象和分布式对象系统。NSInvocation
对象包含Objective-C
消息的所有元素:目标(target),选择器(selector),参数和返回值。可以直接设置这些元素,并在NSInvocation
对象被dispatched时自动设置返回值。
使用invocationWithMethodSignature:方法创建NSInvocation
对象,不要使用alloc
和init
来创建
Objective-C消息转发
如果调用一个没有实现的方法,首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。
这几套方法如下:
1.自己为这个方法增加实现。
+ (BOOL)resolveInstanceMethod:(SEL)sel
实例方法+ (BOOL)resolveClassMethod:(SEL)sel
类方法
void run(id self, SEL _cmd)
{
NSLog(@"%@ %s", self, sel_getName(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(run)) {
class_addMethod(self, sel, (IMP)run, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
2.转发消息,让别的类处理,- (id)forwardingTargetForSelector:(SEL)aSelector
返回需要转发消息的对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [[Car alloc] init];
}
3.如果我们不实现forwardingTargetForSelector
,系统就会调用方案三的两个方法methodSignatureForSelector
和forwardInvocation
需要做的是自己新建方法签名,再在forwardInvocation
中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *sel = NSStringFromSelector(aSelector);
if ([sel isEqualToString:@"run"]) {
//为转发方法手动生成签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
//创建需要转发消息的对象
Car *car = [[Car alloc] init];
if ([car respondsToSelector:selector]) {
//唤醒这个方法
[anInvocation invokeWithTarget:car];
}
}