什么是RunLoop
一般来讲,一个线程一次只能执行一个任务,任务完成后线程就会退出。如果每执行一个任务都创建一个线程会非常浪费资源,所以我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑为:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop,其在没有处理消息时休眠以避免CPU资源占用、在有消息到来时立刻被唤醒。
RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
OSX/iOS 系统中,RunLoop有两个这样的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef是在CoreFoundation框架内的,它提供了纯 C 函数的 API,这些 API 都是线程安全的。NSRunLoop是基于CFRunLoopRef的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
CFRunLoopRef 的代码是开源的,你可以在这里下载到整个 CoreFoundation 的源码来查看。Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。
RunLoop 与线程的关系
iOS开发遇到两种线程对象:pthread_t和NSThread,之前苹果文档说NSThread是对pthread_t的封装,但是现在那份文档已经失效了,有可能现在都是直接包装自更底层的mach thread。CFRunLoop是基于pthread来管理的
苹果不允许直接创建Runloop,只提供了两个自动获取的函数,CFRunloopGetMain()和CFRunLoopGetCurrent()。这两个函数内部的逻辑大概如下:
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
从上面的代码可以看出:
- 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里,该字典以线程对象
pthread_t为key,以RunLoop对象为value - 线程刚创建时并没有
RunLoop,如果不主动获取,那它一直都不会有。 RunLoop的创建是发生在第一次获取时,创建后便会添加进全局字典中,即每一个线程有且仅有一个与之关联的RunLoop对象,在线程结束时将RunLoop对象销毁。- 只能在一个线程的内部获取其 RunLoop(主线程除外)。
苹果官方也给出了一个 RunLoop 模型图,如下:

从上图也能看出,一个线程会关联一个RunLoop对象,RunLoop对象会一直循环,直到超时或收到退出指令。在无限循环的过程中会一直处理到来的事件,右侧将事件分为了两类,一类是Input sources这部分包括基于端口的source1事件,开发者提交的各种source0事件,调用performSelector:onThread:方法事件,还有一类Timer sources,就是常用的定时器事件,这些事件在程序运行期间会不断产生之后会由RunLoop对象检测并负责处理相关事件。
关于 RunLoop 的类
在深入了解RunLoop的运行机制前,我们先了解下CoreFoundation里面关于RunLoop的5个类:
- CFRunLoopRef:代表 RunLoop 的对象
- CFRunLoopModeRef:代表 RunLoop 的运行模式
- CFRunLoopSourceRef:就是 RunLoop 模型图中提到的输入源 / 事件源
- CFRunLoopTimerRef:就是 RunLoop 模型图中提到的定时源
- CFRunLoopObserverRef:观察者,能够监听 RunLoop 的状态改变
它们之间的关系如下:

