NSTimer、CADisplayLink 內存泄漏

NSTimer、CADisplayLink 內存泄漏

內存泄漏的緣由

CADisplayLink 要用 Taget 和 Selector 初始化,NSTimer 也能夠用相似的方法初始化。這樣初始化以後,NSTimer 或 CADisplayLink(如下把二者統稱爲 CADisplayLink)會強引用 Target。當 CADisplayLink 加入 NSRunLoop 中,NSRunLoop 會強引用 CADisplayLink。直到 CADisplayLink 調用 invalidate 方法,CADisplayLink 纔會被 NSRunLoop 移除,CADisplayLink 也再也不強引用 Target。html

一般在 UIViewController 或 UIView 中建立 CADisplayLink,Target 是 UIViewController 或 UIView。若是隻在 UIViewController 或 UIView 的 dealloc 方法中調用 CADisplayLink 的 invalidate 方法,會形成內存泄漏。由於 NSRunLoop 一直都在,CADisplayLink 不釋放,Target 被強引用,Target 的 dealloc 方法不會被調用,CADisplayLink 的 invalidate 方法也不被調用,CADisplayLink 不會從 NSRunLoop 中移除。參見示意圖,實線箭頭表示強引用git

NSRunLoop —> CADisplayLink —> Targetgithub

另外,爲了在 Target 的 dealloc 方法中調用 CADisplayLink 的 invalidate 方法,CADisplayLink 可能做爲 Target 的屬性被強引用,這就造成 CADisplayLink 和 Target 的互相強引用,形成內存泄漏。objective-c

解決方法

改變 invalidate 方法的調用時機

例如,在 UIViewController 的 viewDidDisappear: 方法中,或者在 CADisplayLink 實現的動畫結束後,或者在其餘合適的時機調用 CADisplayLink 的 invalidate 方法。這樣就把 CADisplayLink 從 NSRunLoop 中移除,CADisplayLink 也再也不強引用 Target。即便 Target 強引用 CADisplayLink,在 Target 被釋放後,CADisplayLink 也會被釋放。app

若是找不到合適的時機調用 CADisplayLink 的 invalidate 方法,那麼仍是在 dealloc 方法中調用 invalidate 方法,同時用 NSProxy 避免 CADisplayLink 對 Target 的強引用。開源庫 FLAnimatedImage 中的 FLAnimatedImageView 用 FLWeakProxy,避免 CADisplayLink 的強引用。公開的方法只有初始化方法ide

@interface FLWeakProxy : NSProxy

+ (instancetype)weakProxyForObject:(id)targetObject;

@end

使用時,把 self 套一層 FLWeakProxy 便可防止 self 被 CADisplayLink 強引用。參見示意圖,虛線箭頭表示弱引用oop

NSRunLoop —> CADisplayLink —> Proxy - - > self動畫

FLWeakProxy *weakProxy = [FLWeakProxy weakProxyForObject:self];
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];

這樣,self 的 dealloc 方法會被調用,在裏面會調用 CADisplayLink 的 invalidate 方法,CADisplayLink 會被釋放this

- (void)dealloc
{
    // Removes the display link from all run loop modes.
    [_displayLink invalidate];
}

FLWeakProxy 對初始化時傳入的 targetObject 進行弱引用,弱引用屬性是 target。經過 Runtime 的消息轉發機制 (參見 http://tech.glowing.com/cn/objective-c-runtime/) 把消息轉發給 target,使 target 調用相應的方法。當 target 爲空又收到消息時,把相應的方法返回值設置爲空。具體代碼以下atom

@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

轉載請註明出處:http://www.cnblogs.com/silence-cnblogs/p/7583289.html

相關文章
相關標籤/搜索