betway必威手机版 > betway必威手机版 > NSTimer的内存泄漏,iOS10定时消息的改动

原标题:NSTimer的内存泄漏,iOS10定时消息的改动

浏览次数:113 时间:2020-03-25

图片 1赢得对象的援用计数

装有会产生内部存款和储蓄器泄漏难题。能够由此以下二中艺术消除:

4、子线程中动用NSTimer的坑

iOS10早就发表了一段时间,iOS10的各个适配相信大家已经产生。本文将汇报的是有关iOS10内核的一个小改造,惯例,本文归于进级性技能文,不会讲课API的选取,须求读者对RunLoop有早晚的认识,谢谢网民@送你的独白么 提供的SDK。

但在iOS10碰到下,当机械漏刻被移除后,内核不再向对应的Timer Port发送任何时限信号,所以子线程RunLoop从来处于休眠状态并不曾退出,而小编辈只要求手动唤醒RunLoop就能够。

5、其他

  • NS提姆er不协助暂停和世袭
  • NSTimer不支持后台运营(真机),但是模拟器上App进入后台的时候,NSTimer还有只怕会每每触发。真机踏向后台timer会停。

仿照效法小说:
http://www.jianshu.com/p/7045813769fd

从出口的音信大家获悉,成立子线程后,Target会被劫持保留,直到子线程的主函数再次回到。援用计数在大多时候能够帮忙大家精晓内部存款和储蓄器的运用境况,但在ARC编写翻译情形下,我们爱莫能助直接选取retainCount方法来获取一个目的的援用计数,所以,大家要求做额外的拍卖。

(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo,

  • 主导采纳
  • NSTimer的强援用难题
  • 不准时
  • iOS10中的退换
    中间会涉嫌到有的runloop的学识,这里不会其它去讲,在自己在此以前写的一篇runloop的文章中早就谈到过,有供给的能够看看。

图片 2页面B代码图片 3iOS10图片 4iOS9

在类型中,常用的反应计时器有NSTimer,CADisplayLink,GCD Timer。而利用越来越多的是NSTimer。

3、NS提姆er强援引引起的内部存款和储蓄器难点。

@property (nonatomic ,strong)NSTimer *timer;
- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
}

- (void)timerMethod{
    NSLog(@"timer2 run");
}

运营方面这段代码,如若从那一流VC pop回上顶级VC,timer still running!!

图片 5

强援引暗中表示图

runloop强援用timer,timer强援引target对象。要去掉那三种强援引就要调用invalidate方法。

关于invalidate方法
invalidate方法有2个功能:
1、将timer从runloop中移除
2、timer本身也会放出它装有财富,譬如target、userinfo、block。
其后的timer也就永恒无效了,要重新使用timer就要重新创建。
timer唯有那三个主意能够产生此操作,所以大家撤废三个timer必定要调用此方法。(在抬高到runloop前,可以应用它的getter方法isValid来判别,叁个是严防为nil,另三个是谨防为无用)

NSTimer 在哪个线程创立将在在哪些线程停止,不然会形成财富不可能被科学的释放。由此invalidate主意必需在timer增加到的runloop所在的线程中调用。
ps:在英特网看好些个本领文,[timer invalidate]timer = nil;身处一同行使,笔者感觉仅仅调用invalidate情势就足足消除难题了。

在vc 的dealloc办法中调用invalidate

- (void)dealloc{
    NSLog(@"销毁了");
    [self.timer invalidate];
}

结果恐怕长久以来的!不能够走到dealloc方法。
因为timer对view controller的强引用,诱致vc不能释放,也就不或者走到dealloc方法了。(即便timer属性是weak,结果是走不到dealloc,只不过vc(selfState of Qatar和timer之间不再有保留环)

那正是说加个开关方法:

- (IBAction)invalidateButtonPressed:(id)sender {
    [self.timer invalidate];
}

恩!先点击开关,然后再pop回上一级VC,那时就足以走到dealloc办法了。但是如此并不美观。

难点的根本是self(vc)被timer强援用,那么target不是self(vc)不就能够了呢?

#import "NSTimer Addition.h"
@implementation NSTimer (Addition)
  (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats{
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(blockInvoke:)
                                       userInfo:[block copy]
                                        repeats:repeats];
}

  (void)blockInvoke:(NSTimer *)timer {
    void (^block)() = timer.userInfo;
    if(block) {
        block();
    }
}
@end

vc:
- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 block:^{
        NSLog(@"timer2 run");
    } repeats:YES];
}

- (void)dealloc{
    NSLog(@"销毁了");
    [self.timer invalidate];
}

归来上级VC,可以走到dealloc

图片 6

