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)
三、注意事项
- Swift 动态性限制:纯 Swift 类无法使用 Runtime,需继承
NSObject
或使用@objc dynamic
。 - RunLoop 保活:子线程 RunLoop 需添加输入源(如 Timer),否则
run
方法会立即返回。 - 性能影响:频繁的 Runtime 操作或复杂 RunLoop 观察者可能导致性能问题。