深入 Objective-C Runtime:动态添加属性与方法的实战案例
一、动态添加属性
在 Objective-C 中,通过关联对象(Associated Objects)实现属性的动态添加。核心原理是将键值对与对象关联,突破类扩展的限制。
实现步骤:
- 定义静态键值(确保唯一性)
- 实现
getter和setter方法 - 使用
objc_setAssociatedObject和objc_getAssociatedObject
示例代码:
#import <objc/runtime.h>
// 动态添加字符串属性
@interface NSObject (DynamicProperty)
@property (nonatomic, copy) NSString *dynamicIdentifier;
@end
@implementation NSObject (DynamicProperty)
static char kDynamicIdentifierKey;
- (void)setDynamicIdentifier:(NSString *)dynamicIdentifier {
objc_setAssociatedObject(self,
&kDynamicIdentifierKey,
dynamicIdentifier,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)dynamicIdentifier {
return objc_getAssociatedObject(self, &kDynamicIdentifierKey);
}
@end
// 使用案例
NSObject *obj = [NSObject new];
obj.dynamicIdentifier = @"ID_123";
NSLog(@"%@", obj.dynamicIdentifier); // 输出:ID_123
关联策略说明:
| 策略类型 | 等效属性声明 |
|---|---|
OBJC_ASSOCIATION_ASSIGN |
@property (assign) |
OBJC_ASSOCIATION_RETAIN |
@property (strong) |
OBJC_ASSOCIATION_COPY |
@property (copy) |
二、动态添加方法
通过 class_addMethod 实现方法注入,需处理选择器(SEL)与函数指针(IMP)的映射关系。
实现步骤:
- 定义函数实现(需符合
id self, SEL _cmd签名) - 创建方法选择器
- 调用
class_addMethod注入方法 - 处理消息转发(可选)
示例代码:
// 动态添加无参数方法
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"动态方法执行成功!");
}
@interface MyClass : NSObject
@end
@implementation MyClass
// 处理未实现的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
class_addMethod([self class],
sel,
(IMP)dynamicMethodIMP,
"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
// 使用案例
MyClass *instance = [MyClass new];
[instance performSelector:@selector(dynamicMethod)]; // 输出:动态方法执行成功!
类型编码规则:
-
v@:表示返回 void (v),参数为 id (@) 和 SEL (:) -
i@:表示返回 int (i),参数同上 - 复杂类型需使用
@encode()宏,如@encode(CGPoint)返回"{CGPoint=dd}"
三、实战应用场景
-
分类扩展存储属性
解决分类无法添加存储属性的限制(如为UIImageView添加缓存标识) -
热修复与 AOP
运行时替换方法实现(Method Swizzling)实现:Method original = class_getInstanceMethod([self class], @selector(viewDidLoad)); Method swizzled = class_getInstanceMethod([self class], @selector(swizzled_viewDidLoad)); method_exchangeImplementations(original, swizzled); -
动态响应未实现方法
在forwardInvocation:中动态创建方法,实现灵活的消息转发 -
JSON 模型转换
运行时遍历属性列表实现字典转模型:unsigned int count; objc_property_t *properties = class_copyPropertyList([self class], &count); for (int i = 0; i < count; i++) { const char *name = property_getName(properties[i]); NSString *key = [NSString stringWithUTF8String:name]; }
四、注意事项
-
键值冲突
使用static char地址作为键值,避免字符串重复 -
内存管理
关联对象根据策略自动管理内存,但需注意循环引用:// 错误示例(导致循环引用) objc_setAssociatedObject(objA, &key, objB, OBJC_ASSOCIATION_RETAIN); objc_setAssociatedObject(objB, &key, objA, OBJC_ASSOCIATION_RETAIN); -
性能影响
频繁的动态方法解析会降低消息发送效率,建议在+load中预注册方法 -
类型安全
动态方法需手动确保参数/返回值类型匹配,错误类型编码会导致崩溃
通过灵活运用 Runtime,可在保持类型安全的前提下实现高度动态化编程,但需严格遵循内存管理规则和线程安全原则。