iOS开发中,Runtime的详细介绍,runloop的详细介绍

155 阅读7分钟

iOS Runtime 详细介绍

  1. 一、Runtime 概述
  • Runtime(运行时)是 Objective-C 的核心底层机制,它负责将代码的编译时逻辑转换为运行时动态行为。
  • Objective-C 作为一门动态语言,其特性(如动态类型、动态方法调用)均依赖 Runtime 实现。
  • Runtime 本质是一套 C 语言 API(位于 <objc/runtime.h>),提供了操作类、对象、方法等的底层接口。
  1. 二、Runtime 核心组成
  • 对象(Object)与类(Class)
  • 对象:Objective-C 中所有对象本质是 objc_object 结构体,包含一个指向类的指针 isa(即“对象的类型”)。
struct objc_object {
    Class isa; // 指向对象所属的类
};
  • 类:类本身也是对象(称为“元类”,Meta Class),Class 结构体包含类的方法列表、属性列表、协议列表等。
struct objc_class {
    Class isa; // 指向元类
    Class super_class; // 父类
    const char *name; // 类名
    // 方法列表、属性列表、协议列表等...
};
  • 方法(Method) 方法由 objc_method 结构体表示,包含方法名(SEL)、实现(IMP,函数指针)、参数类型(types):
struct objc_method {
    SEL method_name; // 方法选择器(如 @selector(doSomething:))
    char *method_types; // 参数/返回值类型编码(如 "v@:" 表示无返回值、id selfSEL _cmd)
    IMP method_imp; // 方法实现的函数指针
};
  • SEL 与 IMP
  • SEL(方法选择器):字符串唯一标识方法名(如 @selector(viewDidLoad)),编译时生成,相同方法名的 SEL 全局唯一。
  • IMP(方法实现):指向方法具体代码的函数指针,通过 SEL 可找到对应的 IMP(即“消息发送”过程)。
  1. 三、Runtime 动态特性(核心机制)
  • 消息发送(Message Sending) Objective-C 方法调用本质是“发送消息”,语法 [obj doSomething] 会被编译器转换为 Runtime 函数 objc_msgSend(obj, @selector(doSomething))。
  • 流程:
  • 通过对象的 isa 指针找到类,在类的方法列表中查找 SEL 对应的 IMP;
  • 若未找到,递归查找父类;
  • 若最终未找到,触发“动态方法解析”或“消息转发”。
  • 动态方法解析(Dynamic Method Resolution) 当对象收到无法响应的消息时,Runtime 会先调用类的“动态方法解析”方法,允许类在运行时动态添加方法实现:
实例方法:+ (BOOL)resolveInstanceMethod:(SEL)sel
类方法:+ (BOOL)resolveClassMethod:(SEL)sel 示例:动态为对象添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(dynamicMethod)) {
        // 添加方法实现:参数为 (id self, SEL _cmd),无返回值
        class_addMethod([self class], sel, (IMP)dynamicMethodImpl, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void dynamicMethodImpl(id self, SEL _cmd) {
    NSLog(@"动态添加的方法被调用");
}
  • 消息转发(Message Forwarding) 若动态方法解析失败,Runtime 会进入“消息转发”流程,允许将消息转发给其他对象处理:
快速转发:通过 - (id)forwardingTargetForSelector:(SEL)aSelector 返回一个能处理该消息的对象(效率高,推荐优先使用)。
标准转发:若快速转发未处理,会调用 methodSignatureForSelector: 获取方法签名,再通过 forwardInvocation: 将消息转发给其他对象(可修改参数、返回值)。
  • 方法混淆(Method Swizzling) 通过 Runtime 交换两个方法的 IMP,实现“A 方法调用时执行 B 逻辑,B 方法调用时执行 A 逻辑”。常用于无侵入式埋点、崩溃防护等。
  • 示例:交换 ViewController 的 viewDidAppear: 方法
#import <objc/runtime.h>

@implementation ViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSEL = @selector(viewDidAppear:);
        SEL swizzledSEL = @selector(swizzled_viewDidAppear:);

        Method originalMethod = class_getInstanceMethod(self, originalSEL);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSEL);

        // 交换 IMP
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)swizzled_viewDidAppear:(BOOL)animated {
    // 执行原方法逻辑(此时 swizzled_viewDidAppear 已与 viewDidAppear 交换,调用自身即调用原方法)
    [self swizzled_viewDidAppear:animated];
    // 添加自定义逻辑(如埋点)
    NSLog(@"页面出现:%@", self.class);
}
@end
  1. 四、Runtime 常见应用场景
  • 关联对象(Associated Objects):为分类(Category)添加属性(分类默认不能添加成员变量,通过 objc_setAssociatedObject 实现)。
  • JSON 模型转换:通过 Runtime 获取类的属性列表,自动将 JSON 字典映射为模型对象(如 YYModel、MJExtension 底层实现)。
  • 崩溃防护:拦截未实现的方法(如 NSArray 越界、unrecognized selector),通过消息转发返回默认值避免崩溃。

iOS Runloop 详细介绍

一、Runloop 概述
  • Runloop(运行循环)是 iOS 中管理线程事件处理的核心机制,本质是一个 “事件循环”:不断从“事件源”接收事件、处理事件,直到线程结束。
  • 其核心作用是 “让线程在需要时工作,不需要时休眠”,避免线程空转浪费资源。
