`NSTimer` 的 `scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:` 會 **retain 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.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
// 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 被強引用**。核心解法:
此方案已在多個線上項目穩定運行,完美解決定時器導致的內存洩漏,推薦作為項目基礎組件。