通过WeakProxy防止循环引用

今天看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,所以要重写forwardInvocationmethodSignatureForSelector两个方法,它们响应了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的。。。难道是我理解错了吗?欢迎大家评论~~~~~~~~~

你可能感兴趣的:(通过WeakProxy防止循环引用)