iOS Runtime 和 RunLoop 详解,原理示例以及使用场景

789 阅读2分钟

iOS Runtime 和 RunLoop 详解


一、Runtime(运行时)

1. 作用与原理
  • 作用:Objective-C 的动态运行时系统,负责方法调用、类与对象管理、消息转发等。核心机制包括:

    • 动态消息分发(objc_msgSend)
    • 方法交换(Method Swizzling)
    • 关联对象(Associated Objects)
    • 动态类创建和方法添加
  • 原理:基于 C 语言 API(如 objc/runtime.h),通过 isa 指针和类结构体动态解析方法实现。

2. 使用场景
  • 方法交换:Hook 方法实现(如日志记录)。
  • 关联对象:为分类(Category)添加存储属性。
  • 动态创建类:在运行时生成新类(如 JSON 模型解析)。

3. Objective-C 示例

方法交换(Method Swizzling)

#import <objc/runtime.h>

@implementation UIViewController (Logging)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSel = @selector(viewWillAppear:);
        SEL swizzledSel = @selector(log_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(self, originalSel);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSel);
        
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)log_viewWillAppear:(BOOL)animated {
    NSLog(@"ViewWillAppear: %@", self);
    [self log_viewWillAppear:animated]; // 调用原方法
}
@end

关联对象(Associated Objects)

#import <objc/runtime.h>

@interface UIView (CustomProperty)
@property (nonatomic, strong) NSString *customId;
@end

@implementation UIView (CustomProperty)
- (void)setCustomId:(NSString *)customId {
    objc_setAssociatedObject(self, @selector(customId), customId, OBJC_ASSOCIATION_RETAIN);
}
- (NSString *)customId {
    return objc_getAssociatedObject(self, @selector(customId));
}
@end
4. Swift 示例

方法交换(需继承 NSObject 并使用 @objc dynamic

extension UIViewController {
    @objc dynamic func log_viewWillAppear(_ animated: Bool) {
        print("ViewWillAppear: (self)")
        log_viewWillAppear(animated) // 调用原方法
    }
    
    static func swizzleViewWillAppear() {
        let originalSelector = #selector(UIViewController.viewWillAppear(_:))
        let swizzledSelector = #selector(UIViewController.log_viewWillAppear(_:))
        
        guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector) else { return }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

// 在 AppDelegate 中调用
UIViewController.swizzleViewWillAppear()

关联对象

import ObjectiveC

extension UIView {
    private struct AssociatedKeys {
        static var customId = "customId"
    }
    
    var customId: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.customId) as? String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.customId, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

二、RunLoop(运行循环)

1. 作用与原理
  • 作用:管理线程的事件和消息,处理定时器、触摸事件、网络回调等。主线程 RunLoop 默认启动,子线程需手动管理。
  • 原理:基于 CFRunLoop 的循环机制,通过 CFRunLoopMode 管理不同事件源(Sources/Timers/Observers),在休眠和唤醒间切换。
2. 使用场景
  • 定时器在子线程运行:避免主线程卡顿。
  • 保持后台线程存活:通过 RunLoop 防止线程退出。
  • 监听界面流畅性:观察 RunLoop 状态判断卡顿。

3. Objective-C 示例

子线程运行定时器

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"Timer fired in background thread");
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run]; // 保持 RunLoop 运行
});

添加 RunLoop 观察者

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
    kCFAllocatorDefault,
    kCFRunLoopAllActivities,
    YES,
    0,
    ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop Enter");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop Sleep");
                break;
            default:
                break;
        }
    }
);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
4. Swift 示例

子线程运行定时器

DispatchQueue.global().async {
    let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
        print("Timer fired in background thread")
    }
    RunLoop.current.add(timer, forMode: .default)
    RunLoop.current.run() // 防止线程退出
}

添加 RunLoop 观察者

let observer = CFRunLoopObserverCreateWithHandler(
    kCFAllocatorDefault,
    CFRunLoopActivity.allActivities.rawValue,
    true,
    0
) { observer, activity in
    if activity.contains(.entry) {
        print("RunLoop Enter")
    } else if activity.contains(.beforeWaiting) {
        print("RunLoop Sleep")
    }
}
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)

三、注意事项

  1. Swift 动态性限制:纯 Swift 类无法使用 Runtime,需继承 NSObject 或使用 @objc dynamic
  2. RunLoop 保活:子线程 RunLoop 需添加输入源(如 Timer),否则 run 方法会立即返回。
  3. 性能影响:频繁的 Runtime 操作或复杂 RunLoop 观察者可能导致性能问题。