1. 程式人生 > 其它 >[iOS]定時器NSTimer、CADisplayLink的記憶體管理

[iOS]定時器NSTimer、CADisplayLink的記憶體管理

NSTimer、CADisplayLink會對target產生強引用,如果target同時對他們產生強引用,則會發生迴圈引用。

以NSTimer為例,解決迴圈引用的問題。

方法1:使用block

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    __weak typeof(self) weakself = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES block:^(NSTimer * _Nonnull timer) { [weakself func]; }]; } - (void)func { NSLog(@"%s",__func__); } - (void)dealloc { NSLog(@"%s",__func__); [self.timer invalidate]; }

方法2:使用NSObject作為中間物件

Proxy1.h

@interface Proxy1 : NSObject
+ (instancetype)initWithTarget:(id
)target; @end
Proxy1.m

@interface Proxy1 ()
@property (nonatomic,weak) id target;
@end

@implementation Proxy1

+ (instancetype)initWithTarget:(id)target
{
    Proxy1 *proxy = [[Proxy1 alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    
return self.target; } @end
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy1 initWithTarget:self] selector:@selector(func) userInfo:nil repeats:YES];
}

- (void)func
{
    NSLog(@"%s",__func__);
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}

方法3:使用NSProxy作為中間物件

Proxy2.h

@interface Proxy2 : NSProxy
+ (instancetype)initWithTarget:(id)target;
@end
Proxy2.m

@interface Proxy2 ()
@property (nonatomic,weak) id target;
@end

@implementation Proxy2

+ (instancetype)initWithTarget:(id)target
{
    Proxy2 *proxy = [Proxy2 alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

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

@end
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy2 initWithTarget:self] selector:@selector(func) userInfo:nil repeats:YES];
}

- (void)func
{
    NSLog(@"%s",__func__);
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}

方法3的優點:

執行效率高,無需執行父類的方法搜尋過程,直接進行訊息轉發。

關於NSProxy補充:

通過呼叫isKindOfClass

Proxy1 *proxy1 = [Proxy1 initWithTarget:self];
Proxy2 *proxy2 = [Proxy2 initWithTarget:self];

NSLog(@"%d",[proxy1 isKindOfClass:[ViewController class]]);   // 0
NSLog(@"%d",[proxy2 isKindOfClass:[ViewController class]]);   // 1

proxy1為Proxy1型別,Proxy1繼承自NSObject,可以正常處理isKindOfClass方法,所以判斷結果為0.

proxy2為Proxy2型別,Proxy2繼承自NSProxy,大部分方法會直接進入訊息轉發階段,會改為使用target進行呼叫,所以判斷結果為1.

通過觀察NSProxy的原始碼發現,該方法直接進行了訊息轉發。

/**
 * 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;
}