iOS开发基础39-RunLoop

在iOS开发中,RunLoop是一个极其重要的概念,它不仅负责保持应用程序的持续运行,还能处理各种事件,提高应用的性能和响应速度。。

一、什么是RunLoop

1. 定义

从字面意义上讲,RunLoop即运行循环。它的基本作用包括:

  • 保持程序的持续运行: 确保应用程序在用户关闭之前一直处于活跃状态。
  • 处理各种事件: 如触摸事件、定时器事件、Selector事件等。
  • 节省CPU资源: 合理分配CPU资源,在需要处理任务时处理任务,空闲时休眠。

2. 没有RunLoop的情况

int main(int argc, char * argv[]) {
    NSLog(@"execute main function");
    return 0;
}

在没有RunLoop的情况下,第3行代码执行完后,程序就会立即退出。

3. 有了RunLoop

当有RunLoop存在时,由于main函数内部启动了一个RunLoop,程序并不会马上退出,而是保持持续运行状态。

4. main函数中的RunLoop

main函数中,UIApplicationMain函数负责启动RunLoop

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

UIApplicationMain函数启动RunLoop,使其保持程序的持续运行。这个默认的RunLoop是与主线程相关联的。

验证:

可以看到----------不会打印,一直在执行UIApplicationMain

5. 模拟RunLoop内部实现

其实RunLoop内部就是一个do-while循环,不断地处理各种任务,例如Source、Timer、Observer。

void message(int num) {
    printf("执行第%i个任务", num);
}

int main(int argc, const char * argv[]) {
    do {
        printf("有事吗? 没事我睡了");
        int number;
        scanf("%i", &number);
        message(number);
    } while (1);
    return 0;
}

二、RunLoop对象

1. RunLoop的API

在iOS中,有两套API可访问和使用RunLoop

  • Foundation APINSRunLoop
  • Core Foundation APICFRunLoopRef

NSRunLoop是基于CFRunLoopRef的OC包装,研究CFRunLoopRef可以更深入地了解RunLoop的内部结构。

2. RunLoop资料

苹果官方文档:Run Loop Management

CFRunLoopRef是开源的,可以参考:CF Source Code

3. RunLoop与线程

每条线程都有一个唯一的与之对应的RunLoop对象。

  • 主线程的RunLoop已经自动创建。
  • 子线程的RunLoop需要主动创建。

RunLoop在第一次获取时创建,在线程结束是销毁。

4. 获得RunLoop对象的方法

  • Foundation (OC语言)

    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    
  • Core Foundation (C语言)

    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
    

三、RunLoop相关类

在Core Foundation中,涉及RunLoop的主要有5个类:

1. CFRunLoopRef

CFRunLoopRefRunLoop的核心类,用于管理运行循环。

2. CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式。一个RunLoop包含多个Mode,每个Mode包含若干个Source、Timer和Observer。每次启动RunLoop时,必须指定一个Mode,这个Mode称为CurrentMode。切换Mode需要退出Loop,再重新指定。

系统注册了以下几个默认的Mode:

  • kCFRunLoopDefaultMode: 应用程序的默认Mode。
  • UITrackingRunLoopMode: 用户界面追踪Mode。
  • UIInitializationRunLoopMode: 应用程序初始化时的Mode。
  • GSEventReceiveRunLoopMode: 系统事件接收Mode。
  • kCFRunLoopCommonModes: 占位用的Mode。

3. CFRunLoopSourceRef

CFRunLoopSourceRef代表事件源(输入源),可以分为:

  • Source0:非基于Port的事件源。
  • Source1:基于Port的事件源。

4. CFRunLoopTimerRef

CFRunLoopTimerRef代表基于时间的触发器,与NSTimer类似,用于定时执行任务。

5. CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,可以监听RunLoop的状态变化,如进入Loop、即将处理Timers、即将处理Sources、即将进入休眠、已从休眠唤醒、即将退出Loop等。

// 1.创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
    kCFAllocatorDefault,
    kCFRunLoopAllActivities,
    YES,
    0,
    ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入loop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timers");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理sources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出loop");
                break;
            default:
                break;
        }
    }
);

CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);

四、RunLoop处理逻辑

RunLoop的处理逻辑可以概括为以下步骤:

  1. 通知观察者即将进入RunLoop
  2. 通知观察者即将处理Timer
  3. 通知观察者即将处理Source
  4. 如果有定时任务,处理Timer
  5. 如果有事件源,处理Source
  6. 通知观察者即将进入休眠。
  7. 进入休眠,等待事件。
  8. 被事件唤醒,跳回步骤2。
  9. 通知观察者即将退出RunLoop

五、RunLoop应用

1. NSTimer的模式限制

NSTimer必须添加到RunLoop中才能执行,而且只能在指定的Mode下运行。

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 或者通过scheduled方法创建,自动添加到默认模式
NSTimer *scheduledTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];

2. GCD定时器

GCD定时器可以在指定的队列中执行,极其高效和灵活。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), 2 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"定时器触发");
});
dispatch_resume(timer);

3. UIImageView显示图片

UIImageView在指定的Mode下才能设置图片,建议在主线程进行。

[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageNamed:@"example"] waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];

4. 常驻线程

常驻线程需要一个RunLoop来保持活跃状态。

NSThread *thread = [[NSThread alloc] initWithBlock:^{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}];
[thread start];

5. 自动释放池

RunLoop在某些活动时会自动创建和释放自动释放池。常见的活动包括进入RunLoop、即将进入休眠、唤醒、退出RunLoop等。

结论

RunLoop是iOS系统中至关重要的机制,通过合理配置RunLoop,可以保持应用程序的持续运行,处理各种事件,提高应用性能。掌握RunLoop的工作原理和应用场景,能够帮助开发者设计出更加健壮和高效的应用程序。

posted @   Mr.陳  阅读(557)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示