OC之Runtime

·  阅读 512

Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。

这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。

我们将介绍runtime的基本工作原理,以及如何利用它让我们的程序变得更加灵活。在本文中,我们先来介绍一下类与对象,这是面向对象的基础,我们看看在Runtime中,类是如何实现的。

Runtime数据结构

我们知道,在OC中,基本上所有的类的基类,都是NSObject。因此要深入了解OC中的类的结构,就要从NSObject这个类说起。 在XCode中,我们可以通过查看定义来了解NSObject的实现

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
复制代码

NSObject仅有一个实例变量Class isa:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
复制代码

Class实质上是指向objc_class的指针。而objc_class的定义又是如何呢,在XCode中,我们继续查看定义:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
复制代码

网上可以搜到很多博客,都是按照上面objc_class的定义来继续讲解的。但是也有很多的博客和面试题中出现了class_data_bits_t、class_rw_t、class_ro_t等数据结构,他们又是从哪来的呢?
仔细一看会发现OBJC2_UNAVAILABLE这个提示,在OC 2.0中,这种关于objc_class的定义已经废弃掉了啊,在runtime源码的objc-runtime-new.h中,可以看到objc_class在OC 2.0中的定义。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
	// 省略其他方法
	。。。
}
复制代码

可以看到,objc_class继承自objc_object,在objc_class中,有三个数据成员:

  • Class superclass :同样是Class类型,表明当前类的父类。
  • cache_t cache :cache用于优化方法调用,实际上是装满了bucket_t数据结构的Hash表
  • class_data_bits_t bits:这是Class的核心,实际上是对class_rw_t的数据结构的封装。class_rw_t中包含了class_ro_t 类相关的只读信息、protocols 类分类中的协议、properties 类分类中的属性、methods 类分类中的方法。其中class_ro_t包含了name类名、methodList类的方法列表 --method_t、ivars声明的类的成员变量、类的属性、类的协议

objc_object

objc_object结构体定义如下:

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

	// 省略其余方法
	...
}
复制代码

主要包含:

  • isa_t:共用体
  • 关于isa操作相关的一些方法:通过objc_object结构体,来获取isa所指向的类对象,或者通过类对象的isa指针获取它的元类对象一些遍历的放大
  • 弱引用相关:标记一个对象是否曾经有过弱引用指针
  • 关联对象相关方法:我们为对象设置了关联属性,关于关联属性的一些相关方法也体现在objc_object结构体中
  • 内存管理相关的方法实现:MRC下经常用到的runtain,release等方法实现 ,以上均封装在objc_object结构体中

cache_t

  • 是用于快速查找方法执行函数的一个结构(当我们调用一个方法时,如果有缓存,我们就不需要去方法列表中遍历了,可以提高方法调用速度)
  • 是可增量扩展的哈希表结构(当结构存储量增大的过程中, cache_t会增量扩大它的内存空间来支持更多的缓存,用哈希表实现这个数据结构,是为了提高查找效率)
  • cache_t数据结构是计算机局部性原理的最佳应用(计算机局部性原理:在一般调用方法时,有几个方法调用频次较高,把他们放到方法缓存中,下次的命中率就会更高)

其数据定义如下:

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
	// 省略其余方法
	。。。   
}

typedef uintptr_t cache_key_t;

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};
复制代码

cache的核心是有一个类型为bucket_t的指针,它指向了一个以_key和IMP对应的缓存节点。key对应OC中的selector,在调用方法时是个选择器SEL。IMP理解为无类型的函数指针。
runtime方法调用的流程是,当要调用一个方法时,先不去Class的方法列表中查找,而是先去找cache_t cache 。当系统调用过一个方法后,会将其实现IMP和key存放到cache中,因为理论上一个方法调用过后,被再次调用的概率很大。

class_data_bits_t

这是Class的核心,主要是对class_rw_t的封装,其定义如下:

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
 
	public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
    。。。
复制代码

class_rw_t

class_rw_t代表了类相关的读写信息,例如给类添加的分类的一些方法,属性以及协议等,同时它也对class_ro_t的封装,我们可以随时创建分类,为类增加属性或者方法
rw是readWrite的简写
ro是readOnly的意思,创建类时,添加的成员变量或方法列表在之后就没办法修改了
class_ro_t代表了类相关的只读信息
其定义如下:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;         // 类不可修改的原始核心

    // 下面三个array,method,property, protocol,可以被runtime 扩展,如Category
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    // 和继承相关的东西
    Class firstSubclass;
    Class nextSiblingClass;

    // Class对应的 符号名称
    char *demangledName;
	
	// 以下方法省略
	...
}
复制代码

