OC底层之self、super

1,793 阅读4分钟

前言

本文涉及到的源码环境为objc4-818.2

我们在初始化方法中无数次用到了selfsuper,我们今天就对他们做一下研究。

  • 创建类Father继承自NSObject
  • 创建类Son继承自Father
@implementation Son

-(instancetype)init{
    if (self  = [super init]) {
        NSLog(@"1 -- %@",[self class]);
        NSLog(@"2 -- %@",[super class]);
    }
    return self;
}
@end

输出结果
1 -- Son
2 -- Son
  • 1打印出当前Son没有问题,那为什么2不输出Son的父类Father呢????
  • 为什么[super init]返回子类实例对象而不是父类实例对象???

self

在源码环境的NSObject.mm中查看class方法的实现

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

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

inline Class
objc_object::getIsa() 
{
    // 我们研究普通对象
    if (fastpath(!isTaggedPointer())) return ISA();
    ......
}

inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}

inline Class
isa_t::getDecodedClass(bool authenticated) {
    ...
    return getClass(authenticated);
}

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
    ...
    uintptr_t clsbits = bits;
    ...
    clsbits &= ISA_MASK;
    ...
    return (Class)clsbits;
}

我们前文学过nonpointer对象的isa指针包含指向类对象的指针,以上过程就是从实例对象的isa取出类指针的过程。

对于class方法我们可能比较好奇他访问的self为什么不是NSObject呢??

- (Class)class {
    // 这里访问的self是谁??
    return object_getClass(self);
}

我们知道函数的执行在OC底层是通过objc_msgSend进行消息发送完成的,objc_msgSend有两个默认参数self_cmd

  • self为消息接收者
  • _cmd为函数名
  • ...后面为参数
id objc_msgSend(id self, SEL _cmd, ...);

同样在函数中也有默认参数self_cmd,他们和objc_msgSend中的默认参数相对应,可简单理解为

- (Class)class(id self, SEL _cmd) {
    ......
}

因为我们在自定义类FatherSon中都没有实现class方法,所以消息查找流程会顺着继承链查找到NSObject中的class方法,但是self对象依然是Son的实例对象。

我们再举一个小例子,在父类Father中实现方法fatherMethod

@interface Father : NSObject
- (void)fatherMethod;
@end

@implementation Father
- (void)fatherMethod{
    NSLog(@"self == %@",self);
}
@end

在子类Son中没有实现任何方法,我们用子类的实例对象来调用fatherMethod方法

int main(int argc, char * argv[]) {

@autoreleasepool {
        Son *son = [[Son alloc] init];
        [son fatherMethod];
}
    return 0;
}

执行结果为

self == <Son: 0x28211c140>

无论方法在哪里实现,方法中访问的self都是消息接收者。

super

self是消息接收者,是消息发送函数objc_msgSend的默认参数之一。super就没那么简单了,他不是一个参数而是一个编译器的关键字。我们在Son.m中实现

@implementation Son
-(instancetype)init{
    if (self  = [super init]) {
        NSLog(@"%@",[super class]);
    }
    return self;
}
@end

我们通过clang和汇编两种方式来探索其底层原理

通过clang查看其底层实现

终端进入Son.m路径执行命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Son.m

可以看到Son.cpp文件中[super class]的底层实现为

struct __rw_objc_super { 
    struct objc_object *object; 
    struct objc_object *superClass; 
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};
......

((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"))

消息发送是通过objc_msgSendSuper而不是objc_msgSend,消息接收者变成了__rw_objc_super结构体指针

通过汇编查看

image.png 不同的是这里执行了objc_msgSendSuper2

源码探索

我们带着这些疑问来进入底层的世界

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

objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

这两个函数的参数是一样的,有两个默认参数

  • objc_super类型的super
  • SEL类型的op
  • 参数们

objc_super结构为(objc2环境)

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
    /* super_class is the first class to search */
};

备注写的清楚,方法查找时super_class是第一个查找的class

objc-msg-arm64.s文件中搜索objc_msgSendSuperobjc_msgSendSuper2的实现

ENTRY _objc_msgSendSuper

// 跳转到L_objc_msgSendSuper2_body中继续执行 
b L_objc_msgSendSuper2_body

END_ENTRY _objc_msgSendSuper


ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
 
// 和objc_msgSend不同的是p16不是指向class而是指向superclass
ldp p0, p16, [x0] 
ldr p16, [x16, #SUPERCLASS]  

L_objc_msgSendSuper2_body:
CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached

END_ENTRY _objc_msgSendSuper2

objc_msgSend不同的是p16不是指向class而是指向superclass,但是消息接收者receiver是一样的,验证一下。

在父类Father中定义和实现方法fatherMethodmethod

@interface Father : NSObject
- (void)fatherMethod;
- (void)method;
@end

@implementation Father
- (void)fatherMethod{
    NSLog(@"self == %@",self);
}

- (void)method{
    NSLog(@"father-method");
}
@end

在子类Son中重写fatherMethod方法并实现method方法

@interface Son : Father
- (void)method;
@end

@implementation Son
- (void)fatherMethod{
    [super fatherMethod];
    [super method];
}

- (void)method{
    NSLog(@"son-method");
}
@end

运行之

int main(int argc, char * argv[]) {

    @autoreleasepool {
        Son *son = [[Son alloc] init];
        [son fatherMethod];
    }
    return 0;
}

执行结果
self == <Son: 0x2806b8130>
father-method

[super fatherMethod]中消息接收者依然是子类Son的实例对象,所以fatherMethod中访问到的selfSon的实例对象

虽然子类Son也实现了method方法,但是[super method]是从父类Father开始查找的

小结

self调用方法[self xxx]super调用方法[super xxx]

  • 相同点为:消息接收者receiver是一样的
  • 不同点为:[super xxx]会从父类开始消息查找流程
-(instancetype)init{
    if (self  = [super init]) {
        ......
    }
    return self;
}
+ (id)init {
    return (id)self;
}

[super init]从父类开始查找init函数,但是消息接收者依然是子类实例对象,所以在init函数中访问并返回的self为子类实例对象。

参考文章

# OC中的self和super