Please enable JavaScript.
Coggle requires JavaScript to display documents.
Runloop和线程 (Runloop与线程 (RunLoop (问题 1.Runloop(一次唤醒) == event loop == 事件周期…
Runloop和线程
Runloop与线程
自动释放池概念
创建和销:每一个
事件周期
(event cycle)的开始,系统会自动创建一个自动释放池;在每一个
事件周期
的结尾,系统会自动销毁这个自动释放池。
事件周期:当你的代码在持续运行时,自动释放池是不会被销毁的,这段时间内你也可以安全地使用自动释放的对象;当你的代码运行告一段落,开始等待用户输入(或者其它事件(交互))时,自动释放池就会被释放掉,池中的对象都会收到一个release消息,有的可能会因此被销毁。
缺点:它延缓了对象的释放,在有大量自动释放的对象时,会占用大量内存资源。因此,你需要避免将大量对象自动释放。
需要手动建立并手动销毁掉自动释放池:
1.当你在主线程外开启其它线程时:系统只会在主线程中自动生成并销毁掉自动释放池。
2.当你在短时间内制造了大量自动释放对象时:及时地销毁有助于有效利用iPad上有限地内存资源。(常见于相册图片读取)
3.如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
整个 iOS 的应用都是包含在一个自动释放池 block 中的。
每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)#define I386_PGBYTES 4096 #define PAGE_SIZE I386_PGBYTES
1)自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的(parent 和 child 就是用来构造双向链表的指针。)
2)当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
3)调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息
线程中的自动释放池:
autoreleasepool{}自动释放池
1.主线程中是有自动释放池的,使用GCD和NSOperation 也会自动添加自动释放池,但是NSThread 和NSObject 不会(需要手动使用自动释放池,否则会出现内存泄漏);
2如果在后台线程中创建了autorelease的对象,需要使用自动释放池,否则会出现内存泄露;
3.当自动释放池被销毁或者耗尽(填满)时,对池中所有对象发送release消息;
4.所有的autorelease对象在出了作用域后会自动添加到最近一次创建的自动释放池中(自动释放池可嵌套);
RunLoop
问题
1.Runloop(一次唤醒) == event loop == 事件周期
2.NSThread、NSRunLoop 和 NSAutoreleasePool关系
1)每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建。
2)在主线程的 NSRunLoop 对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(0, 0) 获取到的线程)的每个 event loop 开始前,系统会自动创建一个 autoreleasepool ,并在 event loop 结束时 drain 。
3) 主线程RunLoop默认启动的,其他线程默认关闭;
如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要;
在任何一个Cocoa程序的线程中,都可以通过以下代码来获取到当前线程的run loop。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
runloop执行完毕之后,就会进入休眠 , 只有在某个情况下触发了,才会再次调用;
NSAutoreleasePool 中还提到,每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程。当一个线程停止的时候,它会自动释放掉和它结合的所有自动释放池。
1) Run loop接收输入事件来自两种不同的来源:输入源(input source)和定时源(timer source)。两种源都使用程序的某一特定的处理例程来处理到达的事件。
2) 当你创建输入源,你需要将其分配给run loop中的一个或多个模式;模式只会在特定事件影响监听的源。多数情况下,run loop运行在默认模式下,但是你也可以使其运行在自定义模式。若某一源在当前模式下不被监听,那么任何其生成的消息只在run loop运行在其关联的模式下才会被传递。
Runloop与autoreleasePool联系
当开启或者唤醒runloop的时候,会创建一个autoreleasePool;kCFRunLoopBeforeWaiting | kCFRunLoopExit当runloop睡眠之前或者退出runloop的时候会释放autoreleasePool;
线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)
当程序运行时,会有多条线程去执行进程中的任务,每个线程对应一个Runloop,实现原理是创建一个全局字典,key是线程对象,value是Runloop对象,从而线程和Runloop会一一对应。Run loop同时也负责autorelease pool的创建和释放,每当一个运行循环(事件循坏)结束的时候,它都会释放一次autorelease pool,同时pool中的所有自动释放类型变量都会被释放掉。
苹果在主线程 RunLoop 里注册了两个 Observer:
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),
BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
苹果没有为我们提供直接创建 runLoop 的 API, 而是提供了两个获取 runLoop 的方法, 就是 [NSRunLoop mainRunLoop] 和 [NSRunLoop currentRunLoop], 而子线程的 runLoop 正是在这个获取的时候才会被创建的, 你不获取就不会创建(除了主线程的 runLoop 是程序一启动就创建好并启动的), 所以 [NSRunLoop currentRunLoop] 的主要目的就是创建出当前子线程的 runLoop.
CFRunLoopSourceRef
事件产生的地方,分为Source0和Source1两种
Source0: 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
Source1: 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程
Runloop运行循环,其实内部就是do-while循环,底层是GCD实现的,在这个循环内部不断处理各种事件(比如Source,Timer.Observer) 一个线程对应一个Runloop(线程是Key Runloop是value)主线程默认启动,子线程要手动启动(调用run) Runloop只能选择一个Mode启动,如果当前Mode中没有任何(set)Source和(Array)Timer,(Array)Observer,就会退出Runloop
Runloop是如何实现睡眠和唤醒的呢?
这就要从OSX和iOS操作系统来分析了,OSX和iOS操作系统分为两大部分,分别是应用层和内核层。应用层中的应用程序包括很多线程,每个线程会通过mach_msg向内核层发送消息,内核层会把消息添加到cpu处理的消息队列中,等待cpu处理。当线程发送mach_msg()消息时,是告诉cpu我没有任务要处理了,我要进行休眠了。这时,该线程就不会浪费cpu资源了。但是线程会告诉cpu,如果有任务需要我来处理的时候,cpu要给我发送消息,把我唤醒,然后我来处理任务。大家要知道,内核层包括cpu,硬盘,摄像头,鼠标键盘灯输入设备,所以当用户与界面交互的时候,交互事件是从内核向上抛给应用程序的.