Skip to content

Runtime

面试点

数据结构,类对象与元类对象,消息传递,方法缓存,消息转发, Method-Swizzling,动态添加方法,动态方法解析

数据结构

1. objc_object

objc_object 是 Objective-C 中所有对象的基础结构体。它包含一个 isa 指针,该指针指向对象的类。

struct objc_object {
    Class isa;
};

2. objc_class

objc_class 是描述类的结构体。它本质上也是一个 objc_object,因此包含 isa 指针。一个类的 isa 指针指向它的元类(metaclass)。

struct objc_class {
    Class isa;  // 指向元类
    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;  // 协议列表
};

3. isa 指针

isa 指针是每个 objc_objectobjc_class 都包含的一个指针。它是 runtime 系统中用于实现动态类型和消息传递机制的关键。

  • 对于实例对象,isa 指针指向它的类(objc_class)。
  • 对于类对象,isa 指针指向它的元类(metaclass)。
  • 元类的 isa 指针最终指向 NSObject 的元类,形成一个闭环。

isa 指针的作用包括:

  • 用于确定对象的类型。
  • 用于查找对象的方法实现。

isa_t 是一个在 Objective-C 运行时中用来表示 isa 指针的结构体。它包含了指向对象的类的指针以及一些标志位,用于存储对象的附加信息。在现代的 Objective-C 运行时中,Apple 引入了“优化的 isa”技术,这使得 isa 指针不仅仅是一个简单的指针,而是一个多用途的结构,包含多个字段。

指针类型的 isa 和非指针类型的 isa

  • 指针类型的 isa:传统的 isa 指针是一个简单的指针,直接指向对象的类。
  • 非指针类型的 isa:优化的 isa 是一个包含多个字段的位域(bitfield), 这些字段可以包含指向类的指针以及其他标志位和信息。

优化的 isa 位域

在优化的 isa 实现中,isa_t 包含多个字段,例如:

  • 类指针(class pointer):指向对象所属的类。
  • 指针标志位(pointer bit):指示 isa 是否为指针类型。
  • 弱引用计数(weak reference count):管理对象的弱引用计数。
  • 额外标志位(extra flags):存储附加信息,如对象是否已被标记为自由等。

4. cache_t

cache_t 是用于方法缓存的结构体。它在类的结构体中起到了加速方法查找的作用。当一个方法被调用时,runtime 系统首先会在这个缓存中查找,以减少方法查找的开销。

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

  • _buckets:缓存桶数组,存储方法的缓存条目。
  • _mask:用于计算索引的掩码。
  • _occupied:已使用的缓存条目的数量。

  • 用于快速查找方法执行函数
  • 是可增量扩展的哈西表结构
  • 是局部性原理的最佳应用

5. class_data_bits_t

class_data_bits_t 是一个位域,包含类的只读和读写数据的指针。在 Apple 的运行时实现中,这个类型用于高效存储和访问类的数据。

  • class_data_bits_t主要是对class_rw_t的封装
  • class_rw_t代表了类相关的读写信息、对class_ro_t的封装
  • class_ro_t代表了类相关的只读信息

6. class_rw_t

class_rw_t 是类的读写数据的结构体,包含类的动态信息,如方法列表、属性列表等。这些数据在运行时可以被修改。

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;
};

7. class_ro_t

class_ro_t 是类的只读数据的结构体,包含类的静态信息,如类名、实例大小、成员变量列表等。这些数据在运行时不可修改。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    const uint8_t * ivarLayout;
    const char *name;  // 类名
    const method_list_t *baseMethods;  // 基本方法列表
    const protocol_list_t *baseProtocols;  // 基本协议列表
    const ivar_list_t *ivars;  // 成员变量列表
    const uint8_t *weakIvarLayout;
    const property_list_t *properties;  // 属性列表
};

8. method_t

method_t 是一个描述方法的结构体,包含方法的选择器(selector)、方法实现(IMP)和方法类型编码(type encoding)。

struct method_t {
    SEL name;  // 方法选择器
    const char *types;  // 方法类型编码
    IMP imp;  // 方法实现
};

9. 结构和关系

这些结构体和类型在 Objective-C 的类和方法管理中起着关键作用。它们的关系和作用可以通过下面的示例代码来理解:

// 类对象结构示例
struct objc_class {
    Class isa;
    Class super_class;
    cache_t cache;  // 方法缓存
    class_data_bits_t bits;  // 类数据位域
};

// 类数据位域展开后的数据
struct class_data_t {
    class_ro_t *ro;  // 只读数据
    class_rw_t *rw;  // 读写数据
};

// 读写数据包含的方法列表
struct class_rw_t {
    method_array_t methods;  // 方法列表
    // 其他字段省略
};

