美文网首页iOS
OC底层探索18、[self class]&[super cla

OC底层探索18、[self class]&[super cla

作者: _zhang__ | 来源:发表于2020-10-28 22:53 被阅读0次

一、[self class] 和 [super class]区别

MyPerson 类中添加如下代码:

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

运行工程,输出结果:

MyPerson~0x1000035f8 - MyPerson~0x1000035f8

可看到[self class][super class]均是 MyPerson.下面对其进行原理分析。

1、clang 编译 .cpp ,

clang -rewrite-objc MyPerson.m -o MyPerson.cpp

编译后内容如下:

static instancetype _I_MyPerson_init(MyPerson * self, SEL _cmd) {
    self = ((MyPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)(
                                                                              (__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyPerson"))},
                                                                              sel_registerName("init")
                                                                              );
    if (self) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hv_q0n645rs10j0n5hv1x4ds3vc0000gn_T_MyPerson_123f05_mi_1,
                // [self class] --> objc_msgSend()
                ((Class (*)(id, SEL))(void *)objc_msgSend)(
                         (id)self, // 消息接收者
                         sel_registerName("class") // cmd
                 ),
                // [super class] -->objc_msgSendSuper()
                ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper(
                         // 参数1 - 消息接收者
                         (__rw_objc_super){
                                 (id)self, // object
                                 (id)class_getSuperclass(objc_getClass("MyPerson")) // superClass
                          }, 
                          // 参数2 - SEL cmd
                          sel_registerName("class")
                 )
              
       );
    }
    return self;
}

结构体 __rw_objc_super:

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

1、消息 - cmd

getClass() 其实就是是 getIsa(),代码如下,这里不再多做赘述。

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

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

2、消息接收者分析 - self

// 实现有汇编编写,源码在下面
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

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

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

[self class] : objc_msgSend()的隐藏参数self消息接受者,这里没什么可说的;
下面对[super class]本质进行具体分析。

2.1、super 本质

super是一个关键字,是寄存器下的一个指令,它的本质是什么呢?
--> 结构体 objc_super:

#ifndef OBJC_SUPER
#define 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. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
     // super_class 是第一个要搜索的类
    /* super_class is the first class to search */ 
};
#endif

结合上面编译后代码,可知,取的是super_class. 那么[super class]:

  1. 消息接收者receiver --> selfMyPerson
  2. 方法查找时,搜索的第一个类是 super_class

* 通过断点调试也可得知,self 均为MyPerson:

image.png
可以总结了吗?:NO!

[self class]的本质是objc_msgSend(). 我们都已清楚;
[super class]的本质是objc_msgSendSuper()吗?
不!它只是在编译时被编译成了objc_msgSendSuper(),运行时调用的是objc_msgSendSuper2().

2.2、验证运行时 objc_msgSendSuper2()

打开汇编,control + step into一步步调试,见下图,运行时调的方法是class_getSuperclass2()

image.png

3、objc_msgSend/objc_msgSendSuper/objc_msgSendSuper2 汇编源码

/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd,...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 *
 * objc_msgLookup ABI:
 * IMP returned in r11
 * Forwarding returned in Z flag
 * r10 reserved for our use but not used
 *
 ********************************************************************/
    
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0
    .globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
    .fill 256, 8, 0

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    GetIsaCheckNil NORMAL       // r10 = self->isa, or return zero
    CacheLookup NORMAL, CALL    // calls IMP on success

    GetIsaSupport NORMAL
    NilTestReturnZero NORMAL

// cache miss: go search the method lists
LCacheMiss:
    // isa still in r10
    jmp __objc_msgSend_uncached

    END_ENTRY _objc_msgSend

    
    ENTRY _objc_msgLookup

    GetIsaCheckNil NORMAL       // r10 = self->isa, or return zero IMP
    CacheLookup NORMAL, LOOKUP  // returns IMP on success

    GetIsaSupport NORMAL
    NilTestReturnIMP NORMAL

// cache miss: go search the method lists
LCacheMiss:
    // isa still in r10
    jmp __objc_msgLookup_uncached

    END_ENTRY _objc_msgLookup

    
    ENTRY _objc_msgSend_fixup
    int3
    END_ENTRY _objc_msgSend_fixup

    
    STATIC_ENTRY _objc_msgSend_fixedup
    // Load _cmd from the message_ref
    movq    8(%a2), %a2
    jmp _objc_msgSend
    END_ENTRY _objc_msgSend_fixedup



/********************************************************************
 *
 * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...);
 *
 * struct objc_super {
 *      id  receiver;
 *      Class   class;
 * };
 ********************************************************************/
    
    ENTRY _objc_msgSendSuper
    UNWIND _objc_msgSendSuper, NoFrame
    
// search the cache (objc_super in %a1)
    movq    class(%a1), %r10    // class = objc_super->class
    movq    receiver(%a1), %a1  // load real receiver
    CacheLookup NORMAL, CALL    // calls IMP on success

// cache miss: go search the method lists
LCacheMiss:
    // class still in r10
    jmp __objc_msgSend_uncached
    
    END_ENTRY _objc_msgSendSuper



/********************************************************************
 * id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
 *
 * struct objc_super {
 *     id receiver;
 *     Class cls;   // SUBCLASS of the class to search
 * }
 ********************************************************************/
    
    ENTRY _objc_msgSendSuper2
    
    ldr r9, [r0, #CLASS]    // class = struct super->class
    ldr r9, [r9, #SUPERCLASS]   // class = class->superclass
    CacheLookup NORMAL, _objc_msgSendSuper2
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    ldr r0, [r0, #RECEIVER] // load real receiver
    bx  r12         // call imp

    CacheLookup2 NORMAL, _objc_msgSendSuper2
    // cache miss
    ldr r9, [r0, #CLASS]    // class = struct super->class
    ldr r9, [r9, #SUPERCLASS]   // class = class->superclass
    ldr r0, [r0, #RECEIVER] // load real receiver
    b   __objc_msgSend_uncached
    
    END_ENTRY _objc_msgSendSuper2

*总结:

  • [self class]的本质是 objc_msgSend(self, obj->getIsa());
  • [super class]的本质是class_getSuperclass2(),消息接受者是self,它的方法查找会从super_class开始,省一步self类自己的查找,会更快速。

二、内存平移问题

1、首先,准备如下代码,运行工程:

MyPerson 文件代码:

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

NS_ASSUME_NONNULL_BEGIN

@interface MyPerson : NSObject

@property (nonatomic, assign) int y_age;
@property (nonatomic, copy) NSString *y_name;
- (void)doSomething;

@end

NS_ASSUME_NONNULL_END

// .m
#import "MyPerson.h"

@implementation MyPerson

- (void)doSomething { // self 消息接受者 - MyPerson: 0x7ffee8a7c0e8
    // person -> y_name  -> 8字节
    NSLog(@"函数 %s - %@",__func__,self.y_name);
}

@end

ViewController.m代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    // ViewController 当前的类
    // self cmd (id)class_getSuperclass(objc_getClass("LGTeacher")) self cls kc person
    
    Class cls = [MyPerson class];
    void  *personAddr = &cls;  //
    MyPerson *person = [MyPerson alloc];
    NSLog(@"%p - %p",&person,personAddr);
    // 0x7ffeeb154178 - 0x7ffeeb154188

    [(__bridge id)personAddr doSomething]; 
    // 函数 -[MyPerson doSomething] - ViewController
    
    [person doSomething]; // self.y_name = nil - (null)
    // 函数 -[MyPerson doSomething] - (null) 

通过上面执行结果,person对象的结果自不必提,但是personAddr可以正常执行doSomeThing且在y_name并未赋值的情况下有值ViewController,这是为何?
通过lldb调试,打印个变量地址如下:

(lldb) p &personAddr
(void **) $9 = 0x00007ffee9b51180
(lldb) p personAddr
(MyPerson *) $10 = 0x00007ffee9b51188
(lldb) p &person
(MyPerson **) $11 = 0x00007ffee9b51178
(lldb) p &cls
(Class *) $12 = 0x00007ffee9b51188

指针personAddr的地址即[MyPerson class]类的地址,person对象的地址小8字节。
通过 OC 底层探索04 中已知类的结构,OC 底层探索 03 中已知对象的本质是个结构体。如下图:

image.png

上图信息:person对象的isapersonAddr指针都指向MyPerson类执行方法时所有流程一致,so,personAddr也可正常执行方法;当查找对象y_name时,根据person对象的结构,内存平移8字节找到属性变量y_name
但是personAddr只是一个 8字节的指针变量,地址指向MyPerson类而已并无实质关联,personAddr指针变量是没有y_name 等属性内容的,指针personAddr平移 8 字节找到的自然不会是y_name --> 那么它拿到的是什么呢?

2、的信息情况

压栈情况大概图示(后面详细):

image.png

2.1、函数的隐藏参数压栈问题

示例代码:

// 定义一个函数
void my_func (id person, id mySel){
    NSLog(@"person == %p - mySel == %p",&person,&mySel);
}

 my_func(@(10),@(20));

// 调用结果输出:
// person == 0x7ffeecba3138 - mySel == 0x7ffeecba3130

已知是个先进后出 地址连续且由高到低的结构。
由上面代码也可知,函数my_func()的2个参数压入栈里面,压栈顺序从前往后,地址连续。

2.2、结构体压栈

代码:

// 定义一个 struct
struct my_struct {
    int num1;
    int num2;
};

MyPerson *personTest = [MyPerson alloc];
struct my_struct struct1 = {111, 222};

lldb调试如下:

(lldb) p &personTest // personTest变量 的地址
(MyPerson **) $0 = 0x00007ffee3196188 // 地址 -8
(lldb) p *(int *)0x00007ffee3196180
(int) $1 = 111
(lldb) p *(int *)0x00007ffee3196184 // 一个 int 4字节
(int) $3 = 222
(lldb) 

由上:结构体成员压栈顺序由后往前 --> so 压站信息图中所示(super本质在文章上面已有探究),结构体中两个成员,(id)class_getSuperclass(objc_getClass("MyPerson"))先入站,self后入栈。

2.3、打印从 selfperson对象 之间的压栈信息

void *sp  = (void *)&self;
    void *end = (void *)&person;
    long count = (sp - end) / 0x8;
    
    for (long i = 0; i <= count; i++) {
        void *address = sp - 0x8 * i;
        if ( i == 1) {// _cmd 是 串类型
            NSLog(@"%p : %s",address, *(char **)address);
        }else{
            NSLog(@"%p : %@",address, *(void **)address);
        }
    }
/* 输出信息如下
      0x7ffee65911a8 : <ViewController: 0x7f8c6a702c30> // self
      0x7ffee65911a0 : viewDidLoad // cmd
      0x7ffee6591198 : ViewController // (id)class_getSuperclass(objc_getClass("MyPerson"))
      0x7ffee6591190 : <ViewController: 0x7f8c6a702c30> // self
      0x7ffee6591188 : MyPerson 
      0x7ffee6591180 : <MyPerson: 0x7ffee6591188> // 指针变量的地址 &personAddr
      0x7ffee5171178 : <MyPerson: 0x600000934980> // person 对象的地址
     */

由上,MyPerson地址平移8 恰好是<ViewController: 0x7f8c6a702c30>

总结:

  1. 函数形参压栈,顺序是从前往后;
  2. 结构体成员压栈,顺序从后向前;
    2.1 结构体中成员,int类型成原会直接分配占内存;--> 基本数据类型(值类型) 在栈上分配存储空间
    2.2 需开辟空间的 MyPerson类型变量的地址会压栈(person 变量所在的空间是栈),但变量内存分配在堆上(person 指向的空间在堆)。--> 需开辟空间的数据类型,栈中只存其地址,存储空间在堆上分配

以上。

相关文章

网友评论

    本文标题:OC底层探索18、[self class]&[super cla

    本文链接:https://www.haomeiwen.com/subject/jtqwmktx.html