NSTimer 类别 +阻止实现以替换选择器

发布于 2024-11-18 08:42:06 字数 1561 浏览 3 评论 0原文

我对块和 Objective-C 很陌生,我正在尝试使用两者来编写我的第一个类别。我的想法是在 NSTimer 上创建一个类别,它将接收一个块作为参数,并且该块将在选择器调用中使用。现在我有这个。

// NSTimer+Additions.h

#import <Foundation/Foundation.h>

typedef void (^VoidBlock)();

@interface NSTimer (NSTimer_Additions)


+ (NSTimer *)scheduleTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions;
@end

#import "NSTimer+Additions.h"

static VoidBlock _voidBlock;

@interface NSTimer (AdditionsPrivate) // Private stuff
- (void)theBlock;
@end


@implementation NSTimer (NSTimer_Additions)


+ (NSTimer *)scheduleTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {

    [_voidBlock release];
    _voidBlock = [actions copy];

    NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date] 
                                          interval:theSeconds
                                            target:self 
                                          selector:@selector(theBlock) 
                                          userInfo:nil 
                                           repeats:repeats];
    [timer fire];

    return [timer autorelease];
}


- (void)theBlock {
    _voidBlock();
}

@end

代码要点: https://gist.github.com/1065235

一切都编译正常,但我有以下错误:

2011-07-05 14:35:47.068 TesteTimer[37716:903] * 由于以下原因终止应用程序未捕获的异常 'NSInvalidArgumentException',原因:'+[NSTimer theBlock]:无法识别的选择器发送到类 0x7fff70bb0a18'

我怎样才能使这个类别工作?

I am quite new to blocks and objective-c, and i am trying to write my first category using both. My idea is to create a category on NSTimer that will receive a block as a parameter and this block will be used in the selector call. Right now I have this.

// NSTimer+Additions.h

#import <Foundation/Foundation.h>

typedef void (^VoidBlock)();

@interface NSTimer (NSTimer_Additions)


+ (NSTimer *)scheduleTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions;
@end

#import "NSTimer+Additions.h"

static VoidBlock _voidBlock;

@interface NSTimer (AdditionsPrivate) // Private stuff
- (void)theBlock;
@end


@implementation NSTimer (NSTimer_Additions)


+ (NSTimer *)scheduleTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {

    [_voidBlock release];
    _voidBlock = [actions copy];

    NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date] 
                                          interval:theSeconds
                                            target:self 
                                          selector:@selector(theBlock) 
                                          userInfo:nil 
                                           repeats:repeats];
    [timer fire];

    return [timer autorelease];
}


- (void)theBlock {
    _voidBlock();
}

@end

Gist for the code: https://gist.github.com/1065235

Everything compiles fine but i have the following error:

2011-07-05 14:35:47.068 TesteTimer[37716:903] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSTimer theBlock]: unrecognized selector sent to class 0x7fff70bb0a18'

How can I make this category work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

番薯 2024-11-25 08:42:06

除了错误的目标之外,您的主要缺陷是您使用了静态变量。您将无法支持超过一个计时器。

使用块作为调用方法的参数。

@interface NSTimer (AdditionsPrivate) // Private stuff
- (void)theBlock:(VoidBlock)voidBlock;
@end


@implementation NSTimer (NSTimer_Additions)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:@selector(theBlock:)]];
    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:theSeconds
                                                   invocation:invocation
                                                      repeats:repeats];
    [invocation setTarget:timer];
    [invocation setSelector:@selector(theBlock:)];
    
    Block_copy(actions);
    [invocation setArgument:&actions atIndex:2];
    Block_release(actions);

    return timer;
}


- (void)theBlock:(VoidBlock)voidBlock {
    voidBlock();
}

@end

使用关联引用的问题是泄漏,因为没有释放块的好点。


使用关联引用的早期方法

您可以使用 关联引用将块附加到 NSTimer 的特定实例。

@implementation NSTimer (NSTimer_Additions)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:@selector(theBlock)]];
    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:theSeconds
                                                   invocation:invocation
                                                      repeats:repeats];
    [invocation setTarget:timer];
    [invocation setSelector:@selector(theBlock)];

    objc_setAssociatedObject(timer, @"Block", actions, OBJC_ASSOCIATION_COPY);
    
    return timer;
}