从源码中我们可以看出,class_rw_t包含:
class_ro_t
protocols类分类中的协议
properties类分类中的属性
methods类分类中的方法

class_ro_t

在class_ro_t 中包含了类的名称,以及method_list_t, protocol_list_t, ivar_list_t, property_list_t 这些类的基本信息。 在class_ro_t 的信息是不可修改和扩展的,其定义如下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    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;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
复制代码

通过源码我们可以看出,其包含:
1.name:类名
2.ivars:声明的类的成员变量
3.类的属性
4.类的协议
5.类的方法列表

method_t

method_t是对函数四要素(名称、返回值、参数、函数体)的封装,函数四要素决定了函数的唯一性
method_t是个结构体,主要有三个数据类型:
name 函数名称
types 函数返回值和参数的集合
imp 无类型的函数指针,对应着函数体

types是如何表示返回值和参数的,更多参考Type Encodings

id

我们可以用id表示任意类型的类实例变量。在runtime中,id是这样定义的:

/// A pointer to an instance of a class.
typedef struct objc_object *id;
复制代码

其实是一个objc_object *,因为objc_object的isa存在,所有runtime是可以知道id类型对应的真正类型的。

类对象和元类对象

在上一章节我们介绍了runtime的数据结构,现在问大家一个问题,c语言是面向过程的语言,那么为什么objective-c就可以是面向对象的语言?
通过上述数据结构我们了解到,Class实际就是objc_class * ,而objc_class继承自objc_object。objc_object结构体中,有一个isa指针,指向他的类别class。objc_class中,superclass指向其父类别
由此豁然开朗,我们看到在Objective-C的设计哲学中,一切都是对象。Class在设计中本身也是一个对象。而这个Class对象的对应的类,我们叫它 Meta Class。即Class结构体中的 isa 指向的就是它的 Meta Class。

  • 类对象是存储实例方法列表等信息的结构,实例对象可以通过isa指针找到自己的类对象,来访问实例方法
  • 元类对象存储类方法列表等信息的内容, 类对象可以通过isa找到自己的元类对象,来访问类方法

关于实例对象、类对象和元类对象的关系可以用下图表示:
红色箭头代表superClass指向,黑色虚线表示isa指针的指向

  • 当我们给定一个实例的时候,其类型是objc_object数据结构,内含isa成员变量指针,指向这个实例所对应的类对象,所以:实例和类对象的关系:实例(instance)通过isa指针可以找到自己的类对象(class)
  • 类对象(class)也有isa指针,指向元类对象(meta class )
  • 类对象的superclass指向其父类,根对象的父类指向nil
  • 元类对象也是objc_class类型,任何元类对象,包括根元类对象,其isa指针都指向根元类对象。
  • 元类对象的superclass指向其父类,根元类对象的superclass指向根类对象

类对象和元类对象的区别和联系

  • 类对象负责存储类的实例方法等列表,元类对象负责存储类的类方法等列表
  • 类对象和元类对象都是objc_class数据结构的,因为objc_class继承了objc_object,所以它们才有isa指针,进而才能实现我们刚才所描述的操作。

消息传递机制

在对象上调用方法是包括Objective-C的众多语言都具备的功能。但在Objective-C中,这个术语叫传递消息(pass a message)。消息有名称(name)或选择子(selector),可以接受参数,也可能有返回值。
在Objective-C底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全在runtime决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。

给对象发生消息可以这样写:

id returnValue = [someObject messageName:parameter];
复制代码

编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数objc_msgSend

id objc_msgSend(id self, SEL op, ...)
复制代码

objc_msgSend函数固定要接受两个参数,id类型的self和SEL类型的方法选择器名称,再后面才是消息传递的真正的方法参数。
如果给父类发送消息,例如[super messageName:parameter],会调用objc_msgSendSuper函数,其原理大致类似,这里就不做详细介绍。
上述消息会被编译器翻译成下面代码:

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
复制代码

