iOS Runtime与RunLoop的对比和使用
Runtime 机制
核心概念
- Objective-C 的动态特性:Objective-C 是一门动态语言,很多工作都是在运行时而非编译时决定的
- 消息传递机制:方法调用实际上是发送消息
objc_msgSend(receiver, selector, ...)
- 方法决议机制:动态方法解析、消息转发流程
重要数据结构
Class
:类对象,包含 isa 指针、superclass 指针、方法缓存等objc_object
:所有对象的基类Method
:方法结构体,包含 SEL 和 IMPIvar
:实例变量结构体Property
:属性结构体Protocol
:协议结构体
核心功能
- 方法交换 (Method Swizzling)
Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(xxx_viewDidLoad));
method_exchangeImplementations(originalMethod, swizzledMethod);
- 动态添加方法
class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP)dynamicMethodIMP, "v@:");
- 关联对象 (Associated Objects)
static char associatedKey;
objc_setAssociatedObject(object, &associatedKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
id value = objc_getAssociatedObject(object, &associatedKey);
- 消息转发机制
// 1. 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel;
// 2. 备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector;
// 3. 完整消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
使用案例
- 无侵入埋点统计
// 交换 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])}];
}
- 防止数组越界崩溃
+ (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 机制
核心概念
- 事件循环机制:保持线程持续运行并处理各种事件
- 运行模式 (Mode):包含 Source/Timer/Observer
NSDefaultRunLoopMode
:默认模式UITrackingRunLoopMode
:界面跟踪模式NSRunLoopCommonModes
:通用模式集合
核心组件
-
Source:
- Source0:非基于端口的,处理应用内部事件
- Source1:基于端口的,处理系统事件
-
Timer:基于时间的触发器
-
Observer:观察 RunLoop 状态变化
RunLoop 生命周期
- 通知即将进入 RunLoop
- 通知即将处理 Timer
- 通知即将处理 Source0
- 处理 Source0
- 如果有 Source1 准备就绪,跳转处理
- 通知即将进入休眠
- 通知即将被唤醒
- 处理唤醒时收到的消息
- 通知即将退出 RunLoop
使用案例
- 保持线程常驻
+ (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];}
}
- 性能优化 - 图片加载
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {// ...[self performSelector:@selector(loadImageForCell:)withObject:cell afterDelay:0 inModes:@[NSDefaultRunLoopMode]];// ...
}- (void)loadImageForCell:(UITableViewCell *)cell {// 实际图片加载逻辑
}
- 卡顿监测
- (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);
}
- NSTimer 在滚动时保持运行
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Runtime 与 RunLoop 的协同应用
- 异步主线程执行检测
- (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);}}
}
- 方法调用频率限制
- (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]];
}
注意事项
-
Runtime 使用注意事项:
- Method Swizzling 应该在 +load 方法中进行
- 注意线程安全问题
- 避免过度使用,影响代码可读性
-
RunLoop 使用注意事项:
- 不要随意停止主线程的 RunLoop
- 注意 RunLoop Mode 的选择
- 避免在 RunLoop 中执行耗时操作
-
性能考虑:
- Runtime 的反射操作比直接调用方法慢
- RunLoop 的 Observer 会增加运行开销
- 频繁的 Mode 切换会影响性能
通过合理使用 Runtime 和 RunLoop,可以实现许多强大的功能,但同时也要注意它们带来的复杂性和潜在问题。