NSRunLoop探究

经常听runloop的黑魔法,但是项目里不怎么用,但是该了解一下还是需要的。

从main.m说起

正常的main函数如下:

int  main(int  argc,char* argv[]) {

@autoreleasepool{

return  UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate  class]));

        }

}

现对main函数进行一些无伤大雅的修改:

int  main(int  argc,char* argv[]) {

@autoreleasepool{

       NSLog(@"come here");

     //1.可能是阻塞式函数

        2.可能是死循环所以下面不会执行打印(因为死循环了--runloop)来了

//死循环!!使用runloop,保证该线程不退出

int  a = UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate  class]));

NSLog(@"来了");

return a;

}

}

如注释所说下面一句打印 来了 永远都不会执行,原因是UIApplicationMain函数在runloop中循环执行,类似死循环,所以下一句永远不会执行。

(递归同样会产生这种效果,但这里不是递归原因:

递归:函数调用自己!!会调用Stack Overflow,即栈(内存)溢出--------

函数执行开始时push
函数执行完毕时pop

如上两图所示,每一个函数的调用都会分配一块栈内存(汇编代码中就是pushq,在函数return前都需要将其popq掉)。-----递归在不断得调用自己,不断得pushq而没有popq,而内存空间又是有限的,所以会造成内存溢出,程序崩溃;而死循环里面,函数只调用一次,不断执行里面内容,只pushq一次,所以不会造成内存溢出


RunLoop的目的:

1. 保证线程不退出(主线程死循环,始终都有任务)

2. 监听事件(触摸、时钟、网络等)

3. 无事则休眠

了解过RunLoop的都会知道runloop内容包含有:

1、Mode----模式。

2、Observer----观察者

3、Source----事件源

这里先只介绍Model。有几种模式,我们常用的就是三种默认、UI和commonModes,分别对应NSDefaultRunLoopMode、UITrackingRunLoopMode和NSRunLoopCommonModes。

在我上一篇文章中(NSTimer)提到,NSTimer的三种创建方式,在scheduledTimerWithTimeInterval方法创建的timer会自动添加到一个模式为默认模式(NSDefaultRunLoopMode)的runloop中;其他两种方式则不会自动添加进runloop,为了timer起效所以需要手动添加:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

第二个参数mode就是runloop的模式。

//将timer添加到runloop监听

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];//默认runloop模式,拖拽等用户交互事件时,timer暂停

[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];//UIrunloop模式,在有触摸事件时才执行timer

/*上面两行同时存在才能确保在触摸和未触摸时都执行timer,效果等同于下面一行,添加到commonModes(包含默认和UI模式)占位模式*/

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

RunLoop和线程的关系:

一条线程上可以创建一个runloop,然后并没有显示创建runloop的api接口,runloop的创建都是封装在获取RunLoop额方法和函数中的,这类似于懒加载:

[NSRunLoop currentRunLoop];

看以下代码:

先写一个事件方法;

- (void)timerMrthod {

NSLog(@"来了!!");

static int a =0;

NSLog(@"%@------%d",[NSThread currentThread],a++);

}


然后开启一个线程去执行:

- (void)viewDidLoad {

  [superviewDidLoad];

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSTimer*timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timerfor Mode:NSDefaultRunLoopMode];

NSLog(@"22");

}];

[thread start];

}


WQthread的dealloc方法中:

#import"WQthread.h"

@implementation WQthread

- (void)dealloc {

NSLog(@"dealloc");

}

@end

运行结果:

2017-09-27 17:10:12.104 Runloop--01[2493:112316] 22

2017-09-27 17:10:12.105 Runloop--01[2493:112316] dealloc

并没有进入timer的事件方法,线程thread就销毁了,因为在timer设定的定时时间到来前对象已经销毁了。若要执行,则需保证thread始终存在,可以通过在线程中死循环保证线程一直有任务的方式来实现,比如线程代码改为:

- (void)viewDidLoad {

[superviewDidLoad];

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSTimer*timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timerfor Mode:NSDefaultRunLoopMode];

[[NSRunLoop currentRunLoop]run];//让runloop来死循环

NSLog(@"22");

}];

[thread start];

}

结果:

2017-09-27 17:18:50.569 Runloop--01[2642:117818]来了!!

2017-09-27 17:18:50.570 Runloop--01[2642:117818] {number = 3, name = (null)}------0

2017-09-27 17:18:51.567 Runloop--01[2642:117818]来了!!

2017-09-27 17:18:51.568 Runloop--01[2642:117818] {number = 3, name = (null)}------1

停止线程用 [NSThreadexit];

完整代码:

@interface ViewController()

@property(nonatomic,strong)WQthread*thread;

//保住了OC对象NSThread,但是底层的线程挂了

@property(nonatomic,assign)BOOL isFinish;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

_isFinish=NO;

//_thread = [[WQthread alloc]initWithBlock:^{

//NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

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

//NSLog(@"22");

//}];

//[_thread start];

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSTimer*timer = [NSTimer  timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timerfor Mode:NSDefaultRunLoopMode];

//while (true) {//死循环

////取出runloop中的event

//

//}

//[[NSRunLoop currentRunLoop] run];//让runloop来死循环

while (!_isFinish) {

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.00001]];

}

NSLog(@"22");

}];

[threadstart];

}

- (void)timerMrthod {

NSLog(@"来了!!");

if(_isFinish) {

[NSThreadexit];//停止线程

}

static int a =0;

NSLog(@"%@------%d",[NSThreadcurrentThread],a++);

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

_isFinish=YES;

//[NSThread exit];//这里如果这样写仅仅是停止了主线程,单子线程还在执行任务

}

使用一个BOOL来标记,在点击屏幕的时候结束线程。

子线程和主线程进行通信

还是在touchBegin里面来做,直接上代码:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

_finish=NO;

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSLog(@"touchesBegan - %@",[NSThread currentThread]);

while(!_finish) {//while循环只要不停止,该线程就会一直有任务就不会挂掉,同时添加进当前线程的otherMdthod任务就永远不会轮到执行

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];//这句等于是给该线程一个任务,执行完毕,该线程即进行下一个任务,即从主线程添加进来的otherMdthod方法

//[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];//UI模式不执行主线程添加到该线程默认模式的otherMdthod任务。

}

}];

[thread start];

[self performSelector:@selector(otherMdthod) onThread:thread withObject:nil waitUntilDone:NO];//这一句相当于在主线程中,添加一个方法到子线程thread中,即主线程和子线程进行了通信;thread此时相当于从主线程添加到thread的一个source

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容