二、Runloop 核心作用
  • 保持线程存活:主线程默认启动 Runloop,因此能一直运行不退出;子线程默认无 Runloop,执行完任务后会销毁。
  • 事件处理:统一处理输入源事件(如触摸、网络回调、定时器)、UI 刷新事件等。
  • 线程休眠与唤醒:无事件时让线程进入休眠状态(降低 CPU 占用),有事件时唤醒线程处理。
三、Runloop 核心组成(基于 Core Foundation)
  • Runloop 相关接口位于 <CoreFoundation/CFRunLoop.h>,核心结构体为 CFRunLoopRef,其组成如下:

  • Runloop Mode

  • 运行模式(如 kCFRunLoopDefaultMode、UITrackingRunLoopMode),同一时间 Runloop 只能处于一种 Mode。}

  • Source

  • 事件源(输入源):分为 Port-Based Sources(如 NSMachPort)和 Custom Sources(自定义事件)。

  • Timer

  • 定时器源:基于时间触发的事件(如 NSTimer,需添加到 Runloop 才会生效)。

  • Observer

  • 观察者:监听 Runloop 状态变化(如进入、退出、处理事件前/后)。

四、Runloop 工作原理(生命周期)
  • Runloop 按固定流程循环运行,核心步骤如下:
  1. 通知 Observer:进入 Runloop(kCFRunLoopEntry)。
  2. 通知 Observer:即将处理 Timers(kCFRunLoopBeforeTimers)。
  3. 通知 Observer:即将处理 Sources(kCFRunLoopBeforeSources)。
  4. 处理所有就绪的 Sources(同步执行 Source 回调)。
  5. 若有 Source1 未处理,跳回步骤 2(Source1 是基于端口的异步事件,可能触发新事件)。
  6. 通知 Observer:即将进入休眠(kCFRunLoopBeforeWaiting),线程进入休眠(释放 CPU)。
  7. 休眠被唤醒(由 Source 事件、Timer 触发或外部唤醒),通知 Observer:结束休眠(kCFRunLoopAfterWaiting)。
    • 若唤醒源是 Timer:执行 Timer 回调,跳回步骤 2。
    • 若唤醒源是 Source:执行 Source 回调,跳回步骤 2。
    • 若唤醒源是 Runloop 停止:进入步骤 8。
  1. 通知 Observer:退出 Runloop(kCFRunLoopExit),Runloop 终止。
五、Runloop 与线程的关系
  • 一一对应:每个线程(包括主线程、子线程)都有唯一对应的 Runloop(通过 CFRunLoopGetCurrent() 获取),但子线程的 Runloop 默认未创建,需手动调用 CFRunLoopRun() 启动。
  • 主线程 Runloop:系统自动创建并启动(UIApplicationMain 函数中触发),Mode 默认为 kCFRunLoopDefaultMode(App 正常运行模式)和 UITrackingRunLoopMode(滑动模式,如 UIScrollView 滚动时切换到此 Mode,避免非滑动事件干扰)。
六、Runloop 常见应用场景
  • NSTimer 与 Runloop
  • NSTimer 需添加到 Runloop 才会生效,且受 Mode 影响。例如:
// 添加到默认 Mode:滑动时 Timer 会暂停(因滑动时 Runloop 切换到 UITrackingRunLoopMode)
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerFire) userInfo:nil repeats:YES];

// 添加到 Common Modes(包含 Default + Tracking 等 Mode):滑动时 Timer 不暂停
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerFire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
子线程保活 子线程默认执行完任务后销毁,通过启动 Runloop 可让其长期存活(用于处理异步回调):
// 子线程保活示例
- (void)keepThreadAlive {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
    [thread start];
}

- (void)threadMain {
    @autoreleasepool {
        // 创建并启动 Runloop(需添加 Source/Observer 避免立即退出)
        CFRunLoopSourceContext context = {0};
        CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        CFRelease(source);

        // 启动 Runloop(永久循环,需手动调用 CFRunLoopStop 退出)
        CFRunLoopRun();
    }
}
性能优化 通过 Observer 监听 Runloop 状态,可检测卡顿(如处理事件耗时超过阈值):
// 监听 Runloop 处理事件耗时(简化示例)
- (void)monitorRunloop {
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, 
        kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            static CFAbsoluteTime lastActivityTime;
            if (activity == kCFRunLoopBeforeSources) {
                lastActivityTime = CFAbsoluteTimeGetCurrent();
            } else if (activity == kCFRunLoopAfterWaiting) {
                CFAbsoluteTime cost = CFAbsoluteTimeGetCurrent() - lastActivityTime;
                if (cost > 0.1) { // 超过 100ms 视为卡顿
                    NSLog(@"Runloop 卡顿:%f ms", cost * 1000);
                }
            }
        });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
}

总结

  • Runtime 是 Objective-C 动态特性的底层支撑,通过操作类、对象、方法实现灵活的运行时行为(如方法转发、混淆),常用于框架开发、无侵入式功能扩展。
  • Runloop 是线程事件管理的核心,通过循环处理事件实现线程保活、事件调度,是 UI 响应、定时器、网络回调等功能的基础。
  • 两者共同构成了 iOS 应用的底层运行机制,理解它们有助于深入掌握 Objective-C 语言特性及系统运行原理。