gpt4 book ai didi

ios - NSInvocation 中的目标应该被释放,但它没有

转载 作者:行者123 更新时间:2023-11-29 12:21:26 24 4
gpt4 key购买 nike

我创建了一个弱目标定时器类,当目标被释放时,定时器不会触发并自动失效。代码是这样的:

//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

ViewControllerdelay 方法中存在一个问题,当self.obj = nil 执行时,_obj 应该被释放,但事实上,它不是,我不知道为什么。除了ViewController中的obj属性,没有强引用,但是为什么不能deallocated。

注意 1:如果我在 timerWithTimeInterval:invocation:repeats: 中删除这行代码:[invocation retainArguments] ;,它将被释放。

注意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 中.我花了一天的时间试图弄清楚但没有用,任何人都可以帮助我吗?谢谢你的时间。

最佳答案

要回答这个问题:行 [_invocation invokeWithTarget:_wtTarget]; 是您设置对 TestObj 目标的额外强引用的地方。

documentation对于 [NSInvocation invokeWithTarget:] 说:

Sets the receiver’s target, sends the receiver’s message (with arguments) to that target, and sets the return value.

在我看来,如果您在 NSInvocation 上调用了 -retainArguments,那么您随后设置了一个新的 target 的实现code>NSInvocation 应该(并且确实)释放其旧的 target 并保留其新的。


这也解释了您在两个笔记中观察到的内容:

Note1: If I remove this line of code: [invocation retainArguments]; in timerWithTimeInterval:invocation:repeats:, it will be deallocated.

从不调用 -retainArgumentsNSInvocation 将不会保留其新的 target

Note2: If I don't schedule the timer in runloop, the target object is deallocated too.

如果您不安排计时器,则永远不会调用 -invokeWithTarget

关于ios - NSInvocation 中的目标应该被释放,但它没有,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30524466/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com