今天看FLAnimatedImage
时无意只发现一个proxy类FLWeakProxy
,里面有这样的代码:
[self.weakProxy performSelector:@selector(resetFrameCacheSizeMaxInternal) withObject:nil afterDelay:kResetDelay];
因为performSelector : afterDelay会强引用当前类,为了防止内存泄露才用的proxy
。
1 .先贴出代码 FLWeakProxy.h文件:
@interface FLWeakProxy : NSProxy
+ (instancetype)weakProxyForObject:(id)targetObject;
@end
2.FLWeakProxy.m文件
#import "FLWeakProxy.h"
@interface FLWeakProxy ()
@property (nonatomic, weak) id target;
@end
@implementation FLWeakProxy
#pragma mark Life Cycle
// This is the designated creation method of an `FLWeakProxy` and
// as a subclass of `NSProxy` it doesn't respond to or need `-init`.
+ (instancetype)weakProxyForObject:(id)targetObject
{
FLWeakProxy *weakProxy = [FLWeakProxy alloc];
weakProxy.target = targetObject;
return weakProxy;
}
#pragma mark Forwarding Messages
- (id)forwardingTargetForSelector:(SEL)selector
{
// Keep it lightweight: access the ivar directly
return _target;
}
#pragma mark - NSWeakProxy Method Overrides
#pragma mark Handling Unimplemented Methods
- (void)forwardInvocation:(NSInvocation *)invocation
{
// Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.
// The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.
// We can't really handle struct return types here because we don't know the length.
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
// We only get here if `forwardingTargetForSelector:` returns nil.
// In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.
// We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.
// Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.
// See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
@end
_target
是弱引用, 为了打破循环。然后它在forwardingTargetForSelector
把所有方法调用传给了_target
。- 当_target被释放后,如果方法被调用那么肯定会报
doesNotRecognizeSelector
,所以要重写forwardInvocation
和methodSignatureForSelector
两个方法,它们响应了method,但是不会做任何处理是返回nil,只要不报错就行。
那么我们如何使用呢?
主要有两个场景,定时器NSTimer和performSelector。
1.定时器NSTimer
_weakProxy = [FLWeakProxy weakProxyForObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:_weakProxy selector:@selector(timeUpdate) userInfo:nil repeats:YES];
2.performSelector :delay (没成功
)
代码如下:
_weakProxy = [FLWeakProxy weakProxyForObject:self];
[_weakProxy performSelector:@selector(delayMethod) withObject:nil afterDelay:4];
结果不起作用!我认为:虽然是_weakProxy调用的方法,但是真正是_target调用的performSelector,所以runloop还是强引用了_target,没有起到打破循环的目的!
解决方法:
如果让weakProxy继承的
NSProxy
改成NSObject
就可以完美解决这个问题,因为NSObject本身有performSelector方法,所以是_weakProxy
调用的performSelector,等4秒之后调用delayMethod
时才会调用_target
执行,这样是没有问题的!但是我查了一下NSProxy和NSObject的区别,都说NSProxy是最适合做代理的,而且用NSObject之后,NSObject的category里的方法是无法正常调用的。像下面的代码:
NSLog(@"%@",[proxyA valueForKey:@"length"]);//NSProxy
NSLog(@"%@",[proxyB valueForKey:@"length"]);//NSObject
结果是不一样的,因为valueForKey是catergory里的方法,无法被调用。
结语: 如果只针对NSTimer时,用NSProxy是没有任何问题的。但是对于performSelector不知如何是好,但是像FLAnimatedImage
这么牛的第三方,按理说不会出现这种严重的bug的。。。难道是我理解错了吗?欢迎大家评论~~~~~~~~~