iOS-objc_object & objc_class

913 阅读11分钟

问题先行:

1、如下代码的打印结果是什么?

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

2、如下代码的打印结果是什么?

BOOL result1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL result2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL result3 = [[Son class] isKindOfClass:[Son class]];
BOOL result4 = [[Son class] isMemberOfClass:[Son class]];
NSLog(@"result1 == %d result2 == %d result3 == %d result4 == %d",result1,result2,result3,result4);
BOOL result5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL result6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL result7 = [[Son alloc] isKindOfClass:[Son class]];
BOOL result8 = [[Son alloc] isMemberOfClass:[Son class]];
NSLog(@"result5 == %d result6 == %d result7 == %d result8 == %d",result5,result6,result7,result8);

3、如下代码class1和class2的打印结果一样吗?

Class class1 = [Son class];
Class class2 = [Son class];
NSLog(@"class1 === %p",class1);
NSLog(@"class2 === %p",class2);

4、属性、实例变量、成员变量的区别?

5、如下打印两行打印存在什么关系,&a和&a[0]一样吗?

int a[4] = {1,2,3};
int *b = a;
NSLog(@"%p ==== %p ==== %p ==== %p",&a,&a[0],&a[1],&a[2]);
NSLog(@"%p ==== %p ==== %p",b,b+1,b+2);

资料准备:

1、objc源码下载opensource.apple.com/
2、API官方文档developer.apple.com/documentati…
3、WWDC关于runtime的优化developer.apple.com/videos/play…

初步探索:

通过clang命令,生成相关代码的C++文件,查看底层实现

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

实例如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        ASTest *test = [[ASTest alloc]init];
        [test test];
        return 0;
    }
}
/// 相关 main.app 部分代码
#ifndef _REWRITER_typedef_ASTest
#define _REWRITER_typedef_ASTest
typedef struct objc_object ASTest;
typedef struct {} _objc_exc_ASTest;
#endif
struct ASTest_IMPL {
  struct NSObject_IMPL NSObject_IVARS;
};
struct NSObject_IMPL {
  Class isa;
};
typedef struct objc_class *Class;
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

通过上面的探索,我们可以得出以下结论
1、对象的本质是个结构体
2、结构体内有一个objc_class类型的isa

objc_object、objc_class、NSObject、isa关系分析:

isa是什么?

iOS-isa

objc_object、objc_class、NSObject是什么?

objc_object源码如下

struct objc_object {
private:
    isa_t isa;
public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
 ......
}

objc_class源码如下

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() const {
        return bits.data();
    }
    ......
}

NSObject源码如下

/// NSObject.h
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// NSObject.mm
@implementation NSObject
+ (void)initialize {
}
+ (id)self {
    return (id)self;
}
- (id)self {
    return self;
}
+ (Class)class {
    return self;
}
- (Class)class {
    return object_getClass(self);
}
......
}

总结
1、结构体类型objc_class继承自objc_object,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
2NSObject中的isa在底层是由Class定义的,其中Class的底层编码来自objc_class类型,所以NSObject也拥有了isa属性
3NSObject是一个类,用它初始化一个实例对象objcobjc满足objc_object的特性(即有isa属性),主要是因为isa 是由 NSObject 从objc_class继承过来的,而objc_class继承自objc_objectobjc_objectisa属性,所以对象都有一个isaisa表示指向来自于当前的objc_object。因此会是如下情况对象 -> 类对象 -> 元类 -> 根元类 -> 自己
4Class本身其实也是一个对象,我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例
5NSObject的实例方法selfid类型,其中idobjc_object的别名,因此可以得出万物的本质皆来源于objc_object

objc_class结构体解析

结构体主要包含如下

  • isa:继承自objc_objectisa,占8字节
  • superclassobjc_class类型的一个结构体指针,占8字节
  • cache:方法缓存、获取占16字节
  • bits:可以指向class_rw_tclass_ro_t,可以通过首地址平移32字节获取
  • class_rw_t:运行时生成,包含class_ro_t。插入分类的方法、协议、属性,不能添加成员变量
  • class_rw_t -> class_ro_t:是编译期生成的,它存储了当前类在编译期就已经确定的属性方法以及协议,它里面是没有分类中定义的方法协议

