深入解析Halfrost-Field项目中的Objective-C Runtime:isa与Class机制
前言:为什么需要深入理解Runtime?
在iOS开发中,你是否曾经遇到过这样的困惑:
- 为什么
[self class]和[super class]返回相同的结果? - KVO是如何实现属性监听的神奇效果的?
- Category为什么不能添加实例变量?
- 消息转发机制到底是如何工作的?
这些问题的答案都隐藏在Objective-C Runtime的核心机制中。Runtime是Objective-C语言的基石,理解isa指针和Class结构是掌握Runtime的关键所在。本文将基于Halfrost-Field项目中的深度分析,带你彻底弄懂Objective-C Runtime的底层实现。
一、Runtime基础概念
1.1 什么是Runtime?
Runtime(运行时)是一套底层的C语言API,是iOS系统的核心之一。开发者编写的Objective-C代码在编译阶段会被转换成运行时代码,在运行时确定对应的数据结构和调用具体哪个方法。
Objective-C与C语言的最大区别在于:C语言是静态语言,函数调用在编译期就决定了;而Objective-C是动态语言,方法调用在运行时才会确定。
1.2 Runtime的三个交互层面
二、NSObject的起源与isa指针
2.1 NSObject的基本结构
typedef struct objc_class *Class;
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
在Objective-C中,每个对象都包含一个isa指针,这个指针指向对象的类。这就是Objective-C对象模型的基石。
2.2 objc_class结构体的演变
Objective-C 1.0版本:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
#endif
} OBJC2_UNAVAILABLE;
Objective-C 2.0版本:
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
2.3 isa_t联合体的详细结构
在arm64架构下,isa_t的结构如下:
struct {
uintptr_t indexed : 1; // 是否开启isa指针优化
uintptr_t has_assoc : 1; // 对象是否含有关联引用
uintptr_t has_cxx_dtor : 1; // 对象是否有C++或Objc析构器
uintptr_t shiftcls : 33; // 类的指针(核心字段)
uintptr_t magic : 6; // 调试器判断对象是否初始化完成
uintptr_t weakly_referenced : 1; // 对象是否被弱引用指向
uintptr_t deallocating : 1; // 对象是否正在释放内存
uintptr_t has_sidetable_rc : 1; // 引用计数是否过大需要散列表存储
uintptr_t extra_rc : 19; // 对象的引用计数值减一后的结果
};
2.4 元类(Meta Class)的概念
为了统一实例方法和类方法的查找机制,Objective-C引入了元类的概念:
关键理解:
- 每个实例对象的isa指向对应的类
- 每个类的isa指向对应的元类
- 元类的isa指向根元类
- 根元类的isa指向自己
- 类的superclass指向父类,根类的superclass为nil
- 元类的superclass指向父元类,根元类的superclass指向根类
三、Class结构体的核心组件
3.1 cache_t:方法缓存优化
struct cache_t {
struct bucket_t *_buckets; // 散列表桶数组
mask_t _mask; // 分配用来缓存bucket的总数
mask_t _occupied; // 实际占用的缓存bucket个数
};
struct bucket_t {
private:
cache_key_t _key; // 方法选择器SEL作为key
IMP _imp; // 方法实现IMP
};
缓存的作用:根据80/20原则,一个类大约只有20%的方法经常被调用,占总调用次数的80%。使用Cache来缓存经常调用的方法,可以大幅提升方法查找效率。
3.2 class_data_bits_t:类的数据存储
struct class_data_bits_t {
uintptr_t bits;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro; // 只读数据(编译期决定)
method_array_t methods; // 方法列表
property_array_t properties;// 属性列表
protocol_array_t protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
关键区别:
class_ro_t(readonly):编译期决定的常量数据class_rw_t(readwrite):运行时可修改的数据
3.3 方法的结构定义
struct method_t {
SEL name; // 方法选择器名称
const char *types; // 类型编码
IMP imp; // 方法实现指针
// 排序比较器
struct SortBySELAddress :
public std::binary_function<const method_t&, const method_t&, bool> {
bool operator() (const method_t& lhs, const method_t& rhs) {
return lhs.name < rhs.name;
}
};
};
四、Runtime中的关键机制
4.1 消息发送机制
Objective-C方法调用[receiver message]会被编译器转换为:
id objc_msgSend(id self, SEL op, ...);
消息发送的完整流程:
4.2 消息转发机制
当方法查找失败时,Runtime会启动消息转发机制:
- 动态方法解析:调用
+resolveInstanceMethod:或+resolveClassMethod: - 备援接收者:调用
-forwardingTargetForSelector:,可以返回其他对象来处理消息 - 完整消息转发:调用
-methodSignatureForSelector:和-forwardInvocation:,可以完全自定义转发逻辑
4.3 方法交换(Method Swizzling)
Method Swizzling是Runtime的强大功能之一,允许在运行时交换方法的实现:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
注意事项:
- Swizzling应该在
+load方法中执行 - 必须使用
dispatch_once保证只执行一次 - 需要先尝试
class_addMethod,避免破坏父类的方法 - 在交换的方法中不要调用原始方法名,会导致循环调用
4.4 关联对象(Associated Objects)
Category默认不能添加实例变量,但可以通过关联对象实现类似功能:
// NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
// NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
@end
关联策略枚举:
| 关联策略 | 等效的@property属性 | 描述 |
|---|---|---|
| OBJC_ASSOCIATION_ASSIGN | @property (assign) 或 @property (unsafe_unretained) | 弱引用关联对象 |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 强引用关联对象,非原子性 |
| OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 复制关联对象,非原子性 |
| OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 强引用关联对象,原子性 |
| OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 复制关联对象,原子性 |
五、实际应用场景
5.1 KVO的实现原理
KVO(Key-Value Observing)是基于isa-swizzling技术实现的:
// KVO前后isa指针的变化
Student *stu = [[Student alloc] init];
NSLog(@"Before KVO: %@", object_getClass(stu)); // 输出: Student
[stu addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"After KVO: %@", object_getClass(stu)); // 输出: NSKVONotifying_Student
KVO动态创建的子类会重写以下方法:
class方法:返回原始类,隐藏KVO实现- setter方法:在设置值前后调用
willChangeValueForKey:和didChangeValueForKey: dealloc方法:清理工作_isKVOA方法:标识这是KVO动态创建的类
5.2 实现AOP编程
基于NSProxy实现面向切面编程:
@interface AspectProxy : NSProxy
@property(strong) id proxyTarget;
@property(strong) id<Invoker> invoker;
@property(readonly) NSMutableArray *selectors;
@end
@implementation AspectProxy
- (void)forwardInvocation:(NSInvocation *)invocation {
// 调用前横切逻辑
if ([self.invoker respondsToSelector:@selector(preInvoke:withTarget:)]) {
[[self invoker] preInvoke:invocation withTarget:self.proxyTarget];
}
// 调用目标方法
[invocation invokeWithTarget:self.proxyTarget];
// 调用后横切逻辑
if ([self.invoker respondsToSelector:@selector(postInvoke:withTarget:)]) {
[[self invoker] postInvoke:invocation withTarget:self.proxyTarget];
}
}
@end
5.3 异常保护与安全编程
利用Method Swizzling实现容器类安全访问:
@implementation NSArray (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = objc_getClass("__NSArrayI");
SEL originalSelector = @selector(objectAtIndex:);
SEL swizzledSelector = @selector(swizzling_objectAtIndex:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (id)swizzling_objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
// 异常处理:返回nil而不是崩溃
NSLog(@"Index %lu out of bounds %lu", (unsigned long)index, (unsigned long)self.count);
return nil;
}
return [self swizzling_objectAtIndex:index];
}
@end
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



