OC之RunLoop了解一下
一、进程
进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内比如同时打开QQ、Xcode,系统就会分别启动2个进程
二、线程
是用来执行任务的,线程只要彻底执行完A才能去执行B,这就是线程的安全,为了防止任务能同时进行,引入了多线程。
1、多线程:其实是CPU对任务的调度,任务的优先级越高,CPU被调度的就越快,对于单核CPU只能在同一时间处理一条线程,多线程并发执行实质是任务线程之间的不断切换,因为切换的太快,所以造成了同一时间执行的假象;比如两个线程A、B;A执行到某一时间,切换到B,但是A还没有执行完,系统会把A当前的位置和数据保存在栈中,依次循环,线程对CPU的消耗是比较大的,ios中不建议开多条线程
2、线程分为两种:主线程和子线程
在当前线程中开启多个新线程,不按照顺序执行叫异步,在当前线程中的任务按顺序执行,不开启新的线程,叫同步。队列是状态线程任务的结构:包括并发队列和穿行队列;并发队列的线程可以同时一起执行,串行队列只能依次逐一先后有序的执行
三、多线程的优缺点:
优点:1、提高CPU的利用率,不让它闲着
2、提高程序的执行效率,避免线程阻塞造成的卡顿
缺点:1、大量的线程降低代码的可读性
2、需要大量的内存空间,降低程序的性能
3、多个线程同时争夺同一资源,容易线程不安全,比如mutalArray的例子
死锁:A在等待B的结果,B在等待A的结果
多线程的实现方法有四种:Pthreads、CGD、NSThread、NSOpreation
多线程就是开辟线程、添加队列、队列中同步或者异步执行
四、主线程
一个ios程序运行后,系统会默认开启一条线程,称为UI线程或者主线程,主线程是用来显示、刷新界面、处理UI事件的,假如没有RunLoop程序一运行就结束了,看不到持续运行的app
五、RunLoop的作用;学习原链接RunLoop深入
1、保持程序的持续运行;程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,是Runloop保证主线程不被销毁
2、处理项目中各种事件,比如:selector事件、定时器、触摸事件等
3、节省CPU的资源,提高程序性能。当有操作的时候runloop会通知CPU该做什么,没有事情的时候让它休息
Runloop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口的函数,线程执行了这个函数就会一直处于这个函数内部接受消息----等待消息----处理消息,直到这个循环结束
RunLoop在哪里开启:在UIApplicationMain函数中启动了RunLoop,程序不会马上退出而是保持运行状态,程序运行,主线程开启,和主线程对应的Runloop开启,那么runloop肯定是在程序的入口main函数中开启的
在iOS中有两套API访问和使用RunLoop
Foundation:NSRunloop
CoreFoundation :CFRunLoopRef 他是基于pthread来管理的
NSRunloop是基于CFRunLoopRef的一层OC的包装
在源码中创建的线程核心代码
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
//如果没有线程,创建一个
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
//创建一个可变的字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//将主线程作为参数创建一个Runloop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//将主线程的RunLoop和主线程以key/value的形式保存,可以看到线程和RunLoop是一一对应的
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//当输入一个 currentRunLoop 时,会通过当前线程这个Key,在字典中寻找对应的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
//如果没有找到
if (!loop) {
//重新创建一个
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
//然后将runloop和线程以 key/value的形式保存
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
很好的runloop帖子:Runloop全面学习
六、线程与runloop的关系
程序启动时,系统会自动创建主线程的RunLoop,每条线程都有唯一的一个与之对应的RunLoop对象,主线程的Runloop已经自动创建好了,子线程的runloop需要自己手动创建,runloop在第一次获取时创建,在线程结束时销毁
获取当前线程的Runloop,runloop是懒加载的,在发送消息时自动创建对象
[NSRunLoop currentRunLoop];
在iOS开发中,会遇到两个线程对象:pthread_t 和 NSThread,NSThread只是对pthread的封装,两者肯定是一一对应的,我们可以通过pthread_main_thread_np()或者 [NSThread mainThread]来获取主线程,也可以通过pthread_self() [NSThread currentThread]来获取当前的线程。
苹果是不允许直接来创建Runloop的。他只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()
七、RunLoop相关类
我尝试着打印了 [NSRunLoop mainRunLoop];
mainRunLoop == <CFRunLoop 0x6040001f9a00 [0x103291c80]>
{wakeup port = 0xc03, stopped = false, ignoreWakeUps = true,
current mode = (none),
common modes = <CFBasicHash 0x6040002557b0 [0x103291c80]>
{type = mutable set, count = 1,
entries =>
2 : <CFString 0x103267818 [0x103291c80]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x604000255780 [0x103291c80]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopMode 0x604000192f10 [0x103291c80]>{name = kCFRunLoopDefaultMode, port set = 0xd03, queue = 0x60400014f4c0, source = 0x600000192e40 (not fired), timer port = 0x1503,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 556728920 (19355243452143) / soft deadline in: 1.84467247e+10 sec (@ -1) / hard deadline in: 1.84467247e+10 sec (@ -1)
},
}
}
可以看到Corefoundation中关于runloop的五个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
从打印中可以看出,一个RunLoop想要跑起来,必须要有Model支持,而Mode里边必须有(NSSet *)soure (NSArray *)Timer
这时候的(NSArray *)Observer是用来监听runloop的状态,这时候是不会**的
1、CFRunloopModeRef
代表runloop运行模式,每个runloop都包含几个mode。每个mode又包含若干个source、timer、observer,每次runloop启动时,只能指定其中一个mode,这个mode被称作currentMode,如果需要切换mode,只能退出runloop,再重新指定一个mode进入,这样做主要是为了分开不同的source、timer、observer,让他们互不影响
runloop默认注册了5个mode
NSDefaultRunLoopMode:app默认的mode,通常主线程是在这个mode下运行的
UITrackingRunLoopMode:界面跟踪mode。用于scorllview追踪触摸滑动,保证界面滑动时不受影响
UIInitializationRunLoop:刚启动时app进入第一个mode,启动完成就不再使用
GSRunLoopReceiveRunLoopMode:接受系统时间的内部mode,绘图服务,通常用不到
NSRunLoopCommonModes:这是一个占位用的mode,不是一种真的mode
几个mode的区别
NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopIsRun) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
后面这一句相当于前两者写在一起的模式下运行(####其实这里我也不是很明白####)
2、CFRunLoopTimeRef
CFRunLoopTimeRed是基于时间的触发器,应该说就是NSTimer,它手RunLoop的mode影响
创建Timer的两种方式
1> NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopIsRun) userInfo:nil repeats:YES];
这种方式必须手动添加到 RunLoop 中去才会被调用
2> 通过方法scheduledTimerWithTimeInterval: target : selector
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
/*
注意:调用了 scheduledTimer 返回的定时器,已经自动被添加到当前runLoop 中,而且是NSDefaultRunLoopMode ,想让上述方法起作用,必须先让添加了上述 timer的RunLoop 对象 run 起来,通过scheduledTimerWithTimeInterval 创建的 timer 可以通过以下方法修改 mode
*/
[[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];
****提示**** GCD的定时器不受RunLoop的Mode影响
CFRunLoopSourceRef 事件的输入源
source的分类:
1 》port-Based Source 基于端口,跟其他线程进行交互的,属于mac内核发过来的一些消息
2》 custom input Source 自定义输入源
3》cocoa perform selector sourcees
按照函数条用栈可分为
1》source0:基于port的触摸事件、点击事件
2》source1:基于port的,通过内核和其他线程通信,接受分发系统事件,是和硬件的交互,触摸首先在屏幕上包装成一个event事件,在通过source1进行分发到source0,最后通过source0来处理
逻辑图如下
CFRunLoopObserver : 是观察者,能够监听runloop的状态改变,主要监听以下几个时间节点,在文档的.h中这么声明
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出 Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU //监听所有事件
};
/**
创建观察者,监听runloop
该方法可以在添加timer之前做点事情。可以在添加source之前搞点事情
参数一:CFAllocatorRef allocator CFAllocatorGetDefault 默认值
参数二:CFOptionFlags activities 监听runloop的活动,也就是之前的枚举
参数三:Boolean repeats 是否重复监听
参数四:CFIndex order 写0
参数五:block
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopRunHandledSource, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
//添加观察者,监听当前的runloop
CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer , kCFRunLoopCommonModes);
//cf的是不ARC管理内存的,需要释放
CFRelease(observer);
整个runloop的运行逻辑如下图:
八、RunLoop的应用
1 > NSTimer 需求让定时器在其他线程开启
/*
> 需求
> 有时候,用户拖拽scrollView的时候,mode:UITrackingRunLoopMode,显示图片,如果图片很大,会渲染比较耗时,造成不好的体验,因此,设置当用户停止拖拽的时候再显示图片,进行延迟操作
- 方法1:设置scrollView的delegate 当停止拖拽的时候做一些事情
- 方法2:使用performSelector 设置模式为default模式 ,则显示图片这段代码只能在RunLoop切换模式之后执行
*/
// 加载比较大的图片时,
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// inModes 传入一个 mode 数组,这句话的意思是
// 只有在 NSDefaultRunLoopMode 模式下才会执行 seletor 的方法显示图片
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
[imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"avater"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
}
效果为:当用户点击之后,下载图片,但是图片太大,不能及时下载。这时用户可能会做些其他 UI 操作,比如拖拽,但是如果用户正在拖拽浏览其他的东西时,图片下载完毕了,此时如果要渲染显示,会造成不好的用户体验,所以当用户拖拽完毕后,显示图片。这是因为,用户拖拽,处于 UITrackingRunLoopMode 模式下,所以图片不会显示
2、常驻线程
> 需求
搞一个线程一直存在,一直在后台做一些操作 比如监听某个状态, 比如监听是否联网。
//一个线程对应一个runloop,创建一个线程会自动开辟一个runloop
NSThread * runloopTherd = [[NSThread alloc]initWithTarget:self selector:@selector(runLoop) object:nil];
[runloopTherd start];
-(void)runLoop
{
NSLog(@"-----");
/*
* 创建RunLoop,如果RunLoop内部没有添加任何Source Timer
* 会直接退出循环,因此需要自己添加一些source才能保持RunLoop运转
*/
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
3、自动释放池
Runloop循环时,在进入睡眠之前会清掉自动释放池,并且创建一个新的释放池,用于内部变量的销毁。在子线程开RunLoop的时候一定要自己写一个autoreleasepool。一个runloop对应一条线程,自动释放池是针对当前线程里边的对象
NSThread * releaseTherd = [[NSThread alloc]initWithTarget:self selector:@selector(releaseRun) object:nil];
[releaseTherd start];
-(void)releaseRun
{
//这样保证了内存的安全
@autoreleasepool{
NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runLoop) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
}
4、runloop与网络请求、AFNetWorking ---摘自博客
iOS中,关于网络请求的接口从上到下有:
CFSocket 最底层的接口,只负责socket通信
CFNetwork -----> ASIHttpRequest 基于CFSocket的上层封装
NSURLConnection -----> AFNetworking 基于CFNetwork的分装,提供面向对象的接口
NSURLSession ------->AFNetworking iOS7中新增的接口,内部用到了NSURLConnection
五、PerformSelecter,这个单独放出来,这里只做简单介绍
当调用NSObject的performSelecter:afterDelay:方法后,其实内部会创建一个timer,并添加到当前的线程的runloop中,所以如果当前的线程没有runloop,则这个方法会失效
========稍后我会将RunLoop源代码的注释贴上=========
=======可能理解有错或者不到位欢迎指出========
=========相关帖子=======
上一篇: @synchronized详解