深入解析Halfrost-Field项目中的Objective-C Runtime:isa与Class机制

深入解析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的三个交互层面

mermaid

二、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引入了元类的概念:

mermaid

关键理解

  • 每个实例对象的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, ...);

消息发送的完整流程:

mermaid

4.2 消息转发机制

当方法查找失败时,Runtime会启动消息转发机制:

  1. 动态方法解析:调用+resolveInstanceMethod:+resolveClassMethod:
  2. 备援接收者:调用-forwardingTargetForSelector:,可以返回其他对象来处理消息
  3. 完整消息转发:调用-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动态创建的子类会重写以下方法:

  1. class方法:返回原始类,隐藏KVO实现
  2. setter方法:在设置值前后调用willChangeValueForKey:didChangeValueForKey:
  3. dealloc方法:清理工作
  4. _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),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值