Objective-C 的 Runtime 是一个强大的特性,它让语言具备了很多灵活的动态能力。通过 Runtime,开发者可以在运行时解析类、方法、变量,并进行动态的消息传递、方法交换等操作。以下将详细介绍 Runtime,包括具体的应用场景和底层实现原理。
什么是 Runtime
Runtime 是 Objective-C 运行时库(objc-runtime),它为 Objective-C 提供了许多动态功能,包括:
- 消息传递(Message Passing)
- 动态方法解析(Dynamic Method Resolution)
- 方法交换(Method Swizzling)
- 动态对象创建(Dynamic Object Creation)
- 类与协议的操作(Class and Protocol Manipulation)
开发中的应用场景
1. 消息传递(Message Passing)
Objective-C 的方法调用实际上是通过发送消息完成的,而不是直接调用方法。
// 正常方法调用
[object method];
// 等价的消息传递
objc_msgSend(object, @selector(method));
2. 动态方法解析(Dynamic Method Resolution)
在运行时动态添加方法。可以在方法调用时决定该方法的具体实现。
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method resolution");
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(someMethod)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
3. 方法交换(Method Swizzling)
在运行时交换两个方法的实现,常用于方法拦截和日志记录。
@implementation UIViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_viewDidLoad));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)swizzled_viewDidLoad {
[self swizzled_viewDidLoad];
// 追加自定义逻辑
NSLog(@"viewDidLoad Swizzled");
}
@end
4. 动态对象创建
可以在运行时动态创建对象,包括类、实例变量和方法。
Class newClass = objc_allocateClassPair([NSObject class], "MyDynamicClass", 0);
class_addIvar(newClass, "_dynamicIvar", sizeof(id), log2(sizeof(id)), @encode(id));
class_addMethod(newClass, @selector(dynamicMethod), (IMP)dynamicMethodIMP, "v@:");
objc_registerClassPair(newClass);
id instance = [[newClass alloc] init];
[instance performSelector:@selector(dynamicMethod)];
5. 获取类和协议信息
获取类的所有属性、方法、成员变量等,并在需要时使用。
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char* propertyName = property_getName(property);
NSLog(@"Property: %s", propertyName);
}
free(properties);
分析底层原理
1. 消息传递的底层实现
消息传递采用 objc_msgSend
函数,它接收消息,并按照如下步骤处理:
- 在 Cache 中查找方法实现:每个类都有一个缓存,用于快速查找方法实现。
- 在类的方法列表中查找实现:在缓存未命中时,在类的方法列表中查找。
- 在父类中查找:若当前类未找到,在父类中递归查找。
- 动态方法解析:如果前几步全部失败,则会触发动态方法解析。
- 消息转发:如果找不到并且未解决,执行消息转发,最终抛出异常。
void objc_msgSend(id self, SEL _cmd) {
// 伪代码示意
Method method = cache_lookup(self, _cmd);
if (!method) {
method = class_lookup(self->isa, _cmd);
}
if (!method) {
method = [self methodSignatureForSelector:_cmd];
}
if (!method) {
// method forwarding
[self forwardInvocation:...];
}
if (!method) {
// throw exception: unrecognized selector
[self doesNotRecognizeSelector:_cmd];
}
}
2. 动态方法解析
动态方法解析允许你在消息传递机制中动态地提供方法实现。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 当找不到某方法实现时,会调用此方法来尝试动态添加方法实现
}
3. 方法交换
方法交换使用 method_exchangeImplementations
函数:
void method_exchangeImplementations(Method m1, Method m2) {
// 交换两个方法的实现
IMP m1_imp = method_getImplementation(m1);
IMP m2_imp = method_getImplementation(m2);
method_setImplementation(m1, m2_imp);
method_setImplementation(m2, m1_imp);
}
4. 动态对象创建
动态对象创建通过 objc_allocateClassPair
和 objc_registerClassPair
函数实现:
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes);
void objc_registerClassPair(Class cls);
这些函数允许你在运行时创建新的类,并添加实例变量和方法。
5. 获取类和协议信息
获取类和协议信息使用了大量基于类型信息的函数,比如 class_copyPropertyList
,这些函数返回一个数组,包含类的所有成员和方法信息。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
Method *class_copyMethodList(Class cls, unsigned int *outCount);
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);
其他场景
除了之前提到的消息传递、动态方法解析、方法交换等,Objective-C 的 Runtime 还在很多实用场景中得到了应用。以下是一些更实用且常见的应用场景:
1. 动态替换并增强类方法
通过方法交换,可以在不改变原有代码的基础上,为现有的方法增加功能,比如日志记录、性能监测等:
@implementation UIViewController (Logging)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(logging_viewDidAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)logging_viewDidAppear:(BOOL)animated {
[self logging_viewDidAppear:animated]; // 由于 method swizzling,实际上调用的是原来的 viewDidAppear:
NSLog(@"View did appear: %@", NSStringFromClass([self class]));
}
@end
2. 自动解析字典到模型对象
通过 Runtime,可以自动将 JSON 字典解析成自定义的模型对象,从而减少重复的手动解析工作:
#import <objc/runtime.h>
@interface NSObject (Dictionary)
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues;
@end
@implementation NSObject (Dictionary)
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
NSString *key = [NSString stringWithUTF8String:propName];
id value = [keyedValues objectForKey:key];
if (value) {
[self setValue:value forKey:key];
}
}
free(properties);
}
@end
3. 键值观察(KVO)原理探索及自定义
利用 Runtime 可以深入理解和优化 KVO 实现,比如避免过度嵌套和性能瓶颈。
#import <objc/runtime.h>
@implementation NSObject (KVO)
- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
// 为类动态创建子类并重写 setter 方法
Class class = object_getClass(self);
NSString *className = NSStringFromClass(class);
NSString *subclassName = [NSString stringWithFormat:@"KVO_%@", className];
Class subclass = NSClassFromString(subclassName);
if (!subclass) {
subclass = objc_allocateClassPair(class, subclassName.UTF8String, 0);
objc_registerClassPair(subclass);
Method setter = class_getInstanceMethod(class, @selector(setValue:forKey:));
const char *types = method_getTypeEncoding(setter);
class_addMethod(subclass, @selector(setValue:forKey:), (IMP)new_setter, types);
}
object_setClass(self, subclass);
// 调用原始的 addObserver:forKeyPath:options:context:
[self my_addObserver:observer forKeyPath:keyPath options:options context:context];
}
void new_setter(id self, SEL _cmd, id value, NSString *key) {
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
objc_msgSendSuper(&superStruct, _cmd, value, key);
// 通知所有观察者
NSLog(@"KVO new value for key: %@", key);
}
// 替换系统的 addObserver:forKeyPath:options:context: 方法
+ (void)load {
Method originalMethod = class_getInstanceMethod([NSObject class], @selector(addObserver:forKeyPath:options:context:));
Method swizzledMethod = class_getInstanceMethod([NSObject class], @selector(my_addObserver:forKeyPath:options:context:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
@end
4. 通过关联对象为类添加属性
有时需要在类别(Category)中为已有类添加属性,这可以通过关联对象实现:
#import <objc/runtime.h>
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
@end
5. AOP(面向切面编程)
利用 Runtime 技术可以在不修改原有代码的情况下,对方法的调用进行拦截,并加入新的处理逻辑,这种技术在日志记录、性能监控、权限控制等场景中尤为有用。
#import <objc/runtime.h>
typedef void(^AspectBlock)(id<AspectInfo> aspectInfo);
@interface Aspects : NSObject
+ (void)hookSelector:(SEL)selector ofClass:(Class)klass withAspectBlock:(AspectBlock)block;
@end
@implementation Aspects
+ (void)hookSelector:(SEL)selector ofClass:(Class)klass withAspectBlock:(AspectBlock)block {
Method originalMethod = class_getInstanceMethod(klass, selector);
IMP originalIMP = method_getImplementation(originalMethod);
void (^swizzleBlock)(id) = ^(id obj) {
block((id<AspectInfo>)obj);
((void(*)(id, SEL))originalIMP)(obj, selector);
};
IMP swizzledIMP = imp_implementationWithBlock(swizzleBlock);
method_setImplementation(originalMethod, swizzledIMP);
}
@end
// 使用示例
[Aspects hookSelector:@selector(viewWillAppear:) ofClass:[UIViewController class] withAspectBlock:^(id<AspectInfo> aspectInfo) {
NSLog(@"View will appear: %@", aspectInfo);
}];
6. 自动追踪用户事件
在用户行为统计和分析中,利用 Runtime 可以自动追踪用户在应用中的各种操作,比如点击按钮、页面跳转等。
#import <objc/runtime.h>
@implementation UIButton (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(tracking_sendAction:to:forEvent:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)tracking_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
NSLog(@"Button clicked: %@", NSStringFromSelector(action));
[self tracking_sendAction:action to:target forEvent:event]; // 实际上调用的是原始的 sendAction:to:forEvent:
}
@end
原码窥探其底层实现
Objective-C 的 Runtime 是其动态特性的核心,通过深入理解 Runtime 的底层实现,可以更好地理解和利用其强大功能。以下是对于 Runtime 的一些更深入的底层研究,涵盖类与对象的结构、消息传递机制、方法缓存等方面。
一、对象与类的结构
1. NSObject 的结构
在 Objective-C 中,所有对象都继承自 NSObject
。NSObject
的核心结构包含 isa
指针,这是一个指向类对象的指针。
struct objc_object {
Class isa; // 指向对象所属类的虚拟表
};
struct objc_class {
Class superclass; // 指向父类的虚拟表
Cache cache; // 方法缓存
MethodList *methods; // 方法列表
// 其他信息
};
2. isa 指针
isa
指针既指向类对象,也指向元类(Meta-class)。元类本身是一个类对象,但它存储的是类方法而不是实例方法。
// 示例图示
(MyClass isa -> MyClass) // 实例对象指向类对象
(MyClass isa -> MetaClass) // 类对象指向元类对象
(MetaClass isa -> MetaClass) // 元类对象指向根元类(MetaClass)对象本身
3. 类与元类的关系
每个类对象都有一个元类对象,这个元类对象存储了类方法。
class MyClass : NSObject {
@classmethod
func classMethod() {}
}
@implementation MyClass
+ (void)initialize {
if (self == [MyClass class]) {
// 初始化类相关的静态数据
}
}
二、消息传递机制
1. objc_msgSend 函数
Objective-C 的方法调用本质上是消息传递,通过 objc_msgSend
函数来实现:
void objc_msgSend(id self, SEL _cmd, ...) {
// 伪代码表示
Class cls = self->isa;
IMP imp = lookupMethodInClass(cls, _cmd);
if (imp) {
return imp(self, _cmd, args...);
} else {
// 动态方法解析和消息转发
}
}
IMP lookupMethodInClass(Class cls, SEL sel) {
// 在缓存中查找方法
IMP imp = cache_lookup(cls, sel);
if (imp) return imp;
// 递归查找父类
while (cls) {
MethodList *methods = cls->methods;
for (int i = 0; i < methods->count; i++) {
if (methods->method_list[i].name == sel) {
return methods->method_list[i].imp;
}
}
cls = cls->superclass;
}
return NULL;
}
2. 方法缓存
为了提高方法调用的效率,Objective-C 使用方法缓存来快速定位方法的实现。在类对象结构中,缓存用于存储最近使用的方法和响应的实现指针。
struct Cache {
unsigned int mask; // 缓存的大小掩码
unsigned int occupied; // 已使用缓存数量
MethodCacheEntry *buckets;// 缓存桶
};
struct MethodCacheEntry {
SEL sel; // 方法选择器
IMP imp; // 方法实现指针
};
三、动态方法解析和消息转发
1. 动态方法解析
当一个类接收到一个未知的方法调用时,首先会通过动态方法解析机制来为其动态提供方法实现。
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method resolved.");
}
@end
2. 消息转发
如果动态方法解析也未能提供方法实现,则会进入消息转发阶段。消息转发有两步:
- 快速转发:重载
forwardingTargetForSelector:
方法,将消息转发给另一个对象。
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(someMethod)) {
return anotherObject; // 转发目标对象
}
return [super forwardingTargetForSelector:aSelector];
}
- 慢速转发:重载
methodSignatureForSelector:
和forwardInvocation:
方法,构建NSInvocation
对象并处理。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(someMethod)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
if ([anotherObject respondsToSelector:sel]) {
[anInvocation invokeWithTarget:anotherObject];
} else {
[super forwardInvocation:anInvocation];
}
}
四、Method Swizzling 的底层实现
方法交换是 Objective-C 中一个强大的技术,它允许你在运行时交换两个方法的实现,常用于日志记录和方法增强。
void method_exchangeImplementations(Method m1, Method m2) {
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
}
五、内存管理与引用计数
1. 引用计数与自动引用计数(ARC)
Objective-C 使用引用计数来管理内存,ARC 是一种编译器特性,自动为代码插入 retain
和 release
。
// 在 ARC 下,由编译器自动插入 retain 和 release
id obj = [[NSObject alloc] init]; // 增加引用计数
obj = nil; // 减少引用计数,释放内存
2. @autoreleasepool
在手动引用计数和 ARC 下,可以使用 @autoreleasepool
来管理临时对象的生命周期。
@autoreleasepool {
// 临时对象在这里创建
id tempObj = [[NSObject alloc] init];
// 对象出了作用域会自动释放
}
这些是关于 Objective-C Runtime 的一些更深入的底层研究,通过了解这些底层机制,可以更好地理解和利用 Runtime,提高代码的灵活性、性能和可维护性。
总结
Objective-C 的 Runtime 提供了丰富的动态特性,使得开发者能够在运行时动态地操作对象、方法和类等。这种能力不仅增加了语言的灵活性,也带来了许多强大的功能,如动态方法解析、方法交换和消息传递等。在实际开发中,合理使用 Runtime 可以极大地提升应用的扩展性和灵活性,但要小心使用,避免引起难以调试的问题。
标签:selector,self,iOS,class,108,void,Runtime,方法,method From: https://www.cnblogs.com/chglog/p/18307218