NSTimer Category + Блокирует реализацию для замены селектора - PullRequest
3 голосов
/ 05 июля 2011

Я довольно новичок в блоках и target-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'

Как я могу заставить эту категорию работать?

Ответы [ 3 ]

4 голосов
/ 05 июля 2011

Ваш главный недостаток помимо неверной цели - использование статической переменной.Вы не сможете поддерживать больше одного таймера.

Использование блока в качестве аргумента для вызываемого метода.

@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

Проблема с использованием ассоциативных ссылок заключалась в утечке, поскольку не было смысла освобождать блок.


Ранний подход с использованием ассоциативных ссылок

Вы можете использовать associative references, чтобы прикрепить блок к этому конкретному экземпляру 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
2 голосов
/ 27 июня 2012

Как насчет использования 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;
};
1 голос
/ 05 июля 2011

Это должно работать:

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

Проблема в том, что вы устанавливаете цель нового экземпляра NSTimer равной self.Однако в контексте + scheduleTimerWithTimeInterval:repeats:actions: (обратите внимание на +), self - это NSTimer, а не (как вы, вероятно, думали) ваш вновь созданный экземпляр NSTimer.

Когда выИз сообщения об ошибке видно, что ваше приложение падает, поскольку NSTimer не отвечает на метод класса + theBlock, что, конечно, правильно, поскольку вы определили только метод экземпляра - theBlock.

...