可以看出,一个RunLoop对象(CFRunLoopRef)中包含若干个运行模式(CFRunLoopModeRef)。而每一个运行模式下又包含若干个输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef)。每次调用 RunLoop的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
CFRunLoopRef 类
CFRunLoopRef 是 Core Foundation 框架下 RunLoop对象类。
在Core Foundation中获取RunLoop对象的方法:
CFRunLoopGetCurrent(); // 获得当前线程的 RunLoop 对象
CFRunLoopGetMain(); // 获得主线程的 RunLoop 对象
在Foundation下获取RunLoop对象的方法:
[NSRunLoop currentRunLoop]; // 获得当前线程的 RunLoop 对象
[NSRunLoop mainRunLoop]; // 获得主线程的 RunLoop 对象
CFRunLoopMode和CFRunLoop的结构:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
_commonModes:是一个集合,里面保存的是具有“Common”的mode的名称_commonModeItems:也是一个集合,里面保存的是那些需要同步添加到具有“Common”属性的Mode中的Source/Timer/Observer集合
CFRunLoopModeRef 类
系统默认注册了5个Mode:
kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
值得注意的是kCFRunLoopCommonModes就是被标记为“Common”属性的Mode,可以看出这种模式不是一种真正的模式,仅仅是标识其他模式是否需要同步添加_commonModeItems中的Source/Timer/Observer。
系统默认将kCFRunLoopDefaultMode和UITrackingRunLoopMode添加到了_commonModes中,即标识为“Common”属性,所以当RunLoop运行在这两种模式中会自动同步添加_commonModeItems中的Source/Timer/Observer。
在这里看到更多的苹果内部的 Mode,但那些 Mode 在开发中就很难遇到了
CFRunLoopTimerRef 类
CFRunLoopTimerRef是定时源,是基于时间的触发器。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调,基本上就是NSTimer。
通过对上面三个类的理解,我们可以解释一个关于NSTime的问题
在ViewController.m添加下面的代码并运行,每两秒打印“run”,但是当我们滑动UITableView的时候,会发现定时器在滑动期间,计时停滞了。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 添加一个tableView
[self.view addSubview:self.myTableV];
// 定义一个定时器,
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 将定时器添加到当前RunLoop的NSDefaultRunLoopMode下
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)run{
NSLog(@"***run***");
}
造成上面问题的原因:
程序刚开始运行的时候,RunLoop处于NSDefaultRunLoopMode下。当我们滑动的时候,RunLoop就结束NSDefaultRunLoopMode,切换到了UITrackingRunLoopMode模式下,而这个模式下没有添加NSTimer,所以NSTimer就不工作了。当停止滑动的时候,RunLoop就结束了UITrackingRunLoopMode模式,又切换回NSDefaultRunLoopMode模式,所以NSTimer就又开始正常工作了。
解决方案:
1、将NSTimer也添加到UITrackingRunLoopMode中,但是方式很不优雅
2、通过上面CFRunLoopRef和CFRunLoopModeRef的介绍,我们知道只需将[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode];中的NSDefaultRunLoopMode修改为NSRunLoopCommonModes也可以解决。因为将NSTimer加入到NSRunLoopCommonModes中就是把其加入到_commonModeItems集合中,这样在滑动时就会自动同步添加NSTimer到UITrackingRunLoopMode模式下,所以定时器也可以得到执行。
CFRunLoopSourceRef 类
CFRunLoopSourceRef是事件源,CFRunLoopSourceRef有两种分类方法。
按照官方文档来分类
- Port-Based Sources(基于端口)
- Custom Input Sources(自定义)
- Cocoa Perform Selector Sources
按照函数调用栈来分类
- Source0 :非基于Port,它并不能主动触发事件。使用时,你需要先调用
CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。 - Source1:基于Port,被用于通过内核和其他线程相互发送消息。这种
Source能主动唤醒RunLoop的线程,其回调函数为__IOHIDEventSystemClientQueueCallback()
source0 有公开的 API 可供开发者调用,source1 却只能供系统使用,而且 source1 的实现原理是基于 mach_msg 函数,通过读取某个 port 上内核消息队列上的消息来决定执行的任务。
其实上面的这两种分类方式没有区别,只不过第一种是通过官方理论来分类,第二种是在实际应用中通过调用函数来分类。
下面我们了解一下函数调用栈和Source。
在ViewController中新建一个UIButton,然后在其触发事件中打断点,在xcode左侧可以看到

点击事件为:首先程序启动,调用17行的main函数,main函数调用16行UIApplicationMain函数,然后一直往上调用函数,最终调用到0行的clickMethod函数,即点击函数。
注意
我们在12行看到的是
Sources0,是否意味着点击事件是属于Sources0函数的,点击事件就是在Sources0中处理的呢?
其实还是系统注册的那个
source1接收IOHIDEvent,只是之后在回调__IOHIDEventSystemClientQueueCallback()内触发的Source0,Source0再触发的_UIApplicationHandleEventQueue(),所以UIButton事件看到是在Source0内的。
当 RunLoop 进行回调时,一般都是通过一个很长的函数调用出去 (call out), 当你在你的代码中下断点调试时,通常能在调用栈上看到这些函数。下面是这几个函数的整理版本,如果你在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了:
{
/// 1. 通知Observers,即将进入RunLoop
/// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. 通知 Observers: 即将触发 Timer 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 触发 Source0 (非基于port的) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即将进入休眠
/// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,线程被唤醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
CFRunLoopObserverRef 类
CFRunLoopObserverRef是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop 1
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer 2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 32
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 64
kCFRunLoopExit = (1UL << 7), // 即将退出Loop 128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听全部状态改变
};
上面的 Source/Timer/Observer被统称为 mode item,一个 item可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
RunLoop 的内部逻辑
根据苹果文档里的说明,RunLoop内部逻辑图:

