注册

『ios』NSProxy解决NStimer循环引用的思考

1.nstimer为什么回循环引用
2.NSObject如何解决NStimer循环引用
3.NSProxy如何解决NStimer循环引用
4.为什么要用NSProxy,优势在哪
围绕上面几个问题我们来思考一下

1.nstimer为什么回循环引用

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
self强引用timer,timer强引用self.
那是否可以改为

__weak typeof(self)weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(timerTest) userInfo:nil repeats:YES];
答案也是不行的。target其实里面做的是一个赋值操作。
我们可以从gnu里面的源码中发现这个问题。所以在外面设置weakSelf是不能解决这个问题,如果是block那是可以解决的。所以我们现在就要添加一个中间变量,来解开这个环。

feed585331ec5623ecd7944dcb1382b5.png

2.NSObject如何解决NStimer循环引用
这里我们可以利用消息转发阶段的forwardingTargetForSelector函数来解决这个问题。

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[XHObjectProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];

@interface XHObjectProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

@implementation XHObjectProxy
+ (instancetype)proxyWithTarget:(id)target
{
XHObjectProxy *proxy = [[XHObjectProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end

设置XHObjectProxy这个中间件,然后传入target,并且用一个弱引用的target来接受他。从而实现解开循环。
当timer调用timerTest方法的时候,很显然XHObjectProxy没有这个方法,所以就进入了消息转发阶段。
当到达forwardingTargetForSelector 这个阶段的时候,直接返回self.target也就是[XHObjectProxy proxyWithTarget:self] 中的self。所以可以解决这个问题。

3.NSProxy如何解决NStimer循环引用
NSProxy跟NSObject一样,也是一个基类,是一个实现<NSObject>协议的基类。

@class NSMethodSignature, NSInvocation;

NS_ASSUME_NONNULL_BEGIN

NS_ROOT_CLASS
@interface NSProxy <NSObject> {
__ptrauth_objc_isa_pointer Class isa;
}

+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;

- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);

// - (id)forwardingTargetForSelector:(SEL)aSelector;

@end

里面有消息转发第三阶段的两个方法

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel

所以可以这么写

@interface XHRealProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation XHRealProxy

+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
XHRealProxy *proxy = [XHRealProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end

经过测试我们发现在同时写forwardingTargetForSelector 和 methodSignatureForSelector 这两个方法的情况下,不会执行第二阶段的转发,直接进入methodSignatureForSelector。

4.为什么要用NSProxy,优势在哪
我们一般的方法调用都是通过objc_msgSend来进行的,然后经过消息转发,一步一步的进行。
这张图是NSObject的方法调用


3b3b6ee788678de09a163409c5bea15c.png


  ViewController *vc = [[ViewController alloc] init];

XHRealProxy *proxy1 = [XHRealProxy proxyWithTarget:vc];

XHObjectProxy *proxy2 = [XHObjectProxy proxyWithTarget:vc];
通过代码进行测试,发现继承NSProxy的类,直接执行了methodSignatureForSelector方法,而继承NSObject的类,直接执行了forwardingTargetForSelector方法。
然后又进了nstimer的方法进行测试。发现不管是NSProxy 还是 NSObject 都会先直接走forwardingTargetForSelector这个方法。
觉得很奇怪,然后去gnu上去看源码。发现里面重写了isKindOfClass这个方法,然后里面直接调用了methodSignatureForSelector

/**
* Calls the -forwardInvocation: method to determine if the 'real' object
* referred to by the proxy is an instance of the specified class.
* Returns the result.<br />
* NB. The default operation of -forwardInvocation: is to raise an exception.
*/

- (BOOL) isKindOfClass: (Class)aClass
{
NSMethodSignature *sig;
NSInvocation *inv;
BOOL ret;

sig = [self methodSignatureForSelector: _cmd];
inv = [NSInvocation invocationWithMethodSignature: sig];
[inv setSelector: _cmd];
[inv setArgument: &aClass atIndex: 2];
[self forwardInvocation: inv];
[inv getReturnValue: &ret];
return ret;
}

/**
* Calls the -forwardInvocation: method to determine if the 'real' object
* referred to by the proxy is an instance of the specified class.
* Returns the result.<br />
* NB. The default operation of -forwardInvocation: is to raise an exception.
*/

- (BOOL) isMemberOfClass: (Class)aClass
{
NSMethodSignature *sig;
NSInvocation *inv;
BOOL ret;

sig = [self methodSignatureForSelector: _cmd];
inv = [NSInvocation invocationWithMethodSignature: sig];
[inv setSelector: _cmd];
[inv setArgument: &aClass atIndex: 2];
[self forwardInvocation: inv];
[inv getReturnValue: &ret];
return ret;
}

- (BOOL) conformsToProtocol: (Protocol*)aProtocol
{
NSMethodSignature *sig;
NSInvocation *inv;
BOOL ret;

sig = [self methodSignatureForSelector: _cmd];
inv = [NSInvocation invocationWithMethodSignature: sig];
[inv setSelector: _cmd];
[inv setArgument: &aProtocol atIndex: 2];
[self forwardInvocation: inv];
[inv getReturnValue: &ret];
return ret;
}

为什么NSProxy的效率更高?因为他是直接走消息转发第三步methodSignatureForSelector,而nsobject需要走objc_msgSend整个流程,所以效率更高。


作者:butterflyer
链接:https://www.jianshu.com/p/a079fd0f7d61

0 个评论

要回复文章请先登录注册