// 方法列表包含的方法
struct method_list_t {
    uint32_t count;
    method_t first;
};

  • cache_t:用于方法缓存,加速方法查找。
  • class_data_bits_t:包含类的只读和读写数据指针的位域。
  • class_rw_t:类的读写数据,包含方法列表、属性列表等,可以在运行时修改。
  • class_ro_t:类的只读数据,包含类名、实例大小、成员变量列表等,运行时不可修改。
  • method_t:表示方法的结构体,包含方法选择器、类型编码和实现指针。

对象,类对象,元类对象

  • 类对象存储实例方法列表等信息
  • 元类对象存储类方法列表等信息

                    +----------------------+
                    |  Root Metaclass      |
                    +----------------------+
                    |  +classMethod        |
       isa  +-------------------------------+  isa
       +----> | isa指向自身                  |
       |    +-------------------------------+
       |
  +----v----------------+
  |  Metaclass          |
  +---------------------+
  |  +classMethod       |
  +---------------------+
       isa  +-------------------------------+  isa
       +----> | isa指向Root Metaclass         |
       |    +-------------------------------+
       |
  +----v----------------+
  |  Class              |
  +---------------------+
  |  -instanceMethod    |
  +---------------------+
       isa  +-------------------------------+  isa
       +----> | isa指向Metaclass              |
            +-------------------------------+
                 |
  +--------------v--------------+
  |  Instance Object            |
  +-----------------------------+
  |  isa指向Class               |
  +-----------------------------+

类对象和元类对象有什么区别和联系?

实例对象通过 isa 指针找到类对象,类对象存储着实例对象的方法列表等信息,以及类对象可以通过 isa 指针指向元类对象,从而可以访问到类方法的相关信息。 类对象和元类对象都是 objc_class 结构,他们都继承 objc_object, 所以都包含 isa 指针。

元类的 isa 指针都指向根元类对象,根元类对象的 isa 指向自身。根元类对象的 superclass 指针指向 根类对象。

这句话来自视频,保留意见,似乎是错误的。 如果调用类方法,在根元类中找不到的时候,他就会调用根类的同名实例方法。

面试题:

self classsuper class 的解释

self class

[self class] 调用的是当前实例的 class 方法。这意味着它会返回当前对象所属的类。

  1. 实例对象:当你在一个实例方法中调用 [self class] 时,它会返回当前实例对象的类,即实例对象的 isa 指针所指向的类。
  2. 类对象:如果你调用 [Phone class],这将返回 Phone 类对象。

