iOS底层探索

iOS 探索类(上)--类的结构

2020-09-14  本文已影响0人  Sheisone

本篇文章我们会继续探索iOS底层非常重要的内容--类。

一、类的关系分析

我们创建两个类:LPPersonLPStudentLPPerson继承于NSObjectLPStudent继承于LPPerson

@interface LPPerson : NSObject
@property (nonatomic) NSString *name;
@end

@interface LPStudent : LPPerson

@end

再在Viewcontroller中分别创建一个personstudent对象:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LPPerson *person = [[LPPerson alloc]init];
    LPStudent *student = [[LPStudent alloc]init];

    NSLog(@"%@",person);
    
}

类的isa走向

并在LPStudent *student = [[LPStudent alloc]init];这一行打上断点,接下来运行代码:
lldb调试台中进行如下操作:

(lldb) x/4g person   ///获取person内存,并打印4条信息
0x600003cbc180: 0x00000001066cc590 0x0000000000000000
0x600003cbc190: 0x0000000000000000 0x0000000000000000
/// 0x00000001066cc590是person对象的isa指针,0x00000001066cc590 & 0x00007ffffffffff8ULL 可以得到isa的ISA_BITFIELD信息
(lldb) p/x 0x00000001066cc590 & 0x00007ffffffffff8ULL 
///0x00000001066cc590即为当前person对象的类
(unsigned long long) $1 = 0x00000001066cc590
///验证一下
(lldb) po 0x00000001066cc590
LPPerson
///获取LPPerson类内存信息
///方式1
(lldb) x/4g 0x00000001066cc590
0x1066cc590: 0x00000001066cc568 0x00007fff89d0fd00
0x1066cc5a0: 0x0000600002b85900 0x0001801c00000003
///方式2
(lldb) x/4g LPPerson.class
0x1066cc590: 0x00000001066cc568 0x00007fff89d0fd00
0x1066cc5a0: 0x0000600002b85900 0x0001801c00000003
///方式3
(lldb) x/4g object_getClass(person)
0x1066cc590: 0x00000001066cc568 0x00007fff89d0fd00
0x1066cc5a0: 0x0000600002b85900 0x0001801c00000003
////查看类的isa指针
(lldb) po 0x00000001066cc568
LPPerson
///获取类的isa指针的类即元类
(lldb) p/x 0x00000001066cc568 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00000001066cc568
(lldb) po 0x00000001066cc568
LPPerson

调试过程中我们发现了三种可以查看类内存信息的方式:

以及我们可以得到的结论:

元类的特点:
元类的isa走向:

接着上面继续调试:

///打印元类的内存信息
(lldb) x/4g 0x00000001066cc568
0x1066cc568: 0x00007fff89d0fcd8 0x00007fff89d0fcd8
0x1066cc578: 0x00006000010aa100 0x0003c03500000007
///获取元类的isa指针
(lldb) po 0x00007fff89d0fcd8
NSObject
///获取元类的isa指针的指向类
(lldb) p/x 0x00007fff89d0fcd8 & 0x00007ffffffffff8ULL
(unsigned long long) $10 = 0x00007fff89d0fcd8
///获取类型
(lldb) po 0x00007fff89d0fcd8
NSObject
///获取NSObject的isa指针
(lldb) x/4g 0x00007fff89d0fcd8
0x7fff89d0fcd8: 0x00007fff89d0fcd8 0x00007fff89d0fd00
0x7fff89d0fce8: 0x00006000002a0e00 0x0009c0310000000f
///获取类型
(lldb) po 0x00007fff89d0fcd8
NSObject

看到这里,我们就可以到结论了:

总结:

类的继承走向

(lldb) po student.superclass
LPPerson

(lldb) po LPPerson.superclass
NSObject

(lldb) po NSObject.superclass
 nil
总结:

实例对象继承于类,类对象继承于根类NSObject,而NSObject继承于nil

类的唯一性

继续我们的lldb调试

///获取NSObject的isa指针
(lldb) x/4g 0x00007fff89d0fcd8
0x7fff89d0fcd8: 0x00007fff89d0fcd8 0x00007fff89d0fd00
0x7fff89d0fce8: 0x00006000002a0e00 0x0009c0310000000f

///获取类型
(lldb) po 0x00007fff89d0fcd8
NSObject

///获取NSObject的内存信息
(lldb) x/4g NSObject.class
0x7fff89d0fd00: 0x00007fff89d0fcd8 0x0000000000000000
0x7fff89d0fd10: 0x00006000002b0c00 0x000980100000000f

可以发现这个地方的NSObjectisa地址和我们上面LPPerson的根元类NSObjectisa一样,即:

内存中只存在存在一份根元类NSObject,根元类的元类是指向它自己

同样的我们也可以通过代码验证:

Class class1 = [LPPerson class];
Class class2 = [LPPerson alloc].class;
Class class3 = object_getClass([LPPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p-\n", class1, class2, class3);

///结果:
0x1067f1598-
0x1067f1598-
0x1067f1598-
总结:

从结果中可以看出,打印的地址都是同一个,所以NSObject只有一份,即NSObject(根元类)在内存中永远只存在一份。
也即:由于类的信息在内存中永远只存在一份,所以类对象只有一份

对象的isa和继承走位:

image.png
isa走位总结:

superclass走位总结:

类 之间 的继承关系:

元类也存在继承,元类之间的继承关系如下:

这张图片业堪称业界标杆,isa走位和继承关系非常清晰,不清楚的同学可以多看几遍。

二、objc_class & objc_object

我们查看Objc源码会发现两个名字看起来相近的类:objc_classobjc_object,这两个分别是什么呢?
在之前的文章iOS 探索isa中,我们将main.m利用clang编译成了main.cpp。查看源码发现如下代码:

struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;

我们可以发现NSObject_IMPL实际就是NSObject的编译过来的,其中isa指针是来自于Class类型,而Class又是来自于objc_class结构体,也即是说:所有的对象的isa指针都来自于objc_class

我们在Objc源码中查找下objc_class的定义:

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;

以及

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();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }
....

第一种在runtime.h文件中,显示已经被废弃了
第二种在objc-runtime-new.h文件中,这是最新的代码。同时可以发现,objc_class是继承于objc_object的。

objc_classobjc_object 关系总结:

三、类的结构分析

1、内存偏移:

先思考一个问题,定义一个数组,并打印其每个元素的地址:
一般我们都会这样做:

 int c[4] = {1, 2, 3, 4};
    for (int i = 0; i< 4; i ++) {
      NSLog(@"%p", &c[i]);
    }

其实我们还可以定义一个数组指针来做:

    int c[4] = {1, 2, 3, 4};
    int *d = c;
    for (int i = 0; i< 4; i ++) {
        //NSLog(@"%p", &c[i]);
        NSLog(@"%p", d + i);
    }

二者结果一样:

2020-09-13 22:37:43.204989+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f2170
2020-09-13 22:37:43.205222+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f2174
2020-09-13 22:37:43.205496+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f2178
2020-09-13 22:37:43.205747+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f217c

我们都知道,数组的地址就等于第一个元素的地址,我们可以看到,打印结果中每一个元素的地址只差4字节。这4个字节也可以称为偏移量,所以我是不是可以直接通过数组首地址,再加上对应位置的偏移量即可取出对应位置的数据呢?
我们再验证下:

int c[4] = {1, 2, 3, 4};
    int *d = c;
    for (int i = 0; i< 4; i ++) {
        //NSLog(@"%p", &c[i]);
        NSLog(@"%d", *(d + i));
    }
///结果:
2020-09-13 22:44:17.802263+0800 Alloc&init&new[4876:10198358] 1
2020-09-13 22:44:17.802480+0800 Alloc&init&new[4876:10198358] 2
2020-09-13 22:44:17.802785+0800 Alloc&init&new[4876:10198358] 3
2020-09-13 22:44:17.803658+0800 Alloc&init&new[4876:10198358] 4

完美!接下里我们就愉快的进行类的结构分析了。

2、类的结构分析

旧版本的Objc源码呢,对于objc_class结构体的定义比较简单,其内部结构包括:objc_ivar_listobjc_method_listobjc_cache以及 objc_protocol_list可以很清楚的看到,但是新版本发生了很大的变化,结构和之前完全不一样了,我们就需要借助内存偏移来帮助我们观察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();
    }
    .....  

有用的定义的只有这5个类型,但是真正存放类的结构的只可能在bits中了,我们需要拿到bits,就需要计算出前三个属性的内存大小,从而通过偏移量去取到bits

接下来,我们来分析下前三个属性的内存大小:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;
    
    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;
    
    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
...

只截取了会影响到cache_t大小的部分代码:

注意:static类型的不影响结构体大小

如果走#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
如果走#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
_flags_occupied
所以,我们可以知道cache_t的内存大小 = 12 + 2 + 2 = 16字节,即获取bits只需将首地址的基础上加上isa + superClass + cache = 32字节即可。

获取bits

同样利用LPPerson对象:

///获取LPPerson对象首地址
(lldb) p/x LPPerson.class
(Class) $1 = 0x00000001000020e8 LGPerson
///打印
(lldb) x/4g 0x00000001000020e8
0x1000020e8: 0x00000001000020c0 0x0000000100334140
0x1000020f8: 0x000000010032e410 0x0000801000000000

///获取bits ,在0x00000001000020c0基础上增加偏移量32字节,变成0x00000001000020e0
(lldb) p (class_data_bits_t*) 0x00000001000020e0
(class_data_bits_t *) $2 = 0x00000001000020e0
///根据class_rw_t *data()定义的方法获取data
(lldb) p $2.data()
(class_rw_t *) $3 = 0x0000000100a16440
  Fix-it applied, fixed expression was: 
    $2->data()
///打印data中所有数据
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975528
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff8e202cd8
}

这样我们就成功取到了bits数据了,接下里我们看下class_rw_t的定义:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif
....

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
......

其中也定义了methodspropertiesprotocols,我们尝试获取一下:

(lldb) p $4.methods()
(const method_array_t) $7 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000000000000
      arrayAndFlag = 0
    }
  }
}
(lldb) p $4.properties()
(const property_array_t) $8 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000000000000
      arrayAndFlag = 0
    }
  }
}
(lldb) p $4.protocols()
(const protocol_array_t) $9 = {
  list_array_tt<unsigned long, protocol_list_t> = {
     = {
      list = 0x0000000000000000
      arrayAndFlag = 0
    }
  }
}

觉得不错记得点赞哦!听说看完点赞的人逢考必过,逢奖必中。ღ( ´・ᴗ・` )比心

上一篇下一篇

猜你喜欢

热点阅读