objc_msgSend函数会一句接受者与选择子的类型来调用适当的方法,会在接受者所属的类中搜索其方法列表(list of methods),如果找不到就进入消息转发(message forwarding)操作,具体流程如下图所示:

  • 假如我们调用了实例对象someObject的messageName:方法,系统会根据当前实例的isa指针找到它的类对象。先在缓存中查找是否命中,缓存查找是通过哈希查找
  • 如果缓存中未命中,则会在类对象的方法列表中查找,已排序好的是二分查找,未排序好的是一般查找
  • 如果类对象的方法列表中未命中,则会逐级在父类方法列表查找是否命中,根据superClass指针逐级查找父类,在父类中也是先查找缓存,再查找父类
  • 如果最终未命中,最后进入消息转发操作
  • 调用类方法和实例方法的区别是,根元类的父类会指向根类对象

消息转发

上个章节,我们讲述了消息传递机制,但是当对象无法处理消息时,会发生什么?
在编译器间,向类发送了其无法解读的消息并不一定会报错,因为runtime允许可以在运行期继续向类中添加方法。当对象接受到无法解读的消息后,就会启动消息转发(message forwarding)。
消息转发分为两大阶段:

  • 第一阶段先征询接受者,所属的类,看其是否能动态添加方法,以出来这个未知的选择子。这叫动态方法解析
  • 第一阶段执行完后,接受者自己无法再以动态新增方法手段来响应包含该选择子的消息,此时进入完整的消息转发机制。又可细分为2步。首先,看看有没有对象能处理这条消息,如果有,系统则将消息转发给该对象。如果没有,系统会被所有消息有关信息封装到NSInvocation对象中,最后再给接受者一次处理机会

动态方法解析

对象再收到无法解读的消息后,首先将调用其所属类的下列方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel
复制代码

该方法的参数就是那个unknown selector,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理该unknown selector。参考下面代码实现:

+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
    if (/* aSelector满足某个条件  */) {
        /*
         调用class_addMethod为该类添加一个处理aSelector的方法,譬如:
         class_addMethod(self, aSelector, aImp, @"v@:@");
         */
        return YES;
    }
    return [super resolveInstanceMethod:aSelector];
}
复制代码

假如未实现的方法不是实例方法而是类方法,系统将会调用“resolveClassMethod”方法。

备援接收者

动态方法解析失败后,当前接受者还有第二次机会处理未知的选择子,在这一步中,runtime系统会问他:能否将这条消息转发给其他接收者处理:

- (id)forwardingTargetForSelector:(SEL)aSelector
复制代码

方法参数代表unknown selector,若当前receiver能找到备援对象,则将其返回,若找不到,则返回nil。

通过此方案,我们可以通过『组合』(composition)来模拟出『多继承』(multiple inheritance)的某些特性。具体可参考分类Category & 扩展 Extension

完整的消息转发

如果转发已经到了这一步的话,那么唯一能够做的就是启用完整的消息转发机制了。首先创建NSInvocation对象,将未知消息相关的全部细节都封装于其中。此对象包含选择子、目标(target)以及参数。在触发NSInvocation对象时,消息派发系统将消息派给目标对象。

此步骤会调用forwardInvocation方法来转发消息,该方法定义于<objc/NSObject.h>中:

- (void)forwardInvocation:(NSInvocation *)anInvocation;
复制代码

这个方法可以实现得很简单:只要改变调用目标,是消息在新目标上得以调用即可。然而这样实现出来的方法与『备援接收者』方案所实现的方法等效,所以很少有人采用这种实现方式。

Method-Swizzling

什么是Method-Swizzling? 假如类中有两个方法
一个是selector1,对应的方法实现是IMP1
一个是selector2,对应的方法实现是IMP2
经过Method-Swizzling操作可以修改selector对应的实际的方法实现,当我们给这个对象发送selector1的时候,执行的是IMP2,发送selector2的时候,执行的是IMP1

例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的viewDidAppear中添加跟踪代码;但是这太过麻烦,需要在每个view controller中写重复的代码。创建一个子类可能是一种实现方式,但需要同时创建UIViewController, UITableViewController, UINavigationController及其它UIKit中view controller的子类,这同样会产生许多重复的代码。

这种情况下,我们就可以使用Method Swizzling,如在代码所示:

#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];         
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        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);
        }
    });
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}
@end
复制代码

Swizzling应该总是在+load中执行

在Objective-C中,运行时会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。+load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证–事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用。

Swizzling应该总是在dispatch_once中执行

与上面相同,因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。

相关面试题

介绍下runtime的内存模型

参考runtime数据结构