isa(isa_t类型

iOS-isa

superclass(objc_class类型

typedef struct objc_class *Class;

可以看出是是一个objc_class类型的结构体指针。

cache(cache_t类型

iOS-cache_t

bits(class_data_bits_t类型

/// 官方注释
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
struct class_data_bits_t {
    friend objc_class;
    // Values are the FAST_ flags above.
    uintptr_t bits;
  ......
  class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
   ......
}
  • 相当于unsigned long bits; 占64位
  • bits实际上是一个地址(是一个指针,可以指向class_ro_t,也可以指向class_rw_t
  • 通过官方注释可以得出:class_data_bits_t相当于class_rw_t * 加上rr/alloc标志
  • data()这里将bitsFAST_DATA_MASK进行位运算,只取其中的3, 47位转换成class_rw_t*返回
  • 通过前面的分析可知,想要获取bits的中的内容,只需通过类的首地址平移32字节即可.

class_rw_t(class_data_bits_t中的data方法)

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t version;
    uint16_t witness;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
  ......
}

1objc_class中的data()方法调用了class_data_bits_t结构体中的data()方法,返回class_rw_t *指针
2class_rw_t是在运行时生成的,它在realizeClass中生成,它包含了class_ro_t。它在_objc_init方法中关于dyld的回调的map_images中最终将分类的方法协议都插入到自己的方法列表协议列表中。它不包含成员变量列表,因为成员变量列表是在编译期就确定好的,它只保存在class_ro_t中。不过class_rw_t中包含了一个指向class_ro_t的指针。

LLDB探索属性、方法:

image.png x/4gx object打印当前类结构 image.png

bits是 (类的内存首地址 + isa、superclass、cache的内存长度) 0x100001240 + 32字节(0x20) = 0x100001260 打印bits的结构

image.png 根据class_rw_t *data() { return bits.data(); }打印bits.data()

image.png

从$21指针的打印结果中可以看出bits中存储的信息,其类型是class_rw_t,也是一个结构体类型。
通过查看class_rw_t定义的源码发现,结构体中有提供相应的方法去获取属性列表、方法列表等,如下所示

bits(class_rw_t)中的属性列表

image.png

由此我们知道class_rw_t中的property_list中只有属性,没有成员变量。 class_rw_t有个属性class_ro_t在控制台输出ro,打印class_ro_t的结构 image.png

ro(class_rw_t -> class_ro_t)中的成员变量列表

image.png

1、通过{}定义的成员变量,会存储在类的bits属性中,通过bits -> data() ->ro() -> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量
2、通过@property定义的属性,也会存储在bits属性中,通过bits -> data() -> properties() -> list获取属性列表,其中只包含属性
3class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_tclass_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容\

bits(class_rw_t)中的方法列表
类中的方法列表除了包括实例方法,还包括属性的set方法 和 get方法,还有系统在底层添加了一个c++的.cxx_destruct方法 image.png

上面发现类的类方法并没有在列表中,原因是实例方法存储在类对象中,方法的实现底层都是函数,因此类方法就不能再存储到类对象中了(重名等问题)。
类方法是存储在元类中,可以理解成元类对象的实例方法

WWDC20关于runtime的优化-数据结构的变化:

数据结构的变化

image.png 这个类对象包含了最常用的信息:指向元类、父类、以及方法的缓存。它还有一个指针指向更多的额外信息class_ro_t,其中ro表示read only这部分信息是只读的,其中包含了类名、方法、协议、实例变量和属性等信息。

clean memory 是指加载后不会发生更改的内存。例如:class_ro_t就是clean memory
dirty memory 是指加载后会发生更改的内存。例如:class_rw_t就是dirty memory

一旦类被使用,运行时会分配额外的空间来存储这部分数据,即class_rw_t,其中rw表示read write。这个结构体中,我们只存储运行时产生的数据。

image.png

  • First SubclassNext Sibling Class指针让运行时可以遍历当前使用的所有类。
  • MethodsPropertiesProtocols这部分也是可以在运行时进行修改的。在实践中发现,其实只有大约10%类的方法会发生变化,所以这部分内存可以得到优化,滕出一些空间。
  • Demangled Name只会被Swift类所使用,而且除非有需要获取它们的Objective-C名称,甚至都不会用到。

image.png 这样就把class_rw_t,拆成了2部分。如果确实有需要,我们才会这部分class_rw_ext_t结构分配内存

问题先行解答:

1、打印结果为:

self.class === Son
super.class === Son

self是类的隐藏参数,指向当前调用方法的这个类的实例
super本质上是一个编译器标识符,和self指向的是同一个消息接受者。不同的是super调用方式的时候,会告诉编译器去调用父类的方法而不是本类的方法。
selfsuper发送消息方法分别是objc_msgSendobjc_msgSendSuper

OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    
/// 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 is the first class to search */
};

当在父类的方法列表中找到之后通过objc_super中的receiver去调用该方法的。 objc_super -> receiver就是此时的self,因此最终都为self.class,获取的都为类对象因此是一样的。

2、如下代码的打印结果是什么?
在进行分析之前,先明白几个知识点
class的实例方法,类方法

/// 类方法返回的就是在自己
+ (Class)class {
    return self;
}
/// self为对象,结果是类对象
/// 非编译器优化方法
- (Class)class {
    return object_getClass(self);
}
Class object_getClass(id obj) {
    if (obj) return obj->getIsa();
    else return Nil;
}

isKindOfClass的实例方法,类方法

/// 比较调用者的元类/根元类->父类->父类。。。和目标类
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
/// 比较调用者的类对象—>父类->父类。。。和目标类
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

编译优化的原因,isKindOfClass实际调用的是objc_opt_isKindOfClass函数

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

isMemberOfClass的实例方法,类方法

/// 比较调用者的元类和目标类
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
/// 比较调用者的类对象和目标类
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

即结论如下

result1 == 1 result2 == 0 result3 == 0 result4 == 0
result5 == 1 result6 == 1 result7 == 1 result8 == 1

/// (传入值)NSObject类 -> 根元类 -> NSObject类  和  NSObject类  相等  1
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];     
/// (传入值)NSObject类 -> 根元类  和 NSObject类 不相等 0
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];   
/// (传入值)Son类 -> 元类 -> 根元类 ->  NSObject类  和  Son类   不相等 0
BOOL re3 = [(id)[Son class] isKindOfClass:[Son class]];    
/// (传入值)Son类 -> 元类  和  Son类   不相等 0
BOOL re4 = [(id)[Son class] isMemberOfClass:[Son class]];  

/// (传入值)NSObject对象 ->  NSObject类 和  NSObject类  相等  1
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];      
/// (传入值)NSObject对象 ->  NSObject类 和  NSObject类  相等  1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];    
/// (传入值)Son对象 ->  Son类 和 Son类  相等  1
BOOL re7 = [(id)[Son alloc] isKindOfClass:[Son class]];     
/// (传入值)Son对象 ->  Son类 和 Son类  相等  1
BOOL re8 = [(id)[Son alloc] isMemberOfClass:[Son class]];   

