iOS Runtime与RunLoop的对比和使用

Runtime 机制

核心概念

  1. Objective-C 的动态特性:Objective-C 是一门动态语言,很多工作都是在运行时而非编译时决定的
  2. 消息传递机制:方法调用实际上是发送消息 objc_msgSend(receiver, selector, ...)
  3. 方法决议机制:动态方法解析、消息转发流程

重要数据结构

  • Class:类对象,包含 isa 指针、superclass 指针、方法缓存等
  • objc_object:所有对象的基类
  • Method:方法结构体,包含 SEL 和 IMP
  • Ivar:实例变量结构体
  • Property:属性结构体
  • Protocol:协议结构体

核心功能

  1. 方法交换 (Method Swizzling)
objc 复制代码
Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(xxx_viewDidLoad));
method_exchangeImplementations(originalMethod, swizzledMethod);
  1. 动态添加方法
objc 复制代码
class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP)dynamicMethodIMP, "v@:");
  1. 关联对象 (Associated Objects)
objc 复制代码
static char associatedKey;
objc_setAssociatedObject(object, &associatedKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
id value = objc_getAssociatedObject(object, &associatedKey);
  1. 消息转发机制
objc 复制代码
// 1. 动态方法解析 
+ (BOOL)resolveInstanceMethod:(SEL)sel;
// 2. 备用接收者 
- (id)forwardingTargetForSelector:(SEL)aSelector;
// 3. 完整消息转发 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

使用案例

  1. 无侵入埋点统计
objc 复制代码
// 交换 viewDidAppear: 方法实现 
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ 
        [self swizzleMethod:@selector(viewDidAppear:) 
                 withMethod:@selector(swizzled_viewDidAppear:)];
    });
}
 
- (void)swizzled_viewDidAppear:(BOOL)animated {
    [self swizzled_viewDidAppear:animated];
    [Tracking logEvent:@"ViewAppear" params:@{@"class": NSStringFromClass([self class])}];
}
  1. 防止数组越界崩溃
objc 复制代码
+ (void)load {
    Method originalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
    Method swizzledMethod = class_getInstanceMethod([self class], @selector(safeObjectAtIndex:));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
 
- (id)safeObjectAtIndex:(NSUInteger)index {
    if (index < [self count]) {
        return [self safeObjectAtIndex:index];
    }
    NSLog(@"数组越界");
    return nil;
}

RunLoop 机制

核心概念

  1. 事件循环机制:保持线程持续运行并处理各种事件
  2. 运行模式 (Mode):包含 Source/Timer/Observer
    • NSDefaultRunLoopMode:默认模式
    • UITrackingRunLoopMode:界面跟踪模式
    • NSRunLoopCommonModes:通用模式集合

核心组件

  1. Source:

    • Source0:非基于端口的,处理应用内部事件
    • Source1:基于端口的,处理系统事件
  2. Timer:基于时间的触发器

  3. Observer:观察 RunLoop 状态变化

RunLoop 生命周期

  1. 通知即将进入 RunLoop
  2. 通知即将处理 Timer
  3. 通知即将处理 Source0
  4. 处理 Source0
  5. 如果有 Source1 准备就绪,跳转处理
  6. 通知即将进入休眠
  7. 通知即将被唤醒
  8. 处理唤醒时收到的消息
  9. 通知即将退出 RunLoop

使用案例

  1. 保持线程常驻
objc 复制代码
+ (NSThread *)networkThread {
    static NSThread *thread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ 
        thread = [[NSThread alloc] initWithTarget:self selector:@selector(networkThreadEntryPoint:) object:nil];
        [thread start];
    });
    return thread;
}
 
+ (void)networkThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"com.company.network"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
  1. 性能优化 - 图片加载
objc 复制代码
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // ...
    [self performSelector:@selector(loadImageForCell:)
               withObject:cell 
               afterDelay:0 
                  inModes:@[NSDefaultRunLoopMode]];
    // ...
}
 
- (void)loadImageForCell:(UITableViewCell *)cell {
    // 实际图片加载逻辑 
}
  1. 卡顿监测
objc 复制代码
- (void)startMonitor {
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
        kCFAllocatorDefault, 
        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(@"即将退出RunLoop");
                    break;
            }
        });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
}
  1. NSTimer 在滚动时保持运行
objc 复制代码
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 
                                        target:self 
                                      selector:@selector(timerAction:) 
                                      userInfo:nil 
                                       repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

Runtime 与 RunLoop 的协同应用

  1. 异步主线程执行检测
objc 复制代码
- (void)performOnMainThread:(dispatch_block_t)block {
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), 
               dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
        block();
    } else {
        // 检查是否在主线程RunLoop中 
        if ([NSThread isMainThread]) {
            // 使用RunLoop在当前迭代中执行 
            CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, block);
            CFRunLoopWakeUp(CFRunLoopGetMain());
        } else {
            dispatch_async(dispatch_get_main_queue(), block);
        }
    }
}
  1. 方法调用频率限制
objc 复制代码
- (void)throttledPerformSelector:(SEL)selector withObject:(id)object {
    // 取消之前的调用 
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:object];
    // 延迟执行,确保在RunLoop的下一个周期处理 
    [self performSelector:selector withObject:object afterDelay:0.1 inModes:@[NSDefaultRunLoopMode]];
}

注意事项

  1. Runtime 使用注意事项:

    • Method Swizzling 应该在 +load 方法中进行
    • 注意线程安全问题
    • 避免过度使用,影响代码可读性
  2. RunLoop 使用注意事项:

    • 不要随意停止主线程的 RunLoop
    • 注意 RunLoop Mode 的选择
    • 避免在 RunLoop 中执行耗时操作
  3. 性能考虑:

    • Runtime 的反射操作比直接调用方法慢
    • RunLoop 的 Observer 会增加运行开销
    • 频繁的 Mode 切换会影响性能

通过合理使用 Runtime 和 RunLoop,可以实现许多强大的功能,但同时也要注意它们带来的复杂性和潜在问题。

相关推荐
茶底世界之下6 小时前
阴天花摊不必变暖:用 Awaking 留住花色与湿街
ios·app·产品
Fatbobman(东坡肘子)20 小时前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift
茶底世界之下1 天前
斜光下的旧自行车:用 Awaking 留一版复古胶片色
ios·app·产品
2501_916008891 天前
iPhone 设备信息查看,用 KeyMob 看型号、序列号、电池健康和 UDID
android·ios·小程序·https·uni-app·iphone·webview
茶底世界之下2 天前
iPhone Duo 适配实战,Awaking 踩过的 8 个坑
ios·swiftui·swift
跨境数据猎手2 天前
HTTP 响应体编码与压缩:识别、解码
网络协议·http·ios
也非非也2 天前
iPhone 中国区 AI 落地:Apple 与阿里千问合作的技术观察
大数据·人工智能·ios·agent·iphone·智子
茶底世界之下2 天前
同一片泳池,清透还是奶油胶片?用 Awaking 做两版成片
ios
2501_916008892 天前
iOS App 图标、启动屏和多语言搭配 方法,上架前使用KXBuild一次做好 App 图标
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918412 天前
iOS 推送通知怎么实现,从 APNs p8 密钥到 iOS 真机收到第一条推送
vscode·ios·objective-c·个人开发·swift·敏捷流程