此间运用的是NSTimer分拣作为target,还动用了block(也要专心block形成的巡回引用难点,倘使block捕获了self,而timer又通过userInfo持有block,最后self自个儿又不无timer就能够形成保留环)。这里真的创制timer实例的地点是在NSTimerCategory中,而且target也是NSTimerNSTimer持有timer实例,timer实例持有NSTimer,仍有轮回援用的。要想打破上述循环引用,要求在开立timer的类(非NSTimer)中对timer进行invalidate

另一种创混入假的target的写法,本质上或许同样的

当咱们的前后相继需求准时管理局地事变时,大家就能够用到计时器,常用的停车计时器有NSTimer,CADisplayLink,GCD Timer,本文重要针对NSTimer和CADisplayLink举办描述,因为这两者跟你的Application更为紧凑。

1、获取当前runloop对象, CFRunLoopGetCurrent(State of Qatar;沙漏是在子线程RunLoop中注册的,但机械漏刻的移除操作却是在主线程,由于子线程RunLoop管理完三次准时时域信号后,就可以进来休眠状态。在iOS10在此以前的情状下,反应计时器被移除后,内核还是会向对应的Timer Port发送一遍频域信号,所以子线程RunLoop采用到实信号后会被唤起,由于未有定期源须求管理,所以RunLoop会直接跳转到决断阶段,判定阶段会检查评定当前RunLoopMode是还是不是有事件源须要管理,若未有事件源要求管理,则会退出RunLoop。由于子线程RunLoop的脚下RunLoopMode独有二个定时器,而机械漏刻被移除后,RunLoopMode就平昔不了特殊需求管理的事件源,所以会退出RunLoop,子线程的主函数也为此回到, 在点击再次回到的时候唤醒runloop对象。

情形一:

A界面push步向B分界面,在B中创设子线程,子线程中创设timer、开启runloop;B上的按键用来刑释timer,点击B导航栏再次来到开关再次回到A。

@property (nonatomic ,weak)NSTimer *timer;
@property (nonatomic )CFRunLoopRef runloop;
@property (nonatomic ,weak)NSThread *thread;
@property (nonatomic )CFRunLoopObserverRef observer;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 80, 50, 50)];
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(clicked) forControlEvents:UIControlEventTouchUpInside];

    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod) object:nil];
    self.thread = thread;
    [self.thread start];
}

- (void)timerMethod{
    NSLog(@"timer2 run");
}
- (void)dealloc{
    NSLog(@"销毁了");
// CFRelease(self.observer);
}

- (void)clicked{
  [self.timer invalidate];
}

- (void)threadMethod{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    self.runloop = CFRunLoopGetCurrent();

    self.observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
    });
    CFRunLoopAddObserver(self.runloop, self.observer, kCFRunLoopDefaultMode);

    CFRunLoopRun();
    CFRelease(self.observer);
}

这段代码在iOS10、iOS9意况下运营结果不太近似。

图片 7

iOS9

图片 8

iOS10

iOS10条件下,从B重回A,B不会被假释(不可能走到dealloc)。从运营结果看来,iOS10中子线程runloop最后直接处在休眠状态。

分析:
在B中开创了二个子线程,通过NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod) object:nil];,子线程会对target也正是self(B调整器)举办强援引,那是B不能自由的缘由。要自由B将在退出子线程,也正是要退出子线程的runloop。所以难点大概正是iOS9、iOS10在管理子线程runloop上有不一致。

参照小说第一篇讲到:

若目的RunLoop当前还未定期源须要管理(像上边的例证这样,子线程RunLoop唯有一个放大计时器,该停车计时器移除后,则子线程RunLoop未有准时源要求管理),则文告内核无需再向当前提姆er Port发送依期消息并移除该提姆er Port。在iOS10意况下,当移除Timer Port后,内核会把新闻列表中与该提姆er Port相应的准时信息移除,而iOS10在先的境况下,当移除Timer Port后,内核不会把音信列表中与该Timer Port相应的定期音信移除。iOS10的拍卖是尤为客观的,iOS10在先的管理可能是历史遗留难题吧。

事例中涉嫌到线程异步的题目,放大计时器是在子线程RunLoop中注册的,但电火花计时器的移除操作却是在主线程,由于子线程RunLoop管理完叁次定时实信号后,就能够步入休眠状态。在iOS10原先的条件下,放大计时器被移除后,内核依然会向对应的Timer Port发送三遍频限信号,所以子线程RunLoop选拔到非能量信号后会被唤起,由于尚未准期源需求处理,所以RunLoop会直接跳转到推断阶段,判定阶段会检查测验当前RunLoopMode是还是不是有事件源需求管理,若没有事件源供给管理,则会退出RunLoop。鉴于例子中子线程RunLoop的当前RunLoopMode独有二个机械漏刻,而反应计时器被移除后,RunLoopMode就从未了急需管理的事件源,所以会退出RunLoop,子线程的主函数也由此回到,页面B对象被放飞。