RunLoop的具体顺序为:
1、通知观察者RunLoop已经启动
2、通知观察者即将要开始的Timer
3、通知观察者任何非基于端口的Source0即将触发
4、触发上步准备好的非基于端口的Source0
5、如果有Source1准备好并处于等待状态,立即启动;并进入步骤9
6、通知观察者RunLoop的线程即将进入休眠状态
7、调用 mach_msg 等待接受 mach_port 的消息,线程将进入休眠直到 被下面某一个事件唤醒:
* 一个基于 port 的Source 的事件
* 一个 Timer 到时间了
* RunLoop 自身的超时时间到了
* 被其他什么调用者手动唤醒
8、通知观察者RunLoop的线程刚刚被唤醒
9、处理唤醒时未处理的事件:
* 如果用户定义的Timer 到时间了,处理定时器事件并重启
* RunLoop。进入步骤2
* 如果输入源激活,传递相应的消息
* 如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2
10、通知观察者RunLoop即将退出。
RunLoop 的核心就是一个 mach_msg(),RunLoop 调用这个函数去接收消息,如果没有别人发送port消息过来,内核会将线程置于等待状态。
其内部代码整理如下:
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里如果没有input source 与 timer就会返回。就算有observer也是会返回的。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 6. 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
RunLoop的应用
AutoreleasePool
程序运行后产生的两个CFRunLoopObserver。
-
一个监听
RunLoop对象进入循环的事件,执行回调函数_wrapRunLoopWithAutoreleasePoolHandler,并且优先级order为-2147483647即32位整数的最小值,保证了它的优先级最高。在回调内会调用_objc_autoreleasePoolPush函数来创建AutoreleasePool,由于它的优先级最高,所以能够保证自动释放池在其他回调执行前得到创建。 -
另一个监听器监听
RunLoop对象进入休眠和退出循环的事件,回调函数同样是_wrapRunLoopWithAutoreleasePoolHandler,而优先级为2147483647即32位整数的最大值,保证它的优先级最低。对于监听进入休眠状态时回调函数内首先会调用_objc_autoreleasePoolPop函数来释放AutoreleasePool然后使用_objc_autoreleasePoolPush函数重新创建一个自动释放池。优先级最低保证了释放操作是在其他所有回调执行之后发生。
main函数就是被@autoreleasepool包围着,所以在主线程中创建的任何对象都会及时被释放。
AutoreleasePool的一个小问题
通过上面的讲解,我们会发现需要进入到Runloop中才会释放旧的自动释放池然后创建新的自动释放池。但如果程序在处理一个比较耗时且占用内存较大的任务时,在没有任何事件产生的情况下是不会进入到Runloop中,那些本应该立即释放的局部变量就不会被释放,程序就会被占用过多的内存
- (void)btnClickedHandler
{
NSArray *urls = ;
for (NSURL *url in urls) {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
}
通过监听Runloop状态的代码,可以发现,在for循环结束前都不会改变Runloop的状态,Runloop一直处于休眠的状态,所以for循环大量创建的局部变量不会得到释放,就会占用过多的内存,直到Runloop改变状态,此时就需要手动添加@autoreleasepool来手动创建和释放自动释放池:
- (void)btnClickedHandler
{
NSArray *urls = ;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
}
}
RunLoop实现常驻内存的线程
在开发应用程序的过程中,如果后台操作特别频繁,经常会在子线程做一些耗时操作(下载文件、后台播放音乐等),我们最好能让这条线程永远常驻内存。
+ (void)entryPoint
{
//设置当前线程名为MyThread
[[NSThread currentThread] setName:@"MyThread"];
//获取NSRunLoop对象,第一次获取不存在时系统会创建一个
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
/*
添加一个Source1事件的监听端口
RunLoop对象会一直监听这个端口,由于这个端口不会有任何事件到来所以不会产生影响
监听模式是默认模式,可以修改为Common
*/
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//启动RunLoop
[runloop run];
}
+ (NSThread *)longTermThread
{
//静态变量保存常驻内存的线程对象
static NSThread *longTermThread = nil;
//使用GCD dispatch_once 在应用生命周期只执行一次常驻线程的创建工作
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//创建一个线程对象,并执行entryPoint方法
longTermThread = [[NSThread alloc] initWithTarget:self selector:@selector(entryPoint) object:nil];
//启动线程,启动后就会执行entryPoint方法
[longTermThread start];
});
return longTermThread;
}
- (void)viewDidLoad
{
//获取这个常驻内存的线程
NSThread *thread = [ViewController longTermThread];
//在该线程上提交任务
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
}
上面的例子主要利用了一个source1事件的监听,由于Mode的Source/Observer/Timer中的Observer不为空,所以RunLoop不会退出循环,能够常驻内存。