iOS开发iOS开发面试

iOS - RunLoop 深入理解

2015-09-06  本文已影响8219人  Mitchell
作者:Mitchell 

Run loop 剖析:
Runloop 接收的输入事件来自两种不同的源:输入源(intput source)和定时源(timer source)。输入源传递异步事件。通常消息来自于其他线程或程序。定时源则传递同步事件,发生在特定时间或者重复的时间间隔。两种源都使用程序的某一特定的处理历程来处理到达的时间。


一、什么是RunLoop

int main(int argc,char * argv[]){
    NSLog(@"execute main function");---->程序开始
    return 0; ------------------------->程序结束
}
int main(int argc,char * argv[]){
    BOOL running = YES; -------->程序开始
    do {------------------------------
         // 执行各种任务,处理各种事件------持续运行
    }while(running);---------------------
    return 0;
}

二、main 函数中的 RunLoop

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

三、RunLoop 的输入源

  - (void)launchThread
{
NSPort* myPort = [NSMachPort port];
if (myPort)
{
//这个类持有即将到来的端口消息
[myPort setDelegate:self];
//将端口作为输入源安装到当前的 runLoop
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
//当前线程去调起工作线程
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class] withObject:myPort];
}

为了在线程间建立双向的通信,你需要让工作线程在签到的消息中发送自己的本地端口到主线程。主线程接收到签到消息后就可以知道辅助线程运行正常,并且􏰀供了发送消息给辅助线程的方法。
以下代码显示了主线程的 handlePortMessage:方法。当由数据到达线程的本地端口时,该方法被调用。当签到消息到达时,此方法可以直接从辅助线程里面检索端口并保存下来以备后续使用。

#define kCheckinMessage 100
//处理从工作线程返回的响应
 - (void)handlePortMessage:(NSPortMessage *)portMessage
{
//消息的 id
unsigned int message = [portMessage msgid];
//创建远的端口
NSPort* distantPort = nil;
if (message == kCheckinMessage)
{
//获取工作线程关联的端口,并设置给远程端口
distantPort = [portMessage sendPort];
//为了以后的使用保存工作端口
[self storeDistantPort:distantPort];
}
else
{
//处理其他的消息
}
    - ***b) 辅助线程的实现代码***

对于辅助工作线程,你必须配置线程使用特定的端口以发送消息返回给主要线程。
以下显示了如何设置工作线程的代码。创建了线程的自动释放池后,紧接着创建工作对象驱动线程运行。工作对象的*** sendCheckinMessage: ***方法创建了工作线程的本地端口并发送签到消息回主线程。