super class`

[super class] 也是调用当前实例的 class 方法,但用 super 的语法提示编译器从父类中查找方法实现。不过,class 方法是继承自 NSObject 的,所以无论是通过 self 还是 super 调用,它们最终调用的是 NSObjectclass 方法。

深入理解类对象和元类

  1. 类对象[Phone class] 返回 Phone 类对象。
  2. 元类对象:每个类对象的 isa 指针指向其对应的元类对象。

class 方法的查找过程

  • [self class][super class] 最终都会调用 NSObjectclass 方法。
  • class 方法会返回调用者的类,即当前对象的 isa 指针所指向的类。

代码示例和执行流程

#import "Mobile.h"

@interface Phone : Mobile
@end

@implementation Phone

- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class])); // Line A
        NSLog(@"%@", NSStringFromClass([super class])); // Line B
    }
    return self;
}

@end
  • Line ANSStringFromClass([self class]) 调用时,[self class] 返回当前实例的类,即 Phone
  • Line BNSStringFromClass([super class]) 调用时,虽然语法使用 super,但依旧返回当前实例的类,即 Phone

实际执行过程

  1. 创建实例objective-c Phone *phone = [[Phone alloc] init];

  2. [self class] 调用

    • 调用 Phone 实例的 class 方法。
    • 通过 isa 指针查找到 Phone 类对象。
    • 返回 Phone 类对象。
  3. [super class] 调用

    • 调用 Phone 实例的 class 方法。
    • super 语法会查找父类的 class 方法,但返回值仍然是当前对象的类。
    • 结果与 [self class] 相同,返回 Phone 类对象。

总结

  • [self class] 返回的是当前实例的类,即 Phone
  • [super class] 实际上也是返回当前实例的类,即 Phone,因为 super 只是改变了方法查找路径,并没有改变实际调用者的 isa 指针。

不论使用 [self class] 还是 [super class],最终都返回当前实例的类名,因为 class 方法定义在 NSObject 中,而所有类最终都继承自 NSObjectclass 方法返回的就是调用者的 isa 指针指向的类。

这样一来,无论是通过 self 还是 super 调用 class 方法,都返回当前对象所属的类。

消息传递

void objc_msgSend(void /*id self, SEL op, ...*/)

[self class]  <---> objc_msgSend(self, @selector(class))
void objc_msgSendSuper(void/*struct objc_super *super, SEL op, SEL op ...*/)

struct objc_super {
    /// Specifies an instance of a class
    __unsafe_unretained id receiver;  ==> 当前对象 self
}

[super class] <--> objc_msgSendSuper(super, @selector(class))

方法缓存,缓存查找

1. 当前类中查找

  • 对于已排序好的列表,采用二分查找算法查找方法对应执行函数。
  • 对于没有排序的列表,采用一般遍历查找方法对应执行函数。

2. 父类逐级查找

消息转发

1. 实例转发流程

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface RuntimeObject : NSObject

- (void)test;

@end

NS_ASSUME_NONNULL_END

#import "RuntimeObject.h"

@implementation RuntimeObject

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        NSLog(@"resolveInstanceMethod:");
        return NO;

    } else {
        return  [super resolveInstanceMethod:sel];
    }
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector:");
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"methodSignatureForSelector:");
    if (aSelector == @selector(test)) {
        return  [NSMethodSignature signatureWithObjCTypes:"v@:"];
    } else {
        return  [super methodSignatureForSelector:aSelector];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation:");
}

@end


//resolveInstanceMethod:
//forwardingTargetForSelector:
//methodSignatureForSelector:
//forwardInvocation: 

Method-Swizzling

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface RuntimeObject : NSObject

- (void)test;

- (void)otherTest;

@end

NS_ASSUME_NONNULL_END


#import "RuntimeObject.h"
#import <objc/runtime.h>


@implementation RuntimeObject

+ (void)load {
    Method test = class_getInstanceMethod(self, @selector(test));
    Method exchangeTest = class_getInstanceMethod(self, @selector(otherTest));

    method_exchangeImplementations(test, exchangeTest);
}

- (void)test {
    NSLog(@"test");
}

- (void)otherTest {
    NSLog(@"otherTest");
    [self otherTest];
}

@end

// otherTest
// test

动态添加方法

在 Objective-C 中,动态添加方法是一种强大的功能,允许你在运行时向类中添加方法。这通常在以下几种情况下非常有用:

  1. 动态扩展类的功能。
  2. 在运行时处理未知选择器。
  3. 实现消息转发机制。

1. 动态添加方法的步骤

  1. 定义方法的实现:实现一个 C 函数,作为动态添加方法的实现。
  2. 动态添加方法:使用 class_addMethod 函数将该实现添加到类中。
  3. 处理未实现的方法:通过 resolveInstanceMethodresolveClassMethod 方法来处理未实现的方法。

2. 代码示例

以下是一个完整的示例,展示了如何动态添加一个实例方法和类方法。

定义类和方法实现

首先,定义一个类 MyClass 和一个动态添加的方法实现。

#import <objc/runtime.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@end

@implementation MyClass
@end

// 动态添加的方法实现
void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"Dynamic method -dynamicMethod called");
}
动态添加实例方法

在类的实现中,重写 +resolveInstanceMethod: 方法,以动态添加方法。

@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
动态添加类方法

类似地,重写 +resolveClassMethod: 方法,以动态添加类方法。

@implementation MyClass (DynamicClassMethod)

+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(dynamicClassMethod)) {
        Class metaClass = objc_getMetaClass(class_getName([self class]));
        class_addMethod(metaClass, sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

@end
使用动态添加的方法

最后,在主函数中调用这些方法。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *myObject = [[MyClass alloc] init];

        // 调用动态添加的实例方法
        if ([myObject respondsToSelector:@selector(dynamicMethod)]) {
            [myObject performSelector:@selector(dynamicMethod)];
        }

        // 调用动态添加的类方法
        if ([MyClass respondsToSelector:@selector(dynamicClassMethod)]) {
            [MyClass performSelector:@selector(dynamicClassMethod)];
        }
    }
    return 0;
}
代码解释
  1. 动态添加实例方法

    • +resolveInstanceMethod: 方法在实例方法查找失败时被调用。
    • 如果方法选择器是 @selector(dynamicMethod),则使用 class_addMethod 动态添加该方法。
    • class_addMethod 的参数包括类、选择器、方法实现和类型编码。
    • IMP 类型是指向方法实现的指针。
    • "v@:" 是类型编码,表示返回类型为 void,参数为 self_cmd
  2. 动态添加类方法

    • +resolveClassMethod: 方法在类方法查找失败时被调用。
    • 使用 objc_getMetaClass 获取元类,然后使用 class_addMethod 向元类添加方法。
    • 元类的类型编码与实例方法相同。
运行结果

当运行上述代码时,会看到如下输出:

Dynamic method -dynamicMethod called
Dynamic method -dynamicMethod called

这表明动态添加的实例方法和类方法都被成功调用。

3. 总结

  • 动态方法解析:通过重写 +resolveInstanceMethod:+resolveClassMethod: 方法,可以在运行时动态添加方法。
  • 方法添加:使用 class_addMethod 将方法添加到类或元类中。
  • 方法实现:动态添加的方法实现可以是一个 C 函数,指向 IMP 类型。

这种技术在许多高级场景中非常有用,例如为现有类添加功能、处理未知消息、实现消息转发等。

动态方法解析

@dynamic

  • 动态运行时语言将函数决议推迟到运行时。
  • 编译时语言在编译期进行函数决议。