首页 > 代码库 > 浅谈iOS中的RunLoop
浅谈iOS中的RunLoop
首先解释下为什么是浅谈,主要是RunLoop这个东西不单单是iOS的范畴,还涉及到操作系统,我指的浅谈仅仅针对ios上层应用,底层的东西概不涉及 ,所以只能浅谈浅谈了。
在浅谈RunLoop之前我们来写个小demo,超级简单,一个按钮,然后给按钮一个断点
这块标记了1,2,3,4 红色的字;其实这是这个APP启动的一个过程
但是说好了浅谈RunLoop为啥又扯到APP的启动了 ? 先不要在意这些细节。。。 我先来解释下我标出的1,2,3,4分别是啥东西
1,dyld :这是啥子鬼了? the dynamic link editor (动态链接编辑器)
我们惊奇的发现一个APP启动的时候是先干这个事,我们把xcode稍作配置
第一步
然后
我把这个鬼复制下来 dyld_PRINT_STATISTICS 打上对勾,后面的参数设为1
然后打开另外一个开关
一切准备好之后,我们启动我们的APP,注意在真机上启动。。。。 然后我们看下控制台打印的东西
看了这些日志输出我们对dyld有那么一点初级的概念了,原来这货是加载项目所需要的开发包,第一步算介绍到这里,然后我们看第二步
2,main() :整个APP的入口,将AppDelegate扯进来
3,RunLoop
我们不难发现main函数执行之后并不是直接执行我们的以UI开头的事件或方法,而是先搞一个RunLoop ,也就是RunLoop在用户界面呈现之前就启动了。。。。。 ,我们先继续,我们说下第4条
4,用户层操作
我们可以看到 UIApplication UIWindow UIControl 等我们熟悉的代码,但是不幸的是他们却在RunLoop之后运行。。。。 那么RunLoop到底是个啥子。。。。
RunLoop: 从字面的意思上来看 运行循环,在我大iOS的范畴中又是个啥意思了,意如其字 ; 如果你还看不懂,那我只能点拨下了一师是个”女子“学校 ,换句话说就是 一个APP启动的时候 相当于启动了一个死循环,而这个死循环就是RunLoop,我们在应用里面写的所有代码都在RunLoop里面运行; 如果你搞过单片机就很好懂啦,代码直接来个while(1){ } ,RunLoop其实就是这么回事,解释一下为什么我们的代码是在一个死循环里面运行
1.我手机启动一个APP,不让手机黑屏,过一段时间(几小时或几天)我再点击了,这个APP肯定响应我的动作
2.我再APP里面写一个图片轮播,不管过多久他一直在轮播,谁在支撑他?
3.给个岛国动作片的地址,我们手机干别的事情了,却能自动的下载下来。。。
我们的APP时刻保持用户响应其实就是开启了一个主RunLoop , 这个主RunLoop 是负责这个APP活动的心脏
这就是RunLoop ,我们来总结下RunLoop的功能
功能一:用来处理耗时长的异步事件
mainRunLoop];
- NSLog(@"int main %@",main.currentMode);
- NSRunLoop *current = [NSRunLoop currentRunLoop];
- NSLog(@"int main %@",current.currentMode);
- CFRunLoopRef runloop = CFRunLoopGetCurrent();
- NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runloop));
- NSLog(@"int main %@ ",allModes);
- CFRunLoopRef runloopm = CFRunLoopGetMain();
- NSArray *allModesm = CFBridgingRelease(CFRunLoopCopyAllModes(runloopm));
- NSLog(@"int main %@ ",allModesm);
我们来看下输出的结果。。
我们来总结下,总结之前我们把5种模式输出出来
- UITrackingRunLoopMode,
- GSEventReceiveRunLoopMode,
- kCFRunLoopDefaultMode,///默认模式
- UIInitializationRunLoopMode,
- kCFRunLoopCommonModes
我们惊奇的发现,我们一般页面用到两种默认,Init模式和Default模式;也就是页面初始化的是时候是init模式执行完ViewDidLoad方法之后RunLoop变为了Default模式了 ,注意这里有两个RunLoop, 一个是MainRunLoop 和CurrentRunLoop,MainRunLoop无可厚非就是整个项目的主RunLoop,Current就是当前的,因为当前只有一个线程,所以MainRunLoop 和CurrentRunLoop是一个RunLoop;为啥要说Mode了 ,我们来看一个例子。
我启动一个定时器,每秒中自增1, 看标题;每当我手放在ScrollView上了,定时器就不工作了。。
我的源码如下
- - (void)viewDidLoad {
- [super viewDidLoad];
- // Do any additional setup after loading the view.
- self.view.backgroundColor = [UIColor whiteColor];
- startcount = 0;
- UIButton *button1 = [[UIButton alloc ] init];
- button1.frame = CGRectMake(10, 140, 100, 50);
- [button1 setTitle:@"启动定时器" forState:UIControlStateNormal];
- [button1 addTarget:self action:@selector(button1) forControlEvents:UIControlEventTouchUpInside];
- button1.backgroundColor = [UIColor lightGrayColor];
- [self.view addSubview:button1];
- button1 = [[UIButton alloc ] init];
- button1.frame = CGRectMake(150, 140, 100, 50);
- [button1 setTitle:@"暂停定时器" forState:UIControlStateNormal];
- [button1 addTarget:self action:@selector(button2) forControlEvents:UIControlEventTouchUpInside];
- button1.backgroundColor = [UIColor lightGrayColor];
- [self.view addSubview:button1];
- UIScrollView *scroll = [[UIScrollView alloc] init];
- scroll.frame = CGRectMake(0, 200, 320, 300);
- scroll.backgroundColor = [UIColor redColor];
- scroll.contentSize = CGSizeMake(320, 900);
- scroll.delegate = self;
- [self.view addSubview:scroll];
- timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
- [timer setFireDate:[NSDate distantFuture]];
- }
- -(void)function:(id)sender{
- self.title = [NSString stringWithFormat:@"%d",startcount++];
- [self prinitMode:@"Timer"];
- }
- -(void)button1{
- [timer setFireDate:[NSDate distantPast]];
- }
- -(void)button2{
- [timer setFireDate:[NSDate distantFuture]];
- }
- -(void)scrollViewDidScroll:(UIScrollView *)scrollView{
- [self prinitMode:@"scrollViewDidScroll"];
- }
- -(void)prinitMode:(NSString *)modeName{
- NSRunLoop *main = [NSRunLoop mainRunLoop];
- NSLog(@"mainRunLoop %@ %@",modeName,main.currentMode);
- CFRunLoopRef runloopm = CFRunLoopGetMain();
- NSArray *allModesm = CFBridgingRelease(CFRunLoopCopyAllModes(runloopm));
- NSLog(@"mainRunLoop %@ ALL %@ ",modeName,allModesm);
- NSRunLoop *current = [NSRunLoop currentRunLoop];
- NSLog(@"currentMode %@ %@",modeName,current.currentMode);
- CFRunLoopRef runloop = CFRunLoopGetCurrent();
- NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runloop));
- NSLog(@"current %@ ALL %@ ",modeName,allModes);
- }
然后看下我的日志文件
我们发现手放下UIScrollView的时候RunLoop的模式变成了 UITrackingRunLoopMode ,定时器不起作用; 手离开时,模式变成KCFRunLoopDefaultMode ,定时器继续工作。那么这个就比较麻烦了,滑动的时候定时器不起作用,现实项目中这个肯定不行的。。 如何解决了 ? 刚刚我们说到了AFNetworking的源码,我们可以模仿下源码
- timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
- [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- [timer setFireDate:[NSDate distantFuture]];
我在timer上面加了这段代码,惊奇的发现,现在我滑动UIScrollView,Timer并没有收到影响;我看来下日志,
开启定时的时候: mainRunLoop Timer kCFRunLoopDefaultMode
滑动UIScrollView的时候: mainRunLoop scrollViewDidScroll UITrackingRunLoopMode
再次松开UIScrollView的时候:mainMode Timer UITrackingRunLoopMode
UITrackingRunLoopMode:用户滑动Mode
GSEventReceiveRunLoopMode:用于系统接收事件Mode
kCFRunLoopDefaultMode:RunLoop默认Mode
UIInitializationRunLoopMode:初始化Mode
kCFRunLoopCommonModes:通用Mode
这就解释了为什么AFNetworking中使用RunLoop来开启一个新的timer,上面提到一个项目中的主RunLoop只有一个(有点类似于主线程和子线程,主线程只有一个,子线程有很多个),每次新建一个Timer我们需要开启一个子的RunLoop 然后加入到主RunLoop中
总结下RunLoop的一些特点
1.主线程的RunLoop在应用启动的时候就会自动创建
2.其他线程则需要在该线程下自己启动
3.不能自己创建RunLoop
4.RunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop
5.RunLoop负责管理autorelease pools
6.RunLoop负责处理消息事件,即输入源事件和计时器事件
浅谈iOS中的RunLoop