iOS 如何使用 NSTimer 以及 runloop 和 NSTimer 的关系

时间:2022-09-24 20:33:37

这几天在研究 RunLoop,记录一下runloop 和 NSTimer 的关系,毕竟这个使我们比较常用的.

关于 runloop 参考资料很多, 我看的是 ibireme (郭耀源)的博客,国内开源大牛,程序员的楷模,他的博客链接


在 ios 系统中,每启动一个线程,都会跟一个对应的 runloop,runloop 默认是关闭的 需要我们手动获取,设置并启动,(详细参考上面的博客),主线程例外,系统自动为主线程启动一个 runloop 并配置完毕.这里我们不管,主要看unloop 和 NSTimer 的关系, 如何正确使用 NSTimer.


 NSTimer 的创建方法

        NSTimer *time1 = [NSTimer alloc]initWithFireDate:<#(nonnull NSDate *)#> interval:<#(NSTimeInterval)#> repeats:<#(BOOL)#> block:<#^(NSTimer * _Nonnull timer)block#>;

NSTimer *timer2 = [NSTimer alloc]initWithFireDate:<#(nonnull NSDate *)#> interval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>;

NSTimer *timer3 = [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>];

NSTimer *timer4 = [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> invocation:<#(nonnull NSInvocation *)#> repeats:<#(BOOL)#>];

NSTimer *timer5 = [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> repeats:<#(BOOL)#> block:<#^(NSTimer * _Nonnull timer)block#>];

首先所有的 timer 必须要加入 runloop 中才能生效.

然后使用 timer 要尤其注意释放时机,否则很容易导致内存泄露或者其他麻烦.

    /* 关于 timer1 在主线程中使用,
* 在没有加入 runloop 的情况下, 可以使用 fire 立即执行一次, 但是对于设置的repeat:YES 就没有效果了,block 只会执行一次
* 如果 repeat:NO 那么 timer 在执行一次之后会自动释放, [timer fire]之后,再次调用[timer fire] 就没有效果了
* 再加入 runloop 的情况下, 把 timer 加入 runloop 就会执行. 因为主线程的 runloop 一直在运行,所以我们主要获取 runloop
* 在加入定时器就好. 不需要调用 [runloop run]
* 加入 runloop 之后,repeat:NO时,定时器执行一次就会释放当前 VC,repeat:YES 当前 VC 被持有,必须显示执行[_timer1 invalidate]
* 释放 timer1才能释放 VC, 否则妥妥的内存泄露
*/
self.timer1 = [[NSTimer alloc]initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"执行 timer1 程序");
}];
// [self.timer1 fire];

NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer1 forMode:(NSDefaultRunLoopMode)];
// [runloop run];
// [_timer1 invalidate];

    /* timer1 在子线程中使用
*
* 使用情况和在主线程中是一样的,要注意的是 加入当前 runloop 的时候,要启动 runloop, 因为子线程的 runloop 默认是关闭的
*
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

self.timer1 = [[NSTimer alloc]initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 repeats: YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"执行 timer1 程序");
}];

NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer1 forMode:(NSDefaultRunLoopMode)];
[runloop run];
// [self.timer1 fire];
});

    
/* 关于 timer2 在主线程中使用,
* 在没有加入 runloop 的情况下, 可以使用 fire 立即执行一次, 但是对于设置的repeat:YES 就没有效果了,timerAction 只会执行一次

*You must add the new timer to a run loop, using addTimer:forMode:. Upon firing, the timer sends the message aSelector to target. (If the timer is configured to repeat, there is no need to subsequently re-add the timer to the run loop.)
*但是官方推荐使用这个方法我们必须要加入 runloop ,其实正常使用确实是要你加入 runloop 的

* 参数 repeat: If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
* 如果 repeat:NO 那么 timer 在执行一次之后会自动释放, [timer fire]之后,再次调用[timer fire] 就没有效果了

* 参数 userInfo: Custom user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.
* 该方法允许 timer 携带一个参数 userInfo(字典),里面可以包含我们想要携带的参数,可以为 nil,但是要注意的是 这个定时器对当前对应也是强持有

* 再加入 runloop 的情况下, 把 timer 加入 runloop 就会执行. 因为主线程的 runloop 一直在运行,所以我们主要获取 runloop
* 在加入定时器就好. 不需要调用 [runloop run]
* 加入 runloop 之后,repeat:NO时,定时器执行一次就会释放当前 VC,repeat:YES 当前 VC 被持有,必须显示执行[_timer1 invalidate]
* 释放 timer1才能释放 VC, 否则妥妥的内存泄露


* 子线程使用 注意事项是一样的 参考一下timer1就知道了

*/
self.timer2 = [[NSTimer alloc]initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
//self.timer2.userInfo
// [self.timer2 fire];

NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer2 forMode:(NSDefaultRunLoopMode)];
// [runloop run];
// [_timer2 invalidate];

    
/* 这三种方法 根上面的区别就是 创建 timer 并且自动加到当前的 runloop 当中. 所以在主线程里可以直接运行
* 子线程中 子线程 runloo 启动就可以执行 timer
* 注意事项 和 前面两个情况是一样的 repeat:YES 要注意合适的时机显示 失效 timer 否则注意内存泄露
*
* 这里注意下 NSInvocation 的用法, timer4的参数 就是这个
*
*/
iOS 如何使用 NSTimer  以及 runloop 和 NSTimer 的关系

self.timer3 = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"执行 timer3 程序");
}];
NSTimer *timer4 = [NSTimer scheduledTimerWithTimeInterval:1 invocation:nil repeats:YES];

NSTimer *timer5 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSTimer *timer5 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
// 自动加入当前的 runloop 只需要获取当前 runloop 并启动 即可
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop run];
});

注意 NSInvocation 的使用

iOS 如何使用 NSTimer  以及 runloop 和 NSTimer 的关系

NSInvocation 用法
    //NSInvocation;用来包装方法和对应的对象,它可以存储方法的名称,对应的对象,对应的参数,    /*     NSMethodSignature:签名:再创建NSMethodSignature的时候,必须传递一个签名对象,签名对象的作用:用于获取参数的个数和方法的返回值     */    //创建签名对象的时候不是使用NSMethodSignature这个类创建,而是方法属于谁就用谁来创建        NSMethodSignature*signature = [SSSViewController instanceMethodSignatureForSelector:@selector(sendMessageWithNumber:WithContent:)];    //1、创建NSInvocation对象    NSInvocation*invocation = [NSInvocation invocationWithMethodSignature:signature];    invocation.target = self;    //invocation中的方法必须和签名中的方法一致。    invocation.selector = @selector(sendMessageWithNumber:WithContent:);    /*第一个参数:需要给指定方法传递的值     第一个参数需要接收一个指针,也就是传递值的时候需要传递地址*/    //第二个参数:需要给指定方法的第几个参数传值    NSString*number = @"1111";    //注意:设置参数的索引时不能从0开始,因为0已经被self占用,1已经被_cmd占用    [invocation setArgument:&number atIndex:2];    NSString*number2 = @"啊啊啊";    [invocation setArgument:&number2 atIndex:3];    //2、调用NSInvocation对象的invoke方法    //只要调用invocation的invoke方法,就代表需要执行NSInvocation对象中制定对象的指定方法,并且传递指定的参数//    [invocation invoke];                /*     * After ti seconds have elapsed, the timer fires, invoking invocation.     * 当 timer到了执行点的时候 会 invoking invocation. 触发 invocation.里面的方法     * 使用 invocation. 就可以执行 我们自定义的方法 ,携带多个自定义的参数     */    NSTimer *timer4 = [NSTimer scheduledTimerWithTimeInterval:1 invocation:(invocation) repeats:YES];