我创建了一个弱目标计时器类,当目标被释放时,计时器不会自动触发并使其自身无效。代码是这样的:
// ViewController.m
@interface TestObj : NSObject
@end
@implementation TestObj
- (id)init
{
self = [super init] ;
if (self) {
NSLog(@"%@ %@", self, NSStringFromSelector(_cmd)) ;
}
return self ;
}
- (void)dealloc
{
NSLog(@"%@ %@", self, NSStringFromSelector(_cmd)) ;
}
- (void)timerFiredForInvocation:(id)obj
{
NSLog(@"%@, %@", obj, NSStringFromSelector(_cmd)) ;
}
@end
@interface ViewController ()
@property (nonatomic, strong) WTTimer *timer1 ;
@property (nonatomic, strong) WTTimer *timer2 ;
@property (nonatomic, strong) TestObj *obj ;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_obj = [[TestObj alloc] init] ;
NSMethodSignature *methodSig = [_obj methodSignatureForSelector:@selector(timerFiredForInvocation:)] ;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig] ;
invocation.target = _obj ;
invocation.selector = @selector(timerFiredForInvocation:) ;
id objArgument = [[TestObj alloc] init] ;
[invocation setArgument:&objArgument atIndex:2] ;
_timer2 = [WTTimer scheduledTimerWithTimeInterval:2.0 invocation:invocation repeats:YES] ;
NSLog(@"timer is scheduled") ;
// delay to set self.obj to nil and make it be deallocated
[self performSelector:@selector(delay) withObject:nil afterDelay:3.0] ;
}
- (void)delay
{
NSLog(@"%@ %@", self, NSStringFromSelector(_cmd)) ;
self.obj = nil ;
}
@end
// WTTimer.m
@class TimerDelegateObject ;
@protocol WTTimerDelegate <NSObject>
- (void)wtTimerFired:(TimerDelegateObject *)obj ;
@end
@interface TimerDelegateObject : NSObject
@property (nonatomic, weak) id<WTTimerDelegate> delegate ;
- (void)timerFired:(NSTimer *)timer ;
@end
@implementation TimerDelegateObject
- (void)timerFired:(NSTimer *)timer
{
[_delegate wtTimerFired:self] ;
}
@end
@interface WTTimer () <WTTimerDelegate>
@property (nonatomic, strong) NSTimer *timer ;
// target and selector
@property (nonatomic, weak) id wtTarget ;
@property (nonatomic) SEL selector ;
// for NSInvocation
@property (nonatomic, strong) NSInvocation *invocation ;
@end
@implementation WTTimer
- (instancetype)initWithFireDate:(NSDate *)date
interval:(NSTimeInterval)seconds
target:(id)target
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
{
self = [super init] ;
if (self) {
_timer = [[NSTimer alloc] initWithFireDate:date interval:seconds target:target selector:aSelector userInfo:userInfo repeats:repeats] ;
}
return self ;
}
+ (WTTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo
{
TimerDelegateObject *delegateObj = [[TimerDelegateObject alloc] init] ;
NSDate *dateFire = [NSDate dateWithTimeIntervalSinceNow:ti] ;
WTTimer *timer = [[WTTimer alloc] initWithFireDate:dateFire
interval:ti
target:delegateObj
selector:@selector(timerFired:)
userInfo:nil
repeats:yesOrNo] ;
delegateObj.delegate = timer ;
// config WTTimer
timer.wtTarget = invocation.target ; // timer.wtTarget is weak
invocation.target = delegateObj ;// I change the target to delegateObj, so [invocation retainArguments] won't retain the original target
[invocation retainArguments] ;
timer.invocation = invocation ;
return timer ;
}
+ (WTTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo
{
WTTimer *timer = [WTTimer timerWithTimeInterval:ti invocation:invocation repeats:yesOrNo] ;
if (timer) {
[[NSRunLoop currentRunLoop] addTimer:timer.timer forMode:NSDefaultRunLoopMode] ;
}
return timer ;
}
- (void)wtTimerFired:(TimerDelegateObject *)obj
{
if (_wtTarget) {
if (_invocation) {
[_invocation invokeWithTarget:_wtTarget] ;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_wtTarget performSelector:_selector withObject:self] ;
#pragma clang diagnostic pop
}
} else {
// the target is deallocated, the timer should be invalidated
[self.timer invalidate] ;
NSLog(@"the target is deallocated, invalidate the timer") ;
}
}
- (NSDate *)fireDate
{
return [_timer fireDate] ;
}
- (void)setFireDate:(NSDate *)fireDate
{
_timer.fireDate = fireDate ;
}
- (NSTimeInterval)timeInterval
{
return [_timer timeInterval] ;
}
- (void)fire
{
return [_timer fire] ;
}
- (void)invalidate
{
[_timer invalidate] ;
}
- (BOOL)isValid
{
return [_timer isValid] ;
}
- (id)userInfo
{
return _timer.userInfo ;
}
@end
在delay
执行ViewController
时self.obj = nil
方法存在一个问题,_obj
应该被解除分配,但事实上,它不是,我不知道为什么。除了ViewController中的obj属性外,没有强引用,但为什么它不能被释放。
注意1:如果我在[invocation retainArguments] ;
中删除了这行代码:timerWithTimeInterval:invocation:repeats:
,则会将其解除分配。
注意2:如果我没有在runloop中安排计时器,那么目标对象也会被解除分配。
+ (WTTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo
{
WTTimer *timer = [WTTimer timerWithTimeInterval:ti invocation:invocation repeats:yesOrNo] ;
if (timer) {
// [[NSRunLoop currentRunLoop] addTimer:timer.timer forMode:NSDefaultRunLoopMode] ;
}
return timer ;
}
如果您对此感兴趣,请将代码发布在https://github.com/kudocc/WTTimer中。我花了一天时间试图解决这个问题,但没有好处,任何人都可以帮助我吗?谢谢你的时间。
答案 0 :(得分:1)
要回答这个问题:第[_invocation invokeWithTarget:_wtTarget];
行是您为TestObj
目标设置超强引用的地方。
[NSInvocation invokeWithTarget:]
的{{3}}说:
设置接收者的目标,将接收者的消息(带参数)发送到该目标,并设置返回值。
在我看来,如果您在-retainArguments
上拨打了NSInvocation
,那么您随后设置了新的target
,NSInvocation
的实施应该(并且确实)发布它的旧target
并保留其新的。
这也解释了你在两个笔记中观察到的内容:
注意1:如果我在
[invocation retainArguments];
中删除了这行代码:timerWithTimeInterval:invocation:repeats:
,则会将其解除分配。
从不致电-retainArguments
,NSInvocation
将不会保留新的target
。
注意2:如果我没有在runloop中安排计时器,那么目标对象也会被释放。
如果您不安排计时器,则永远不会调用-invokeWithTarget
。