//根据端口信息启动线程
 +(void)LaunchThreadWithPort:(id)inData
{
//设置当前线程和主线程的通信端口
NSPort* distantPort = (NSPort*)inData;
//获取当前的工作类
MyWorkerClass* workerObj = [[self alloc] init];
//发送签到消息
[workerObj sendCheckinMessage:distantPort];
//释放
[distantPort release];
//让 runloop 处理事务
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while (![workerObj shouldExit]);
[workerObj release];
[pool release];

当使用 NSMachPort 的时候,本地和远程线程可以使用相同的端口对象在线程间进行单边通信。换句话说,一个线程创建的本地端口对象成为另一个线程的远程端口对象。
以下代码辅助线程的签到例程,该方法为之后的通信设置自己的本地端口,然后发送签到消息给主线程。它使用 LaunchThreadWithPort:方法中收到的端口对象做为目标消息。

//工作线程签到方法
 - (void)sendCheckinMessage:(NSPort*)outPort
{
//保留远程端口,以便将来使用
[self setRemotePort:outPort];
//创建并且传递工作线程的端口
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
//创建签到消息
NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
receivePort:myPort components:nil];
if (messageObj)
{
//完成配置消息并立即将其发送
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
- ***配置 NSMessagePort 对象***

为了和 NSMeaasgePort 的建立稳定的本地连接,你不能简单的在线程间传递端口
对象。远程消息端口必须通过名字来获得。在 Cocoa 中这需要你给本地端口指定一个名字,并将名字传递到远程线程以便远程线程可以获得合适的端口对象用于通信。以下代码显示端口创建,注册到你想要使用消息端口的进程。

//创建本地消息端口
NSPort* localPort = [[NSMessagePort alloc] init];
//配置对象并且将其添加到当前 runloop 中
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
//使用一个特殊的名字注册端口,名字必须是唯一的
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
name:localPortName];
- ***在 Core Foundation 中配置基于端口的源***

这部分介绍了在 Core Foundation 中如何在程序主线程和工作线程间建立双通道通信。
以下代码显示了程序主线程加载工作线程的代码。第一步是设置
CFMessagePortRef 不透明类型来监听工作线程的消息。工作线程需要端口的名称来建立连接,以便使字符串传递给工作线程的主入口函数。在当前的用户上下文中端口名必须是唯一的,否则可能在运行时造成冲突。

#define kThreadStackSize (8 *4096)
OSStatus MySpawnThread()
{
//创建一个本地的端口来接收响应
CFStringRef myPortName;
CFMessagePortRef myPort;
CFRunLoopSourceRef rlSource;
CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
Boolean shouldFreeInfo;
//创建端口的名称
myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
//创建端口
myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&MainThreadResponseHandler,
&context,
&shouldFreeInfo);
if (myPort != NULL)
{
//端口被成功的创建了,现在为它创建一个 runloop 源
rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
 if (rlSource) {
//将源添加到 runloop 中去
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
//一旦被安装,这些指针可以被释放了。
CFRelease(myPort); 
CFRelease(rlSource);
}}
//创建线程并且持续的运行
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint,
(void*)myPortName,kThreadStackSize,NULL,NULL,
NULL,0,&taskID));
}

端口建立而且线程启动后,主线程在等待线程签到时可以继续执行。当签到消息到达后,主线程使用 MainThreadResponseHandler 来分发消息,如下面代码 所示。这个函数􏰀取工作线程的端口名,并创建用于未来通信的管道。

#define kCheckinMessage 100
//获取主线程端口消息持有者
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
 SInt32 msgid, CFDataRef data, void* info)
{
//如果消息是签到的消息
 if (msgid == kCheckinMessage)
 {
    //消息端口
     CFMessagePortRef messagePort;
    //线程名称
     CFStringRef threadPortName;
    //数据长度
     CFIndex bufferLength = CFDataGetLength(data); 
    //读取数据流
    UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
     CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
    //设置线程端口名称
    threadPortName = CFStringCreateWithBytes (NULL, buffer,bufferLength,kCFStringEncodingASCII, FALSE);
    //你必须获得一个远程消息端口的名称
    messagePort = CFMessagePortCreateRemote(NULL,(CFStringRef)threadPortName);
    //如果有消息端口
     if (messagePort) {
    //保留线程公共的端口为了将来的引用
    AddPortToListOfActiveThreads(messagePort);
    //因为端口已经被之前的方法保留,所以这里将指针释放       
     CFRelease(messagePort);
}
//清空,释放线程端口名称
CFAllocatorDeallocate(NULL, buffer);
}
else
{
//处理其他的信息
}
return NULL;
}

主线程配置好后,剩下的唯一事情是让新创建的工作线程创建自己的端口然后签到。以下代码 显示了工作线程的入口函数。函数获取了主线程的端口名并使用它来创建和主线程的远程连接。然后这个函数创建自己的本地端口号,安装到线程的 runloop,最后连同本地端口名称一起发回主线程签到。

#工作线程的入口函数#
OSStatus ServerThreadEntryPoint(void* param)
{
//创建对主线程的远程端口
CFMessagePortRef mainThreadPort;
//获取主线程的名称
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
//释放作为参数传递过去的字符串的指针
CFRelease(portName);
//创建工作线程的端口
CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL,CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
//在线程的 context 信息中存储端口的信息以备以后的使用
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo;
Boolean shouldAbort = TRUE;
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL, myPortName,
 &ProcessClientRequest, &context, &shouldFreeInfo);
