runloop
是一种高级的循环机制,让程序持续运行,并处理程序中的事件,让线程在需要的时候忙起来,不需要的时候休眠。
主线程的runloop
保证应用程序的存活,从而可以实时接收到用户的响应,能够触发事件。
保持程序持续运行,节省CPU资源,提高程序性能。
程序开始时第一次获取runloop
的时候,一定获取的是主线程的runloop
。在获取runloop的函数中会定义一个全局的字典变量,该字典是以线程为key
,获取的runloop
为value
,保存到字典中。方便之后获取runloop
的时候可以先遍历字典找一找之前有没有获取。如果字典没有对应的runloop
,再获取对应线程的runloop
。
runloop
对象。runloop
对象,runloop
会在第一次获取它的时候创建。runloop
,子线程需要手动获取对应的runloop
。runloop
也会随之销毁。runloop
中有5种Mode:
NSDefaultRunLoopMode
: app默认的Mode
,主线程就在这个状态下运行。UITrackingRunLoopMode
: 界面跟踪,用于scrollview
的触摸滑动,保证界面不会受其他Mode
的影响。UIInitializationRunLoopMode
:在程序刚启动的时候进入的第一个Mode,然后就切换到NSDefaultRunLoopMode
。kCFRunLoopCommonModes
: 这是一种标记Mode
,事件可以运行在所有标记的Mode中,也可以添加观察者。GSEventReceiveRunLoopMode
: 接受系统事件的内部 Mode
,通常用不到。一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
常用的有三种Mode:
NSDefaultRunLoopMode
, 默认的模式, 有事件响应的时候, 会阻塞旧事件。NSRunLoopCommonModes
, 普通模式, 不会影响任何事件。UITrackingRunLoopMode
, 只能是有事件的时候才会响应的模式。输入源:通常用于处理基于文件描述符的事件,可以将外部事件与runloop关联起来,使runloop可以监听这些事件,并且进行相应的处理。
定时源:用于在指定时间间隔触发事件,适用于需要定期执行的任务。通过定时器在特定的时间触发回调方法。
source0
和source1
都属于CFRunLoopSourceRef
,而CFRunLoopSourceRef
是对于输入事件源的抽象。
source0
和source1
都是在runloop
中描述事件的触发机制。source0
用于处理手动触发的事件,source1
用于处理系统或者其他线程之间的事件。
source0
(不会唤醒runloop):
source0
是非基于端口的事件源,用于处理应用程序内部生成的事情。source1
(会唤醒runloop):
source1
是基于端口的事件源,用于处理与其他进程或者内核之间的交互。示例:
用户用手指点击app界面时,会先触摸到硬件,屏幕会将这个事件先包装成Event
,Event
先告诉source1(mach_port)
唤醒runloop
,然后将这个事件Event
分发给source0
,让source0
处理该事件。
//观测的时间点有一下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
NSDefaultRunLoopMode
下。runloop
就结束了NSDefaultRunLoopMode
,切换到UITrackingRunLoopMode
,但是在这个模式下,并没有添加NSTimer
,所以定时器就不工作了。runloop
就结束UITrackingRunLoopMode
模式,回到NSDefaultRunLoopMode
模式,NSTimer
就开始工作了。runloop
的循环过程中,NSTimer
的触发事件阻塞,导致循环不能进行,延误了NSTimer
的触发时间。runloop
的循环过程中,主线程在某一时刻发生阻塞,导致循环不能进行,延误了NSTimer
的触发时间。runloop
的运行模式,导致NSTimer
在原有的模式下不能正常触发。怎样解决:
NSTimer
放入容易阻塞的主线程中。NSTimer
的线程中。NSTimer
中进行耗时的操作,如果不能避免,就将操作转移到其他线程。实现多线程的方法:
更倾向于GCD和NSOperation,封装更高级,使用起来更方便。
//让处理在主线程中执行
dispatch_async(dispatch_get main_queue(), ^{
/*
*只在主线程可以执行的处理
*例如用户界面更新
*/
});
- (void)once { //GCD的一次性代码(只执行一次)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
}
- (void)after {
NSLog(@"run -- 0");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
NSLog(@"run -- 2");
});
}
dispatch_semaphore_t semalook = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);
NSLog(@"1开始");
sleep(2);
NSLog(@"1结束");
dispatch_semaphore_signal(semalook);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);
NSLog(@"2开始");
sleep(2);
NSLog(@"2结束");
dispatch_semaphore_signal(semalook);
});
可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
GCD
执行的效率更高,队列中执行的是由block
构成的任务,写起来比较方便。GCD
只支持FIFO
的队列,但是NSOperationQueue
可以设置最大并发数,设置优先级,添加依赖,调整顺序。NSOperationQueue
可以在队列中设置依赖关系,但是GCD
只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)
任务,才能控制执行顺序,较为复杂。NSOperationQueue
因为面向对象,所以支持KVO
,可以监测operation
是否正在执行(isExecuted
)、是否结束(isFinished
)、是否取消(isCanceld
)Mutex
):使用互斥锁可以确保同时时间只有一个线程访问共享资源,在Objective-C中,可以使用@synchronized
关键字来创建互斥锁。 @synchronized (self) {
// 访问共享资源的代码
}
Spin Lock
):自旋锁是一种忙等待锁,不断尝试获取锁,直到成功为止。在Objective-C中,可以使用os_unfair_lock
来创建自旋锁。 os_unfair_lock_t lock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(lock);
// 访问共享资源的代码
os_unfair_lock_unlock(lock);
Semaphore
):信量是一种数器,用于控制同时访问某个资源的线程数量,在Objective-C中可以使用dispatch_semaphore
来创建信号量。 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 访问共享资源的代码
dispatch_semaphore_signal(semaphore);
Atomic Operations
):原子操作是一种特的操作,可以确保对共享资源的读写操作是原子的,即不会被其他线程中断。在Objective-C中,可以使用atomic
键字来声明属性原子类型。 @property (atomic, strong) NSObject *sharedObject;
Serial Queue
):使用串行队列可以确保任务按顺序执行,从而避多个线程同时访问共享资源。可以使用GCD(Grand Central Dispatch
)来创建串行队列。 dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
// 访问共享资源的代码
});