引言

Objective - C 的 Runtime 是一个强大且灵活的运行时系统,它为开发者提供了许多在编译时无法实现的动态特性。在之前的文章中,我们已经深入探讨了 Runtime 的底层原理,包括 isa 指针、class 结构、objc_msgSend 机制等。本文将重点介绍 Runtime 在实际开发中的一些常见应用,并通过具体的例子详细说明。

一、核心Runtime API详解

1. 类与对象操作

1
2
3
4
5
6
7
8
9
10
11
// 获取类的父类
Class class_getSuperclass(Class cls);

// 获取类的实例方法
Method class_getInstanceMethod(Class cls, SEL name);

// 获取类的所有方法
Method *class_copyMethodList(Class cls, unsigned int *outCount);

// 创建类的实例
id class_createInstance(Class cls, size_t extraBytes);

2. 方法操作

1
2
3
4
5
6
7
8
// 交换两个方法的实现
void method_exchangeImplementations(Method m1, Method m2);

// 添加新方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

// 获取方法实现
IMP class_getMethodImplementation(Class cls, SEL name);

3. 关联对象

1
2
3
4
5
// 设置关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

// 获取关联对象
id objc_getAssociatedObject(id object, const void *key);

4. 属性与协议

1
2
3
4
5
// 获取属性列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);

// 获取协议列表
Protocol *class_copyProtocolList(Class cls, unsigned int *outCount);

二、15种典型应用场景

1. 方法交换(Method Swizzling)

应用场景:统一日志记录、埋点统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation UIViewController (Logging)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method original = class_getInstanceMethod(self, @selector(viewDidLoad));
Method swizzled = class_getInstanceMethod(self, @selector(swizzled_viewDidLoad));
method_exchangeImplementations(original, swizzled);
});
}

- (void)swizzled_viewDidLoad {
[self swizzled_viewDidLoad];
NSLog(@"ViewController %@ loaded", NSStringFromClass([self class]));
}
@end

2. 动态添加方法

应用场景:懒加载功能模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface DynamicLoader : NSObject
@end

@implementation DynamicLoader
void dynamicMethod(id self, SEL _cmd) {
NSLog(@"Dynamic method called");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(lazyMethod)) {
class_addMethod(self, sel, (IMP)dynamicMethod, "v@:");
return YES;
}
return NO;
}
@end

3. 关联对象扩展类

应用场景:为系统类添加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface UIImage (Watermark)
@property (nonatomic, strong) UIImage *watermarkedImage;
@end

@implementation UIImage (Watermark)
static char kWatermarkKey;

- (void)setWatermarkedImage:(UIImage *)image {
objc_setAssociatedObject(self, &kWatermarkKey, image, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIImage *)watermarkedImage {
return objc_getAssociatedObject(self, &kWatermarkKey);
}
@end

4. 字典转模型

应用场景:JSON数据解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
- (instancetype)initWithDictionary:(NSDictionary *)dict {
self = [super init];
unsigned int count;
objc_property_t *props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(props[i])];
if (dict[key]) {
[self setValue:dict[key] forKey:key];
}
}
free(props);
return self;
}
@end

5. AOP编程(面向切面编程)

应用场景:统一异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation NSObject (ExceptionHandler)
+ (void)load {
Method original = class_getInstanceMethod(self, @selector(forwardInvocation:));
Method swizzled = class_getInstanceMethod(self, @selector(swizzled_forwardInvocation:));
method_exchangeImplementations(original, swizzled);
}

- (void)swizzled_forwardInvocation:(NSInvocation *)invocation {
@try {
[self swizzled_forwardInvocation:invocation];
} @catch (NSException *exception) {
NSLog(@"Caught exception: %@", exception);
}
}
@end

6. KVO实现

应用场景:属性变更监听

1
2
3
4
5
6
7
8
9
10
11
@interface Observable : NSObject
@property (nonatomic, strong) NSString *value;
@end

@implementation Observable
- (void)setValue:(NSString *)value {
[self willChangeValueForKey:@"value"];
_value = value;
[self didChangeValueForKey:@"value"];
}
@end

7. 动态代理

应用场景:实现轻量级代理模式

1
2
3
4
5
6
7
8
9
@interface DynamicProxy : NSObject
@property (nonatomic, weak) id target;
@end

@implementation DynamicProxy
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end