class_rw_t 和 class_ro_t的区别

  • 在class_ro_t 中包含了类的名称,以及method_list_t, protocol_list_t, ivar_list_t, property_list_t 这些类的基本信息。 在class_ro_t 的信息是不可修改和扩展的
  • class_rw_t代表了类相关的读写信息,它包含对class_ro_t的封装,同时还包含运行时给类添加的分类的一些方法,属性以及协议等

为什么要设计metaclass

  • 类对象、元类对象能够复用消息发送流程机制;
  • 单一职责原则

如果我们调用NSObject 一个类方法,没有对应的实现,但是有同名的实例方法实现,会不会发生崩溃?会不会产生实际的调用?

会产生实际调用,原因是NSObject作为根元类对象,其superClass指针指向了根类对象 所以在元类中查找不到类方法的时候,就会顺着superClass指针去实例方法列表中查找 若有同名方法,就会实现调用

代码输出1

@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self)
    {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end
复制代码

输出:

Son
Son
复制代码

原因: 当调用self class时,会转化成 objc_msgSend函数,相当于调用:

objc_msgSend(self, @selector(class));
复制代码

此时,会在Son类对象去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。 objc Runtime开源代码对- (Class)class方法的实现:

- (Class)class {
    return object_getClass(self);
}
复制代码

当调用super class时,会转化成 objc_msgSendSuper函数,相当于调用:

objc_msgSendSuper(objc_super, @selector(class时));
复制代码

此时第一步先构造 objc_super 结构体,结构体第一个成员就是 self 。第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”)) , 实际该函数输出结果为 Father。第二步是去 Father这个类里去找- (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用,此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son。

代码输出2

@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
        BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
        NSLog(@"%d %d %d %d", res1, res2, res3, res4);
    }
    return 0;
}
复制代码

运行输出:

1 0 0 0
复制代码

原因: isKindOfClass实现如下:

- (BOOL)isKindOf:aClass
{
    Class cls;
    for (cls = isa; cls; cls = cls->superclass) 
        if (cls == (Class)aClass)
            return YES;
    return NO;
}
复制代码

isMemberOfClass实现如下:

- (BOOL)isMemberOf:aClass
{
    return isa == (Class)aClass;
}
复制代码
  • 当 NSObject Class对象第一次进行比较时,得到它的isa为 NSObject的Meta Class, 这个时候 NSObject Meta Class 和 NSObject Class不相等。所以第二行返回false
  • 然后取NSObject 的Meta Class 的Super class,这个时候又变成了 NSObject Class, 所以第一行返回true
  • Sark Class 的isa指向的是 Sark的Meta Class,和Sark Class不相等,所以第四行返回flase
  • Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等
  • NSObject Meta Class的 super class 指向 NSObject Class,和 Sark Class 不相等
  • NSObject Class 的super class 指向 nil, 和 Sark Class不相等,所以第三行返回false

代码输出3

@interface Person : NSObject

@end

@implementation Person

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

@end

@interface Engineer : Person

- (void)test;

@end

@implementation Engineer

- (void)test {

if ([super respondsToSelector:@selector(test)]) {
[super performSelector:@selector(test)];
}
NSLog(@"Engineer--test");
}

@end
@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
Engineer *engineer = [[Engineer alloc] init];
[engineer test];
}
复制代码

答案:死循环 原因:super它的背后其实是调用的objc_msgSendSuper方法

 id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

 struct objc_super {
 id receiver;
 Class super_class; // the class to search
 }
复制代码

第一个参数是一个objc_super,objc_super的第一个参数是方法的调用者,第二个参数是查找方法的开始位置

当调用[super performSelector:@selector(test)];时做了2个事情

1、 构造objc_super

{
self, // receiver 注意这里是engineer
class_getSuperclass(objc_getClass("Engineer")) // 也就是父类`Person`
}
复制代码

2、 调用objc_msgSendSuper

objc_msgSendSuper(
{self,class_getSuperclass(objc_getClass("Engineer"))},
sel_registerName("performSelector:"),
复制代码

所以[super performSelector:@selector(test)];等价于objc_msgSend(objc_super->receiver, @selector(performSelector:))),注意这里从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。消息的接受者还是engineer,由于Engineer和Person都没有实现performSelector:方法,所以就是NSObject的默认实现

所以[super performSelector:@selector(test)]; 等价于[self performSelector:@selector(test)];

从而导致了死循环。

参考文章:
Objective-C Runtime Programming Guide
《Effective Objective-C 2.0》
runtime源码
刨根问底Objective-C Runtime

分类:
iOS