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,可以实现许多强大的功能,但同时也要注意它们带来的复杂性和潜在问题。