首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Objective-C NSTimer 循環引用終極解決方案:弱引用封裝與適配器模式

     編輯:關於C語言

核心問題

`NSTimer` 的 `scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:` 會 **retain target**,導致:

  1. ViewController 持有 Timer → Timer retain VC → **循環引用** → VC 永不釋放
  2. `dealloc`/`viewDidDisappear` 不被調用,無法在此 invalidate

方案演進路線

第一步:Block 類別 + 類對象 Target

// NSTimer+LPBlocks.h
@interface NSTimer (LPBlocks)
+ (NSTimer *)lp_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(void (^)(void))block
                                         repeats:(BOOL)repeats;
@end

// NSTimer+LPBlocks.m
@implementation NSTimer (LPBlocks)
+ (NSTimer *)lp_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(void (^)(void))block
                                         repeats:(BOOL)repeats {
    return [self scheduledTimerWithTimeInterval:interval
                                          target:self
                                        selector:@selector(lp_timerBlockInvoke:)
                                        userInfo:[block copy]
                                         repeats:repeats];
}
+ (void)lp_timerBlockInvoke:(NSTimer *)timer {
    void (^block)(void) = timer.userInfo;
    if (block) block();
}
@end

優點: Target 是類對象,不 retain 實例。
缺點: Block 捕獲 `self` 仍形成 `self → timer → block → self` 循環。

第二步:LPTimer 封裝類 + 雙重 Weak 斷環

// LPTimer.h
@interface LPTimer : NSObject
- (void)scheduleTimerWithTimeInterval:(NSTimeInterval)interval
                               target:(id)target
                             selector:(SEL)selector
                             userInfo:(id)userInfo
                              repeats:(BOOL)repeats;
- (void)stopTimer;
@end

// LPTimer.m
@interface LPTimer ()
@property (nonatomic, strong) NSTimer *pollTimer;
@property (nonatomic, weak) id weakTarget;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id userInfo;
@end

@implementation LPTimer

- (void)scheduleTimerWithTimeInterval:(NSTimeInterval)interval
                               target:(id)target
                             selector:(SEL)selector
                             userInfo:(id)userInfo
                              repeats:(BOOL)repeats {
    _weakTarget = target;
    _selector = selector;
    _userInfo = userInfo;
    
    __weak typeof(self) weakSelf = self;
    _pollTimer = [NSTimer lp_scheduledTimerWithTimeInterval:interval
                                                        block:^{
        [weakSelf doTimer];
    }
                                                       repeats:repeats];
}

- (void)doTimer {
    if ([_weakTarget respondsToSelector:_selector]) {
        [_weakTarget performSelector:_selector withObject:self];
    } else {
        NSLog(@"WARNING: unknown selector");
        [self stopTimer]; // 目標消失自動停止
    }
}

- (void)stopTimer {
    [_pollTimer invalidate];
    _pollTimer = nil;
}

- (void)dealloc {
    [self stopTimer];
}
@end

關鍵斷環點

  1. `NSTimer` → `self` (LPTimer 類對象) **不 retain 實例**
  2. Block 捕獲 `weakSelf` (LPTimer 實例) **弱引用**
  3. LPTimer 持有 `weakTarget` (外部 VC) **弱引用**
  4. 外部 VC 持有 `LPTimer` (strong) → **單向引用鏈**

使用示例

// ViewController.m
@interface ViewController ()
@property (nonatomic, strong) LPTimer *refreshTimer;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    _refreshTimer = [[LPTimer alloc] init];
    [_refreshTimer scheduleTimerWithTimeInterval:10
                                           target:self
                                         selector:@selector(refreshData)
                                         userInfo:nil
                                          repeats:YES];
}

- (void)refreshData {
    // 請求數據...
}

- (void)dealloc {
    // 無需手動 stopTimer,LPTimer dealloc 會自動 invalidate
    NSLog(@"VC dealloc called"); // 正常打印
}

適配器模式統一接口

為了讓 `LPTimer` 像 `NSTimer` 一樣使用,可實現 `NSTimer` 所有類/實例方法轉發,或提供同名類方法工廠:

+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          target:(id)target
                                        selector:(SEL)selector
                                        userInfo:(id)userInfo
                                         repeats:(BOOL)repeats {
    LPTimer *timer = [[self alloc] init];
    [timer scheduleTimerWithTimeInterval:interval target:target selector:selector userInfo:userInfo repeats:repeats];
    return timer;
}

總結

NSTimer 循環引用本質是 **Target 被強引用**。核心解法:

  1. 將 Timer 的 Target 改為**類對象**(不持有實例)
  2. 用 **Block + weakSelf** 斷開 Timer → 實例 引用
  3. 封裝層持有外部 Target 的 **weak 引用**
  4. 封裝層 `dealloc` 自動 `invalidate`,外部無感知

此方案已在多個線上項目穩定運行,完美解決定時器導致的內存洩漏,推薦作為項目基礎組件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved