一、为什么NSTimer有时候不好使?
因为创建的 NSTimer 就不会工作了。默认是被加入到defaultMode,所以当Runloop的mode发生变化的时候,当前的NSTimer就不会工作了
二、AFNetworking中如何运用Runloop?
AFURLConnectionOperation这个类是基于NSURLConnection构建的,其希望能在后台线程中接收Delegate回调,因此AFNetworking单独创建了一个线程,并在这个线程中启动了一个Runloop。 开启RunloopRunLoop启动前内部必须要有至少一个 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前 先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并 在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。
注意self.runLoopModes allObjects 当需要这个后台线程执行任务时,AFNetworking 通过调用[NSObject performSelector:onThread:..]将这个任务扔到了后台线程的 RunLoop
三、autoreleasePool在何时被释放?
App启动后,苹果在主线程RunLoop里注册了两个Observer其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。
第一个Observer监视的事件是Entry(即将进入Loop),其回调内会调用objc_autoreleasePoolPush() 创建自动释放池.其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个Observer监视了两个事件:BeforeWaiting(准备进入休眠)时调用objc_autoreleasePoolPop()和objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出 Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的,这些回调会被RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
四、PerformSelector 的实现原理?
当NSObject的performSelecter:afterDelay:后,实际上内部会创建Timer并添加到当前线程到RunLoop中,所以如果当前线程没有RunLoop这个方法就会失效。
当调用performSelecter:onThread:时,实际上会创建一个Timer驾到对应的线程去,同样的,如果对应线程没有RunLoop该方法会失效。
五、PerformSelector:afterDelay:这个方法在子线程中是否起作用?为什么?怎么解决?
不起作用,子线程默认没有Runloop,也就没有Timer。解决办法时可以通过使用GCD来实现:dispatch_after
六、RunLoop的Mode
关于Mode首先要知道一个Runloop对象中可能包含多个Mode,并且每次调用Runloop的主函数时,只能指定其中一个Mode(CurrentMode)切换Mode,需要重新指定一个Mode。主要时为了分隔开不同的Source,Timer,Observer,让他们之间互不影响。
当 RunLoop 件的运行在Mode1上时,是无法接受处理Mode2或Mode3上的Source、Timer、Observer事件的。
总共是有五种CFRunLoopMode:
kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行
UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑 动时不受其他 Mode 影响) UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不 再使用
GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步 Source/Timer/Observer 到多个 Mode 中的一种解决方案
七、RunLoop的实现机制
对于RunLoop而言最核心的事情就是保证线程在没有消息的时候休眠,在有消息时唤醒,以提高程序性能。RunLoop这个机制是依靠系统内核来完成的(苹果操作系统核心组件Darwin中的Mach)
RunLoop 通过 mach_msg()函数接收、发送消息。它的本质是调用函数 mach_msg_trap(),相当于是 一个系统调用,会触发内核状态切换。在用户态调用 mach_msg_trap()时会切换到内核态;内核态中内 核实现的 mach_msg()函数会完成实际的工作。 即基于 port 的 source1,监听端口,端口有消息就会触发回调;而 source0,要手动标记为待处理和手动 唤醒 RunLoop
大致逻辑为:
1、通知观察者 RunLoop 即将启动。
2、通知观察者即将要处理 处理 Timer 事件
3、通知观察者即将要处理source0 事件
4、处理source0事件。
5、如果基于端口的源(Source1)准备好并处于等待状态,进入步骤 9。
6、通知观察者线程即将进入休眠状态。
7、将线程置于休眠状态,由用户态切换到内核态,直到下面的任一事件发生才唤醒线程。
(1)一个基于 port 的 Source1的事件(图里应该是source0)。
(2)一个 Timer 到时间了。
(3) RunLoop 自身的超时时间到了。
(4) 被其他调用者手动唤醒。
8、通知观察者线程将被唤醒。
9、处理唤醒时收到的事件。
(1)如果用户定义的定时器启动,处理定时器事件并重启 RunLoop。进入步骤 2。
(2) 如果输入源启动,传递相应的消息。
(3)如果 RunLoop 被显示唤醒而且时间还没超时,重启 RunLoop。进入步骤 2
10、通知观察者RunLoop结束。
代码如下:
// 全局的 dictionary, key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 第一次进入时,创建全局 dictionary
if (!__CFRunLoops) {
// 创建可变字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable();
// 先创建主线程的 RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 主线程的 RunLoop 存进字典中
CFDictionarySetValue(dict, pthread_main_thread_np(), mainLoop);
}
// 用 传进来的线程 作 key,获取对应的 RunLoop
CFRunLoopRef loop = CFDictionaryGetValue(__CFRunLoops, t);
// 如果获取不到,则新建一个,并存入字典
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
}
return loop;} // 获取主线程的 RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np());
return __main;
}
// 获取当前线程的 RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
return _CFRunLoopGet0(pthread_self());
}
RunLoop对应关系
八、RunLoop和线程
线程和 RunLoop 是一一对应的,其映射关系是保存在一个全局的
自己创建的线程默认是没有开启 RunLoop 的Dictionary里
1、怎么创建一个常驻线程?
1、为当前线程开启一个RunLoop(第一次调用 [NSRunLoop currentRunLoop]方法时实际是会先去创建一个RunLoop)
3,启动该RunLoop 创建RunLoop
2、向当前 RunLoop 中添加一个Port/Source等维持RunLoop的事件循环(如果RunLoop的mode中一个item都没有,RunLoop 会退出)
2、输出下边代码的执行顺序 题目
答案是 1423,test 方法并不会执行。 原因是如果是带 afterDelay 的延时函数,会在内部创建一个 也就是如果当前线程没有开启 RunLoop,该方法会失效。 那么我们改成:
image.png 然而 test 方法依然不执行。 原因是如果 RunLoop 的 mode 中一个 item 都没有,RunLoop 会退出。即在调用 RunLoop 的 于其 mode 中没有添加任何 item 去维持 RunLoop 的时间循环,RunLoop 随即还是会退出。 所以我们自己启动 RunLoop,一定要在添加 item 后 image.png3、怎样保证子线程数据回来更新 UI 的时候不打断用户的滑动操作?
当我们在子请求数据的同时滑动浏览当前页面,如果数据请求成功要切回主线程更新 UI,那么就会影响当 前正在滑动的体验。 我们就可以将更新 UI 事件放在主线程的 NSDefaultRunLoopMode 上执行即可,这样就会等用户不再滑动页 面,主线程 RunLoop 由 UITrackingRunLoopMode 切换到 NSDefaultRunLoopMode 时再去更新 UI image.png
网友评论