但在iOS10碰到下,当放大计时器被移除后,内核不再向对应的Timer Port发送任何数字信号,所以子线程RunLoop从来处于休眠状态并未脱离,而我辈只供给手动唤醒RunLoop就可以。

从上面iOS9运作结果图来看,红框的两处年华差恰还好一秒左右。(作者点击开关的年华在结尾一回休眠和最终一回提醒之间,在这里之间timer被移除)

图片 9

iOS9

相比较iOS10周转结果(点击开关的风云也是在结尾一遍休眠之后),确实能够得出结论:iOS9条件下,timer移除后,内核确实向timer port再一次发送了实信号使得子线程runloop唤醒,最终runloop由于还未mode item而退出。

为此也即:

- (void)clicked{
    [self.timer invalidate];
    CFRunLoopWakeUp(self.runloop);
}

手动唤醒runloop,那样改换现在的运转结果:

图片 10

iOS10

又或然是,不采取CFRunLoopWakeUp而直白用CFRunLoopStop( )来退出runloop。因为运用CFRunLoopWakeUp,也便是是让runloop依赖当前runloop mode有未有事件源来决定是不是退出。而这种艺术自个儿就不是可怜可信赖,因为系统也是有相当大希望给runloop增多一些事件源,引致runloop不肯定会脱离。

ps:一些题外话。是有个别自己思路改进,写出来是为着给协和之后看的。各位看官能够跳过那有个别~
在最伊始写完那笔记之后的几天又翻出这段代码来看。大约是心血短路吧..曾经以为下面代码中的开关点击是贰个子线程runloop source0。。。还做了下边一张图剖析。。。(大约犯蠢没看清按键事件的机会)

图片 11

唯独非常的慢就意识到那哪儿是怎么着子线程source0....子线程runloop没有source0唯有timer和observer(明明事情未发生前本人还nslog出来过)!这几个按钮事件是主线程的嘛!
然后在误打误撞的情景下...小编在主线程runloop又加多了三个observer对主线程runloop状态实行监听,代码很简短小编就不贴了。从A步向到B,什么都毫无做,等待main runloop牢固下来(一同来main runloop很活泼,最终稳固下来就是休眠了,只剩下子线程runloop状态在调整台有出口,如下图)。在本身点击按键之后,main runloop唤醒,iOS10中子线程同上最后直接处在休眠状态。

图片 12

iOS10测试

要唤醒runloop休眠有这么三种状态:基于端口的输入源到达(source1)、timer唤醒、runloop超时时间到、人为手动唤醒runloop。

因为直接感觉点击按键时这一表现是二个source0,所以对主线程runloop唤醒感觉意外。然后再一次看回深深了解runloop那篇文章,开采存那样一个Q&A:

Q:还会有二个标题哈,正是UIButton点击事件打印货仓看的话是从source0调出的,文中说的是source1事件,不领会哪个是千真万确的吗?
A:首先是由特别Source1 接受IOHIDEvent,之后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触及的_UIApplicationHandleEventQueue()。所以UIButton事件来看是在 Source0 内的。你能够在 __IOHIDEventSystemClientQueueCallback 处下一个 Symbolic Breakpoint 看一下。

遵从笔者的答疑,做了测验,开采真正是那样的。所以主线程的唤醒是出于source1事件。

图片 13

aTarget是强援用,The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer卡塔尔国 is invalidated.

1、基本使用

创建timer的方法:

//把创建timer并把它添加到当前线程runloop中,模式是默认的default mode
  (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//和上面的方法作用差不多,但不会把timer自动添加到runloop中,需要人手动加
  (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

参数表明:

  • ti:定时器触发间隔时间,单位为秒,能够是小数。
  • aTarget:发送消息的对象,timer会强援引aTarget,直到调用invalidate方法。
  • aSelector:将在发送给aTarget的新闻,能够不带参,倘使含有参数则应把timer作为参数字传送递过去:- (void)timerFireMethod:(NSTimer *)timer
  • userInfo:传递的客户新闻,timer对此进行强引用。
  • yesOrNo:是或不是再次。如若是YES则再度触发,直到调用invalidate方法;借使是NO,则只触发叁遍就活动调用invalidate方法。

比如:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
}

- (void)timerMethod{
    NSLog(@"timer2 run");
}

timer要增多到runloop才有效,由此运转要知足多少个标准:1.当下线程的runloop存在,2.timer加多到runloop,3.runloop mode要适配。
比方说在子线程中选择NSTimer:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 80, 50, 50)];
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(clicked) forControlEvents:UIControlEventTouchUpInside];

    [NSThread detachNewThreadSelector:@selector(threadMethod) toTarget:self withObject:nil];
}

- (void)threadMethod{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    CFRunLoopRun();
}

- (void)clicked{
    [self.timer invalidate];
    [self.navigationController popViewControllerAnimated:YES];
}

