NSTimer or DISPATCH_SOURCE_TYPE_TIMER

NSTimer 使用中需要注意的有两点: 防止循环引用添加到对应的runloop, 本次主要关注循环应用的解决方案.

/**
     使用 NSTimer - block方法, 可以通过使用 __weak 打破循环引用, 比较推荐
*/
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%s ---", __func__);
        CGFloat random = arc4random() % 255;
        strongSelf.view.backgroundColor = [UIColor colorWithRed:random / 255.0 green:random / 255.0 blue:random / 255.0 alpha:1];
    }];
/**
     使用 NSTimer - target方法, 可以通过 NSProxy 方法交换来打破循环应用, 需要在 -dealloc 中将timer销毁(invalidate), 也可以在其他合适的时机将timer销毁, 否则报错
     "*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSProxy doesNotRecognizeSelector:__timerAction] called!'"
 */
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:[OPProxy proxyWithTarget:self] selector:@selector(__timerAction) userInfo:nil repeats:YES];

dispath_source_t timer, 我比较推荐使用这种方式. 这种方式是通过内核函数获取时间, 不会受runloop轮询的影响, 时间更加准确. 使用底层函数, 效率更高.

/**
     使用 NSTimer - block方法, 可以通过使用 __weak 打破循环引用, 比较推荐
 */
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%s ---", __func__);
        CGFloat random = arc4random() % 255;
        strongSelf.view.backgroundColor = [UIColor colorWithRed:random / 255.0 green:random / 255.0 blue:random / 255.0 alpha:1];
    }];
/**
     使用 NSTimer - target方法, 可以通过 NSProxy 方法交换来打破循环应用, 需要在 -dealloc 中将timer销毁(invalidate), 也可以在其他合适的时机将timer销毁, 否则报错
     "*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSProxy doesNotRecognizeSelector:__timerAction] called!'"
*/
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:[OPProxy proxyWithTarget:self] selector:@selector(__timerAction) userInfo:nil repeats:YES];

以下是个人封装的工具方法仅供参考:

#import 

NS_ASSUME_NONNULL_BEGIN

@interface OPProxy : NSProxy

@property (weak, nonatomic, readonly) id target;
+ (instancetype)proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END
#import "OPProxy.h"

@interface OPProxy ()

@property (weak, nonatomic, readwrite) id target;

@end

@implementation OPProxy

+ (instancetype)proxyWithTarget:(id)target {
    OPProxy *proxy = [OPProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

@end
#import 

NS_ASSUME_NONNULL_BEGIN

@interface OPTimer : NSObject

+ (NSString *)executTask:(void(^)(void))task
                   start:(NSTimeInterval)start
            timeInterval:(NSTimeInterval)timeInterval
                  repeat:(BOOL)repeat
                   async:(BOOL)async;

+ (NSString *)executTask:(id)target
                selector:(SEL)selector
                   start:(NSTimeInterval)start
            timeInterval:(NSTimeInterval)timeInterval
                  repeat:(BOOL)repeat
                   async:(BOOL)async;

+ (void)cancelTask:(NSString *)taskId;

@end

NS_ASSUME_NONNULL_END
#import "OPTimer.h"
#import "OPProxy.h"

static NSMutableDictionary *__timersDictionary;
dispatch_semaphore_t __timerSemaphore;

@implementation OPTimer

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __timersDictionary = [NSMutableDictionary dictionary];
        __timerSemaphore = dispatch_semaphore_create(1);
    });
}

+ (NSString *)executTask:(void (^)(void))task
                   start:(NSTimeInterval)start
            timeInterval:(NSTimeInterval)timeInterval
                  repeat:(BOOL)repeat
                   async:(BOOL)async {
    if (!task || start < 0 || (timeInterval <= 0 && repeat)) {
        return nil;
    }
    
    dispatch_queue_t queue = async ? dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) : dispatch_get_main_queue();
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, timeInterval * NSEC_PER_SEC, start * NSEC_PER_SEC);
    
    dispatch_semaphore_wait(__timerSemaphore, DISPATCH_TIME_FOREVER);
    NSString *timerKey = [[NSString alloc] initWithFormat:@"%zd", __timersDictionary.count];
    __timersDictionary[timerKey] = timer;
    dispatch_semaphore_signal(__timerSemaphore);
    
    dispatch_source_set_event_handler(timer, ^{
        task();
        if (!repeat) {
            [self cancelTask:timerKey];
        }
    });
    dispatch_resume(timer);
    
    return timerKey;
}

+ (NSString *)executTask:(id)target
                selector:(SEL)selector
                   start:(NSTimeInterval)start
            timeInterval:(NSTimeInterval)timeInterval
                  repeat:(BOOL)repeat
                   async:(BOOL)async {
    if (!target || !selector) {
        return nil;
    }
    
    return [self executTask:^{
        if ([[OPProxy proxyWithTarget:target] respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [[OPProxy proxyWithTarget:target] performSelector:selector];
#pragma clang diagnostic pop
        }
    } start:start timeInterval:timeInterval repeat:repeat async:async];
}

+ (void)cancelTask:(NSString *)taskId {
    if (![taskId length]) {
        return;
    }
    
    dispatch_semaphore_wait(__timerSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_source_t timer = __timersDictionary[taskId];
    if (timer) {
        dispatch_source_cancel(timer);
        [__timersDictionary removeObjectForKey:taskId];
    }
    dispatch_semaphore_signal(__timerSemaphore);
}

@end

你可能感兴趣的:(NSTimer or DISPATCH_SOURCE_TYPE_TIMER)