if (shouldFreeInfo)
{
//不能创建本地的端口,那么就杀死线程
MPExit(0);
}
CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (!rlSource){
//如果没有创建本地的端口,那么就杀死线程
MPExit(0);
}
//将源添加到当前的 runloop 中去
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
//一旦被安装完毕,那么就可以被释放了
CFRelease(myPort);
CFRelease(rlSource);
//将端口名称和签到信息打包,并且写入流
CFDataRef returnData = nil;
CFDataRef outData;
CFIndex stringLength = CFStringGetLength(myPortName);
UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
CFStringGetBytes(myPortName,
CFRangeMake(0,stringLength),
kCFStringEncodingASCII,
0,
FALSE,
buffer,
stringLength,
NULL);
outData = CFDataCreate(NULL, buffer, stringLength);
CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL,NULL);
//清空线程数据
CFRelease(outData);
CFAllocatorDeallocate(NULL, buffer);
//进入 runLoop
CFRunLoopRun();
}

一旦线程进入了它的runloop,所有发送到线程端口的时间都会由 ProcessClientRequest 函数来处理。

@interface RunLoopSource : NSObject
{
CFRunLoopSourceRef runLoopSource;
NSMutableArray* commands;
}
 - (id)init;
 - (void)addToCurrentRunLoop;
 - (void)invalidate;
// Handler method
 - (void)sourceFired;
// Client interface for registering commands to process
 - (void)addCommand:(NSInteger)command withData:(id)data;
 - (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
@end
//这些是 CFRunLoopSourceRef 的回调函数
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
CFRunLoopRef runLoop;
RunLoopSource* source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
 - (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;

尽管使用 Objective-C 代码来管理输入源的自定义数据,但是将输入源附加到 runloop 却需要使用基于 C 的回调函数(RunLoopSourceScheduleRoutine)。因为这个输入源只有一个客户端(主线程),它使用调度函数发送注册信息给应用程序的委托(delegate)。当委托需要和输入源通信的时候,它会使用 RunLoopContext 对象来完成。

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
//获取输入源
RunLoopSource* obj = (RunLoopSource*)info;
//获取应用程序的委托
AppDelegate* del = [AppDelegate sharedAppDelegate];
//根据 runloop输入源 和 runloop 获取 RunLoopContext,并将这个 RunLoopContext 注册到主线程,用于委托和输入源之间的通信。
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
//在主线程执行注册源
[del performSelectorOnMainThread:@selector(registerSource:)
withObject:theContext waitUntilDone:NO];

当输入源被告知的时候回处理自定义数据的那个例程,以下代码展示了这个和 RunLoopSource 对象相关回调的例程。这里只是简单的让 RunLoopSource 执行 sourceFired 方法,然后继续处理在命令缓存区出现的命令。

void RunLoopSourcePerformRoutine (void *info)
{
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}

使用 CFRunLoopSourceInvalidate 函数将输入源从 runloop 中移除,系统会调用输入源的取消例程。可以使用该例程来通知其他客户端该输入源已经失效,客户端应该释放输入源的引用。

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
//获取源
RunLoopSource* obj = (RunLoopSource*)info;
//获取系统代理
AppDelegate* del = [AppDelegate sharedAppDelegate];
//根据 源和 runloop 获取 RunLoopContext
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
//发送移除源的命令
[del performSelectorOnMainThread:@selector(removeSource:)
withObject:theContext waitUntilDone:YES];
}
- ***安装输入源到 RunLoop***

以下代码显示了 RunLoopSource 的 init 和 addToCurrentRunloop 的方法。Init 方法创建 CGFunLoopSourceRef 类型,该类型必须被附加到 runloop 里。它将 RunLoopSource 对象作为上下文引用参数,以便回调例程持有该对象的一个引用指针。输入源的安装只在工作线程调用 addToCurrentRunLoop 方法才发生,此时 RunLoopSourceScheduledRoutine 被调用。一旦输入源被添加到 runloop,线程就运行 runloop 并等待事件。

 - (id)init
{
CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,
//CFRunLoopSource 输入源
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine};
//命令数组
commands = [[NSMutableArray alloc] init];
return self;
}
 - (void)addToCurrentRunLoop
{
    //获取当前 runloop
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    //将源添加到 runloop
    CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}