类方法class返回自己
类方法isKindOfClass比较的是调用者的元类\根元类 -> 父类 -> … 和目标类
类方法isMemberOfClass比较的是调用者的元类\根元类和目标类

实例方法class返回类对象
实例方法isKindOfClass比较的是调用者的类对象 -> 父类 -> … 和目标类
实例方法isMemberOfClass比较的是调用者的类对象 和目标类

3、如下代码class1class2的打印结果一样吗?
一样,因为类的信息在内存中永远只存在一份,所以类对象是个单例。

4、属性、实例变量、成员变量的区别?
属性(property):在OC中是通过@property开头定义,编译阶段会自动生成带下划线的成员变量和setter、getter方法
实例变量(即成员变量中的对象变量就是实例变量):以实例对象实例化来的,是一种特殊的成员变量。
成员变量除去基本数据类型、NSString等其他都是实例变量(即可以添加属性的成员变量),实例变量主要是判断是不是对象。NSString是常量类型,因为不能添加属性,如果定义在类中的{}中,是成员变量

5、如下打印两行打印存在什么关系,&a和&a[0]一样吗? 结果如下
&a和&a[0]一样
b和&a和&a[0]一样
b+1和&a[1]一样
b+2和&a[2]一样
以上其实和通过对象的地址可以获取到对象类信息是一样的,都是通过内存平移实现

0x16da536d0 ==== 0x16da536d0 ==== 0x16da536d4 ==== 0x16da536d8
0x16da536d0 ==== 0x16da536d4 ==== 0x16da536d8