- (void)theBlock {
    VoidBlock _voidBlock = (VoidBlock)objc_getAssociatedObject(self, @"Block");
    _voidBlock();
}

@end

Your major flaw besides the wrong target is your use of a static variable. You won't be able to support beyond a single timer.

Using block as argument to the invoked method.

@interface NSTimer (AdditionsPrivate) // Private stuff
- (void)theBlock:(VoidBlock)voidBlock;
@end


@implementation NSTimer (NSTimer_Additions)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:@selector(theBlock:)]];
    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:theSeconds
                                                   invocation:invocation
                                                      repeats:repeats];
    [invocation setTarget:timer];
    [invocation setSelector:@selector(theBlock:)];
    
    Block_copy(actions);
    [invocation setArgument:&actions atIndex:2];
    Block_release(actions);

    return timer;
}


- (void)theBlock:(VoidBlock)voidBlock {
    voidBlock();
}

@end

The problem with using associative references was the leak as there was no good point to release the block.


Earlier Approach using associative references

You can use associative references to attach the block to that particular instance of NSTimer.

@implementation NSTimer (NSTimer_Additions)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:@selector(theBlock)]];
    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:theSeconds
                                                   invocation:invocation
                                                      repeats:repeats];
    [invocation setTarget:timer];
    [invocation setSelector:@selector(theBlock)];

    objc_setAssociatedObject(timer, @"Block", actions, OBJC_ASSOCIATION_COPY);
    
    return timer;
}


- (void)theBlock {
    VoidBlock _voidBlock = (VoidBlock)objc_getAssociatedObject(self, @"Block");
    _voidBlock();
}

@end
放低过去 2024-11-25 08:42:06

利用 userInfo 来承载你的区块怎么样? (这是用ARC完成的)

void (^callback)(void) = ^{
    NSLog(@"do stuff");
}

NSTimer *timer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(handleTimeout:) userInfo:[NSDictionary dictionaryWithObject:[callback copy] forKey:@"block"] repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

然后添加静态选择器:

+ (void)handleTimeout:(NSTimer *)timer
{
    void (^callback)(void) = [timer.userInfo objectForKey:@"block"];
    callback();

    [timer invalidate];
    timer = nil;
};

What about leveraging userInfo to carry your block? (this is done with ARC)

void (^callback)(void) = ^{
    NSLog(@"do stuff");
}

NSTimer *timer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(handleTimeout:) userInfo:[NSDictionary dictionaryWithObject:[callback copy] forKey:@"block"] repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

And then add the static selector of:

+ (void)handleTimeout:(NSTimer *)timer
{
    void (^callback)(void) = [timer.userInfo objectForKey:@"block"];
    callback();

    [timer invalidate];
    timer = nil;
};
给不了的爱 2024-11-25 08:42:06

这应该可行:

NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date] 
                                          interval:theSeconds
                                            target:timer
                                          selector:@selector(theBlock) 
                                          userInfo:nil 
                                           repeats:repeats];

问题是您将新 NSTimer 实例的目标设置为 self。但是,在 + ScheduleTimerWithTimeInterval:repeats:actions: 的上下文中(注意 +),selfNSTimer ,而不是(正如您可能认为的那样)新创建的 NSTimer 实例。

从错误消息中可以看出,您的应用程序崩溃是因为 NSTimer 没有响应类方法 + theBlock,这当然是正确的,因为您只定义了实例方法- theBlock

This should work:

NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date] 
                                          interval:theSeconds
                                            target:timer
                                          selector:@selector(theBlock) 
                                          userInfo:nil 
                                           repeats:repeats];

The problem is that you're setting the target of the new NSTimer instance to be self. However, in the context of + scheduleTimerWithTimeInterval:repeats:actions: (notice the +), self is NSTimer, and not (as you probably thought) your newly-created NSTimer instance.

As you can see from the error message, your app is crashing because NSTimer doesn't respond to the class method + theBlock, which is of course correct since you only defined the instance method - theBlock.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文