应用
1.控制线程生命周期(线程保活)
2.解决NSTimer在滑动时停止工作的问题
3.监控应用卡顿
4.性能优化
解决NSTimer在滑动时停止工作的问题
创建一个iOS工程,在界面上放一个TextView.创建一个定时器
static int count = 0;
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
会发现,当滚动TextView时,定时器停止滚动。
scheduled安排。scheduledTimer开头的,这种定时器安排好了,会将NSTimer对象添加到默认模式下工作。滚动的时候切换到其他模式了,退出默认模式了,所以会停止滚动。
解决:
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
_modes是所有模式集合_commonModes可以认为标记为common的模式- NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式,NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
- 如果传的参数是NSRunLoopCommonModes,意味着NSTimer能在标记为common的模式下工作,也就是说能在
_commonModes数组中存放的模式下工作。 _commonModes存放NSDefaultRunLoopMode,UITrackingRunLoopMode- 如果对象timer或者source0,source1等,传入的参数是
_commonModes,那么就会把这个对象放在_commonModelItems里。 - 定时器是不准时的,即使没有滑动干扰也是不准时的,因为定时器是Runloop处理的,Runloop有可能会处理其他事务。有准时的定时器,后面再说。
注意:scheduledTimerWithTimeInterval并没有开辟新的线程,而是在主线程中执行。所以还是在主线程所在的runloop,即:`[NSRunLoop mainRunLoop]`
NSLog(@"%p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
NSLog(@">>>>>%p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
}];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"=====%p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
});
线程保活
1.线程保活
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// waitUntilDone传yes,会等test执行完之后,再执行NSLog(@"123");
// 传NO就是执行test的同时,执行NSLog(@"123");
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
// NSLog(@"123");
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
// 这个方法的目的:线程保活
- (void)run {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s ----end----", __func__);
}
@end
#import "MJThread.h"
@implementation MJThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
- run方法的目的,是让线程保活。
- 因为Runloop的mode里没有任何的,source0,source1,timer,observer,会立马退出。所以为了不让Runloop退出,这里添加一个source1.
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run];,让Runloop一直在循环。达到保活的目的。单独写这行代码,不写上面那一行代码是不行的,依然会退出。必须在循环中保证有事件。[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];waitUntilDone传yes,会等test执行完之后,再执行NSLog(@"123");
2. 线程释放
上面的线程保活方法,会导致控制器和线程都无法释放,控制器无法释放是因为循环引用。 增加一个控制器为根控制器,控制器里有个按钮,点击跳转ViewController.发现在ViewController里点击导航返回按钮,返回根控制器后,控制器和线程并没有释放。
self.thread = [[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
MJThread会对控制器强引用。改成:
self.thread = [[MJThread alloc] initWithBlock:^{
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%@ ----end----", [NSThread currentThread]);
}];
[self.thread start];
但是,控制器释放后,线程还是没释放。此时这个线程为全局线程。 即使该线程置为nil,该线程也不会释放
- (void)dealloc
{
NSLog(@"%s", __func__);
self.thread = nil; //线程依然不会被释放
}
ViewController里增加stop按钮。
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJThread alloc] initWithBlock:^{
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%@ ----end----", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (IBAction)stop:(id)sender {
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];//YES时马上执行,为NO时先加入线程的runloop事件循环里面,等待runloop来调度执行。
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
-(void)stopThread{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
CFRunLoopStop(CFRunLoopGetCurrent());
}
// 这个方法的目的:线程保活
//- (void)run {
// NSLog(@"%s %@", __func__, [NSThread currentThread]);
//
// // 往RunLoop里面添加Source\Timer\Observer
// [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] run];
//
// NSLog(@"%s ----end----", __func__);
//}
-(void)dealloc{
NSLog(@"%s",__func__);
//此方法不行,线程还是没有释放。是不是因为写在这里,来不及释放?增加个stop按钮试试呢?
// [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];//YES时马上执行,为NO时先加入线程的runloop事件循环里面,等待runloop来调度执行。
// self.thread = nil;
}
@end
以上,发现线程仍然不会被释放。问题出在[[NSRunLoop currentRunLoop] run];
查看官方文档:
it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
底层调用了runMode:beforeDate:,换句话说,相当于开启了一个无限的loop NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
解决方案:
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
// beforeDate:超时时间,线程退出时间
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// [[NSRunLoop currentRunLoop] run];
NSLog(@"%@----end----", [NSThread currentThread]);
// NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
// [[NSRunLoop currentRunLoop] run];
/*
it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers
*/
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
// 在子线程调用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop]; //当不点击stop按钮,控制器释放的时候,让线程也释放
}
@end
- 以上方案也是有问题的,
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop]; //当不点击stop按钮,控制器释放的时候,让线程也释放
}
会崩溃。崩在[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];,坏内存访问。
崩溃原因:
- (IBAction)stop {
if (!self.thread) return;
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
waitUntilDone传入NO,不等stopThread执行完,stop就执行完了,从而dealloc执行完,从而self就不存在了,这时候self.thread的runloop通过self执行stopThread时,会出现坏内存访问,坏内存是当前控制器,也就是self.所以这里改为YES.
但这样,控制器点击stop,返回根控制器后,发现线程并没有释放。
这是因为,点击stop后,停止循环,然后控制器释放,这时再判断!weakSelf.isStoped这个条件的时候,发现weakSelf已被释放,导致条件判断有误,大括号内的代码一直会被执行。所以线程释放不了。
但是!!!以上修改后,点击stop按钮后,再点返回,发现又崩了。
修改:stopThread方法最后清空线程,再加上一些判断。
最后完美的方案:
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@----end----", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (!self.thread) return;
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
if (!self.thread) return;
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 清空线程
self.thread = nil;
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
@end
3.线程保活的封装
#import <Foundation/Foundation.h>
typedef void (^MJPermenantThreadTask)(void);
@interface MJPermenantThread : NSObject
/**
开启线程
*/
//- (void)run;
/**
在当前子线程执行一个任务
*/
- (void)executeTask:(MJPermenantThreadTask)task;
/**
结束线程
*/
- (void)stop;
@end
#import "MJPermenantThread.h"
/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) NSThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[MJThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.innerThread start];
}
return self;
}
//- (void)run
//{
// if (!self.innerThread) return;
//
// [self.innerThread start];
//}
- (void)executeTask:(MJPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task
{
task();
}
@end
调用:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJPermenantThread alloc] init];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.thread executeTask:^{
NSLog(@"执行任务 - %@", [NSThread currentThread]);
}];
}
- (IBAction)stop {
[self.thread stop];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
4.使用Runloop相关C语言API进行封装:
#import <Foundation/Foundation.h>
typedef void (^MJPermenantThreadTask)(void);
@interface MJPermenantThread : NSObject
/**
开启线程
*/
//- (void)run;
/**
在当前子线程执行一个任务
*/
- (void)executeTask:(MJPermenantThreadTask)task;
/**
结束线程
*/
- (void)stop;
@end
/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) NSThread *innerThread;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.innerThread = [[MJThread alloc] initWithBlock:^{
NSLog(@"begin----");
// 创建上下文(要初始化一下结构体)
CFRunLoopSourceContext context = {0};
// 创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 销毁source
CFRelease(source);
// 启动
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
// while (weakSelf && !weakSelf.isStopped) {
// // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
// }
NSLog(@"end----");
}];
[self.innerThread start];
}
return self;
}
//- (void)run
//{
// if (!self.innerThread) return;
//
// [self.innerThread start];
//}
- (void)executeTask:(MJPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task
{
task();
}
@end
-
CFRunLoopSourceContext context = {0};没有初始化的局部变量,他的初始值是不确定的,有可能会很大,所以这里建议把结构体置为{0}. -
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop。所以改为false后,就可省去一些判断和标记。