Objective-C中的 self 和 super

195 阅读5分钟

Objective-C的开发中,经常使用selfsuper关键字,self是调用对象本身的方法,而super是调用父类的方法。那么本篇文章来研究一个有意思的问题。

一、提出问题

objc源码工程中新建Person类并继承自NSObject,空实现;在新建Teacher类并继承自Person,然后添加一个-printClass方法:

// Teacher.h
@interface Teacher : Person

- (void)printClass;

@end

// Teacher.m
@implementation Teacher

- (void)printClass {
    Class tCls = [self class];
    Class sCls = [super class];
    NSLog(@"class: %@, super: %@", tCls, sCls);
}

@end

main.mmain函数修改如下:

// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Teacher *teacher = [[Teacher alloc] init];
        NSLog(@"teacher: %@", teacher);
        [teacher printClass];
    }
    return 0;
}

运行程序,控制台输出如下:

2020-02-06 21:07:42.472001+0800 objc-debug[2013:81119] teacher: <Teacher: 0x100fc7450>
2020-02-06 21:07:42.472830+0800 objc-debug[2013:81119] class: Teacher, super: Teacher
Program ended with exit code: 0

可以看到,[self class][super class]输出的都是Teacher,可Teacher明明是继承自Person的,调用[super class]不是应该输出Person吗?

二、问题分析

2.1 源码分析

NSObject的实例方法-class的实现分析一下:

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

它返回的是对象的isa,也就是类,按照这个实现,入参obj似乎只能是teacher实例。验证一下这个推测,将Teacher-printClass方法修改一下,只研究[super class]

// Teacher.m
@implementation Teacher

- (void)printClass {
    Class sCls = [super class];
    NSLog(@"super: %@", sCls);
}

@end

在第5行处打上断点,运行程序,来到上述断点后,再在object_getClass()函数的第一行打上断点,然后继续运行:  我们看到,入参objTeacher类型实例,正是我们在main函数中创建的teacher(注意控制台输出是main函数的输出),也就是说,虽然是通过super关键字调用的-class方法,但毕竟-printClassTeacher类的实例方法,在调用object_getClass()入参依然是teacher实例,那么teacher->isa获取到就是Teacher

2.2 汇编分析

上面只是打了个断点从现象上分析了原因,但是原理是什么,并没有讲清楚。下面同汇编入手来探究一下其根本原因。在-printClass方法的第一行代码打上断点(上述代码块第5行),运行程序来到断点后,Xcode菜单->Debug->Debug Workflow->Always Show Disassembly,断点会来到汇编中:  调用[super class]实际也是发送消息,调用了objc_msgSendSuper2(),在objc源码中搜索后发现是由汇编实现的,但在objc-abi.h中有定义:

OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);

其第一个参数是objc_super结构体对象,第二个参数是SEL,此处SEL也就是-class,看一下objc_super的定义:

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 

    __unsafe_unretained _Nonnull Class super_class;
    /* super_class is the first class to search */
};

objc_super有两个成员,一个receiver,即消息接受者,一个super_class,即父类。另外注意最后一行注释,说super_class是首先进行消息查找的类,也就是说,消息查找时,不查找当前类,直接从父类开始查找。

2.3 模拟向父类发送消息

通过前面的输出结果,以及上面的分析,可以推测objc_super的成员receiver就是teacher实例,根据这个推测我们模拟一下[super class]的过程。
-printClass方法修改如下:

// Teacher.m
#import "Teacher.h"
#import "objc/message.h"

@implementation Teacher

- (void)printClass {
    
    struct objc_super mySuper = {
        self,
        [Person class]
    };
    
    objc_msgSendSuper(&mySuper, @selector(class));
}

@end

可能会有下图所示的报错:

按照下图步骤修改即可:

 然后Person实现如下:

// Person.m
#import "Person.h"
#import <objc/runtime.h>

@implementation Person

- (Class)class {
    NSLog(@"person self: %@", self);
    return object_getClass(self);
}

@end

运行程序,控制台输出(log实际会输出很多行,这是因为系统会自动多次调用-class方法):

...
2020-02-06 22:31:23.059597+0800 objc-debug[2625:124099] person self: <Teacher: 0x101127860>
2020-02-06 22:31:23.059694+0800 objc-debug[2625:124099] teacher: <Teacher: 0x101127860>
2020-02-06 22:31:23.059826+0800 objc-debug[2625:124099] person self: <Teacher: 0x101127860>
...

Person的实例方法中的self就是teacher实例,其实就是objc_super的成员receiver。为了验证这个说法,我们把receiver改一下:

- (void)printClass {
    
    Teacher *tc = [[Teacher alloc] init];
    NSLog(@"tc: %@", tc);
    struct objc_super mySuper = {
        tc,
        [Person class]
    };
    
    objc_msgSendSuper(&mySuper, @selector(class));
}

新创建一个Teacher实例tc,将它作为receiver穿进去。运行程序:

2020-02-06 22:41:26.432620+0800 objc-debug[2700:130559] person self: <Teacher: 0x101931650>
2020-02-06 22:41:26.432882+0800 objc-debug[2700:130559] teacher: <Teacher: 0x101931650>
2020-02-06 22:41:26.440264+0800 objc-debug[2700:130559] person self: <Teacher: 0x101e01b50>
2020-02-06 22:41:26.440359+0800 objc-debug[2700:130559] tc: <Teacher: 0x101e01b50>
2020-02-06 22:41:26.440516+0800 objc-debug[2700:130559] person self: <Teacher: 0x101e01b50>

我们是在输出teacher之后才调用的-printClass,可以看到在此之后Person输出的self就是tc

三、总结

在使用selfsuper关键字调用方法时,selfsuper并不是消息的接收者,而仅仅是告诉编译器,从什么地方开始查找方法,真正的消息接收者是当前对象本身。 比如在一个实例方法中执行[super xxx],实际会执行objc_msgSend(self, xxx, ...),第一个参数即消息接收者self,而不是super,因为super只是一个关键字,并不是一个可以作为消息接收者的对象。