假定要runloop更改格局,调用三遍addTimer:forMode:艺术就足以了:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

其余的NSTimer早先化方法大同小异就不开展了。

CFRunLoopTimer是RunLoop的准期源,与Source1相通,都属于端口事件源,但不相同的是,每三个Source1都有与之对应的端口,而贰个RunLoopMode中的全部CFRunLoopTimer共用二个端口(Mode Timer Port卡塔尔(قطر‎,CFRunLoop提姆er在RunLoop中的职业原理如下图。

在创立timer的类方法中:

情形三

让子线程timer计数一次就告一段落

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    count = 0;

    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod) object:nil];
    self.thread = thread;
    [self.thread start];
}
- (void)threadMethod{
    @autoreleasepool {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod:) userInfo:nil repeats:YES];
//            self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 block:^{
//                NSLog(@"timer2 run");
//            } repeats:YES];
        self.runloop = CFRunLoopGetCurrent();
        self.observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
        });
        CFRunLoopAddObserver(self.runloop, self.observer, kCFRunLoopDefaultMode);
        CFRunLoopRun();
        CFRelease(self.observer);
        NSLog(@"thread end");
    }
}

- (void)timerMethod:(NSTimer *)timer{
    count  ;
    NSLog(@"timer2 run");
    if (count == 2) {
        [timer invalidate];
        NSLog(@"timer invalidate");
    }
}
- (void)dealloc{
    NSLog(@"销毁了");
}

图片 14

iOS10

和状态一比不上,这里移除timer的操作是放在子线程中做的(在timer call out中)。从决定台出口中能够看看,那是在子线程runloop唤醒未来才移除timer,接着就进展是不是退出runloop的决断。由于子线程runloop中早已远非事件源了,因而runloop就淡出了。
在情形一,子线程成立timer,主线程移除timer,点击开关的机会是由人来把控的,由此会生出在子线程runloop休眠后移除timer以致runloop不可能唤起的难点。而气象三则从未如此的主题素材,能源能够取得平安释放。vc再次来到上顶级也能博得销毁。

从依期源在RunLoop中的专门的工作规律我们意识到,只要相符条件的停车计时器都会被触发,也正是说,在同三遍Loop中,大概会实践多少个停车计时器的回调。

2、创制多个对象,重写time r的类格局。

事情发生在此以前要做多少个出殡和安葬短信验证码的倒计时作用,策画用NS提姆er来达成,做的经过中发觉坑还是有许多的。

貌似情况下,从页面B再次回到到页面A后,页面B会被假释,页面B的dealloc方法会输出dealloc,但从实际上的运维效果能够见到,在iOS10条件下页面B并未被放出,WTF,为啥iOS10遭逢下会那样?要应对那几个难点,大家供给先明了iOS10的退换是如何。

2、NSTimer 不准确

在那篇随笔中有那样八个观点:

成都百货上千陈诉计时器的本事文中皆有与此相类似叁个理念,如果三个电火花计时器遗失了此次能够触发的时间点,那么机械漏刻将跳过这一个时间点,等待下二个时间点的赶到。但这几个观点跟机械漏刻在RunLoop中的工作原理并不符。定期音信从功底发出,消息在音讯大旨等待被管理,RunLoop每回Loop都会去音信中央查找相应的端口语资源消息息,若找到相应的端口音讯就能够進展管理,所以,就算当前RunLoop正在施行一个耗费时间很短的职分,当职责推行完步向下一次Loop时,那多少个未被拍卖的音信依旧会被管理。经过多量测量试验申明,定期消息并不会因延迟而掉失。

说西晋码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
    });
    // 添加观察者:监听RunLoop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    self.timer.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
    [self performSelector:@selector(busyOperation) withObject:nil afterDelay:0.5];

    // 释放Observer
    CFRelease(observer);
}

- (void)timerMethod{
    NSLog(@"timer2 run");
}

- (void)busyOperation{
    NSLog(@"线程繁忙开始");
    long count = 0xffffffff;
    CGFloat calculateValue = 0;
    for (long i = 0; i < count; i  ) {
        calculateValue = i/2;
    }
    NSLog(@"线程繁忙结束");
}

图片 15

32:runloop将在步向休眠;64:runloop唤醒

对照runloop状态代码,32代表runloop就要休眠,64代表runloop唤醒,128意味着runloop退出

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

定期音讯不会因为延时而消退。假使这段代码有写得不客观的地点请报告笔者。但好歹有少数是足以一定的,NSTimer电磁打点计时器不是老大纯粹。

本文由betway必威手机版发布于betway必威手机版,转载请注明出处:NSTimer的内存泄漏,iOS10定时消息的改动

关键词: 随笔 iOS 消息

上一篇:终端使用SS代理,实现终端

下一篇:没有了