- ***协调输入源的客户端***

为了让添加的输入源有用,需要维护它并从其他线程给它发送信号。输入源的主要工作就是将于输入源相关的线程置于休眠状态知道有事件发生。这就意味着程序中的要有其他线程知道该输入源信息并且有办法与之通信。
通知客户端关于输入源信息的方法之一就是当你的输入源开始安装到你的 runloop 上面后阿松注册请求。将输入源注册到任意数量的客户端,或者通过代理将输入源注册到感兴趣的客户端那。以下代码显示了应用委托定义的注册方法以及它在 RunLoopSource 对象的调度函数被调用时如何运行。该方法接收 RunloopSource 提供的 RunLoopContext 对象,然后将其添加到他自己的源列表里面。

//注册源
 - (void)registerSource:(RunLoopContext*)sourceInfo;
{
[sourcesToPing addObject:sourceInfo];
}
//移除源
 - (void)removeSource:(RunLoopContext*)sourceInfo
{
id objToRemove = nil;
for (RunLoopContext* context in sourcesToPing)
{
if ([context isEqual:sourceInfo])
{
objToRemove = context;
break;
}
}
if (objToRemove)
[sourcesToPing removeObject:objToRemove];
}
- ***通知输入源***

当客户端发送数据到输入源之后,它必须发送信号通知源并且唤醒它的 runloop。发送信号给源可以让 runloop 知道该源已经做好处理消息的准备。而且因为信号发送时线程可能处于休眠,所以必须总是显示的唤醒 runllop。如果不这样做的话会导致延迟处理输入源。
当客户端准备好处理加入缓冲区的命令后会调用此方法。

 - (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
//给 runLoop 发送信号
CFRunLoopSourceSignal(runLoopSource);
//显示的启动 runLoop
CFRunLoopWakeUp(runloop);

注意:你不应该试图通过自定义输入源处理一个 SIGHUP 或其他进程级别类型的信号。CoreFoundation 唤醒 run loop 的函数不是信号安全的,不能在你的应用信号处理例程(signalhandler routines)里面使用。关于更多信号处理例程,参阅 sigaction 主页。

scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:􏰂 
scheduledTimerWithTimeInterval:invocation:repeats:

上述方法创建了定时器并以默认模式把它们添加到当前线程的 run loop。
Core Foundation 创建定时器

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
&myCFTimerCallback, &context);

四、RunLoop 对象

//获取当前 runloop(NSRunLoop*)
 + (NSRunLoop *)currentRunLoop;
[NSRunLoop currentRunLoop];
//CFRunLoopRef
 - (CFRunLoopRef)getCFRunLoop CF_RETURNS_NOT_RETAINED;
[[NSRunLoop currentRunLoop]getCFRunLoop];
 - (void)threadMain {
 // The application uses garbage collection, so no autorelease pool is needed. 
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
 kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 if (observer) {
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
 CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode); 
}
// Create and schedule the timer.
 [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do{
 // Run the run loop 10 times to let the timer fire.
 [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
loopCount--;
  }
  while (loopCount);
}

五、RunLoop 与线程


六、获取RunLoop 对象

//获得当前线程的 RunLoop 对象
[NSRunLoop currentRunLoop];

//获得主线程的 RunLoop 对象
[NSRunLoop mainRunLoop];

//当前RunLoop
CFRunLoopGetCurrent();
//主线程 RunLoop
CFRunLoopGetMain();

七、NSRunLoop 相关类

    //1、创建timer
    //dispatchQueue:定时器将来回调的方法在哪个线程中执行
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //2.设置timer
    /*
     第一个参数:需要设置哪个timer
     第二个参数:指定定时器开始的时间
     第三个参数:指定间隔时间
     第四个参数:定时器的精准度,如果传0代表要求非常精准(系统会让计时器执行时间变得更加准确,性能消耗也会提高),如果传入一个大于0的值,代表我们允许的误差
     //例如传入60,就代表允许误差有60秒
     */
    //设置第一次执行的时间
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
    //DISPATCH_TIME_NOW
    dispatch_source_set_timer(timer,start , 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //3、设置timer的回调
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"%@",[NSRunLoop currentRunLoop]);
    });
    dispatch_resume(timer);