8. 方法拦截

应用场景:实现方法调用监控

1
2
3
4
5
6
7
8
9
10
11
12
@implementation NSObject (MethodInterceptor)
+ (void)load {
Method original = class_getInstanceMethod(self, @selector(performSelector:));
Method swizzled = class_getInstanceMethod(self, @selector(intercepted_performSelector:));
method_exchangeImplementations(original, swizzled);
}

- (id)intercepted_performSelector:(SEL)aSelector {
NSLog(@"Intercepted selector: %@", NSStringFromSelector(aSelector));
return [self intercepted_performSelector:aSelector];
}
@end

9. 实现单例

应用场景:线程安全的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface Singleton : NSObject
+ (instancetype)sharedInstance;
@end

@implementation Singleton
static id _instance;

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}

+ (instancetype)sharedInstance {
return [[self alloc] init];
}
@end

10. 热修复

应用场景:紧急修复线上bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface BuggyClass : NSObject
- (void)buggyMethod;
@end

@implementation BuggyClass
void fixedMethod(id self, SEL _cmd) {
NSLog(@"Fixed implementation");
}

+ (void)load {
Method buggy = class_getInstanceMethod(self, @selector(buggyMethod));
class_replaceMethod(self, buggy, (IMP)fixedMethod, "v@:");
}
@end

11. 统计方法调用次数

应用场景:性能分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation NSObject (MethodCounter)
static char kInvocationCountKey;

- (void)incrementInvocationCount {
NSNumber *count = objc_getAssociatedObject(self, &kInvocationCountKey);
objc_setAssociatedObject(self, &kInvocationCountKey, @(count.integerValue + 1), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (void)load {
Method original = class_getInstanceMethod(self, @selector(performSelector:));
Method swizzled = class_getInstanceMethod(self, @selector(counted_performSelector:));
method_exchangeImplementations(original, swizzled);
}

- (id)counted_performSelector:(SEL)aSelector {
[self incrementInvocationCount];
return [self counted_performSelector:aSelector];
}
@end

12. 实现协议

应用场景:动态遵循协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface DynamicAdopter : NSObject
@end

@implementation DynamicAdopter
+ (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return YES;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
if (Protocol_getMethodDescription(aProtocol, aSelector, YES, YES)) {
return self;
}
return [super forwardingTargetForSelector:aSelector];
}
@end

13. 实现链式调用

应用场景:构建DSL

1
2
3
4
5
6
7
8
9
10
11
12
@interface Chainable : NSObject
- (Chainable *(^)(NSString *))setName;
@end

@implementation Chainable
- (Chainable *(^)(NSString *))setName {
return ^(NSString *name) {
[self setValue:name forKey:@"name"];
return self;
};
}
@end

14. 实现动态容器

应用场景:动态存储键值对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface DynamicContainer : NSObject
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKey:(NSString *)key;
@end

@implementation DynamicContainer
- (void)setValue:(id)value forKey:(NSString *)key {
objc_setAssociatedObject(self, (__bridge const void *)(key), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)valueForKey:(NSString *)key {
return objc_getAssociatedObject(self, (__bridge const void *)(key));
}
@end

15. 实现自定义KVC

应用场景:扩展KVC功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface CustomKVC : NSObject
- (void)customSetValue:(id)value forKey:(NSString *)key;
@end

@implementation CustomKVC
- (void)customSetValue:(id)value forKey:(NSString *)key {
unsigned int count;
objc_property_t *props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
if ([NSStringFromCString(property_getName(props[i])) isEqualToString:key]) {
[self setValue:value forKey:key];
free(props);
return;
}
}
free(props);
[self doesNotRecognizeSelector:_cmd];
}
@end

三、注意事项

  1. 性能开销:频繁使用Runtime API可能影响性能
  2. 线程安全:方法交换等操作需加锁保护
  3. 版本兼容:不同iOS版本的Runtime实现可能有差异
  4. 内存管理:关联对象需合理选择内存策略
  5. 方法冲突:避免方法名冲突导致不可预期行为

四、总结

Objective - C Runtime通过开放底层API,赋予开发者强大的动态编程能力。掌握这些API的使用方法和应用场景,能够显著提升代码的灵活性和扩展性。建议在实际开发中结合具体需求,合理运用Runtime技术,并注意性能与维护性的平衡。

参考资料