- 在 RunLoop 底层默认会调用这里
/// 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);
} 
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
}
- 监听的代码:
  - (void)viewDidLoad{
    [super viewDidLoad];
    //1、创建监听对象
    /*
     第一个参数:告诉系统如何给Observer对象分配存储空间
     第二个参数:需要监听的类型
     第三个参数:是否需要重复监听
     第四个参数:优先级
     第五个参数:监听到对应的状态之后的回调
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),
     kCFRunLoopBeforeTimers = (1UL << 1),
     kCFRunLoopBeforeSources = (1UL << 2),
     kCFRunLoopBeforeWaiting = (1UL << 5),
     kCFRunLoopAfterWaiting = (1UL << 6),
     kCFRunLoopExit = (1UL << 7),
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
     */
    CFRunLoopObserverRef oberver= CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"进入RunLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"即将醒来");
                break;
            case kCFRunLoopExit:
                NSLog(@"退出");
                break;
            default:
                break;
        }
    });
    //2、给主线程的RunLoop添加监听
    /*
     第一个参数:需要监听的 RunLoop 对象
     第二个参数:给指定的 RunLoop 对象添加的监听对象
     第三个参数:在哪种模式下监听
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), oberver, kCFRunLoopCommonModes);
NSTimer *timer = [NSTimer   scheduledTimerWithTimeInterval:2 target:self   selector:@selector(demo) userInfo:nil repeats:YES];
}
  - (void)demo{
    NSLog(@"%s",__func__);
}

我们会看到这几行打印会重复执行

2015-09-06 17:02:04.848 RunLoop观察者[35817:418636] 即将醒来
2015-09-06 17:02:04.849 RunLoop观察者[35817:418636] -[ViewController demo]
2015-09-06 17:02:04.849 RunLoop观察者[35817:418636] 即将处理Timer
2015-09-06 17:02:04.849 RunLoop观察者[35817:418636] 即将处理source
2015-09-06 17:02:04.849 RunLoop观察者[35817:418636] 即将进入睡眠
2015-09-06 17:02:06.848 RunLoop观察者[35817:418636] 即将醒来
2015-09-06 17:02:06.849 RunLoop观察者[35817:418636] -[ViewController demo]
2015-09-06 17:02:06.849 RunLoop观察者[35817:418636] 即将处理Timer
2015-09-06 17:02:06.849 RunLoop观察者[35817:418636] 即将处理source
2015-09-06 17:02:06.849 RunLoop观察者[35817:418636] 即将进入睡眠

八、关于 RunLoop 的理解

  seconds = 9999999999.0
- 线程挂了。

九、RunLoop 应用场景

        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    NSLog(@"%@",[NSRunLoop currentRunLoop]);
- 只有两种状态
     _wrapRunLoopWithAutoreleasePoolHandler:activities = 0x1  = 1
     _wrapRunLoopWithAutoreleasePoolHandler:activities = 0xa0 = 160
- 对比 RunLoop 的活动状态:
对比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
     }
- 得出结论:
     + _wrapRunLoopWithAutoreleasePoolHandler:activities = 0x1 = 1 = 即将进入RunLoop创建一个自动释放池
     + _wrapRunLoopWithAutoreleasePoolHandler:activities = 0xa0 = 160 = 128+32
     + 32: 即将进入休眠 1、销毁一个自动释放池 2、创建一个新的自动释放池
     + 128:即将退出RunLoop 销毁一个自动释放池

上一篇 下一篇

猜你喜欢

热点阅读