阅读 243

iOS底层原理 (4)---- 类的原理分析上

isa的分析

以LGPerson为例来进行分析

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p  = [LGPerson alloc];
        NSLog(@"--->%@",p);       
}
复制代码

LLDB调试如下:

//查看对象的值
(lldb) po p
<LGPerson: 0x107c0bcc0>

//打印对象的内存,一般第一段存储的就是isa的地址
(lldb) x/4gx p
0x107c0bcc0: 0x011d8001000082c5 0x0000000000000000
0x107c0bcd0: 0x0000000000000000 0x0000000000000000

//将对象的isa地址与掩码进行 ‘&’操作
//0x00007ffffffffff8UL 这个掩码的值是固定的
(lldb) p/x 0x011d8001000082c5 & 0x00007ffffffffff8UL  
(unsigned long) $2 = 0x00000001000082c0

//得到的是 LGPerson
(lldb) po 0x00000001000082c0
LGPerson
复制代码

接下来继续分析LGPerson

//查看LGPerson的内存情况
(lldb) x/4gx  0x00000001000082c0
0x1000082c0: 0x0000000100008298 0x00007fff88b86cc8
0x1000082d0: 0x000000010052b7a0 0x0002802400000003

//将LGPerson的isa与掩码进行'&'操作
(lldb) p/x 0x0000000100008298 & 0x00007ffffffffff8UL
(unsigned long) $4 = 0x0000000100008298

//打印该地址存放的值
(lldb) po 0x0000000100008298
LGPerson

复制代码

上一步得到一个LGPerson,这一步又得到一个LGPerson,名字相同,但是内存地址不一样 0x00000001000082c0 != 0x0000000100008298,这是什么情况呢?LGPerson是类名,在内存中有多个这样的类存在吗?类像对象一样可以在内存中创建多次吗?

类的深层探索

在上面的基础上,通过多种方式创建多个类,查看他们的内存地址

Class cls1 = [LGPerson class];
Class cls2 = [[LGPerson alloc] init].class;
Class cls3 = object_getClass([LGPerson alloc]);
NSLog(@"cls1 ==== %p", cls1);
NSLog(@"cls2 ==== %p", cls2);
NSLog(@"cls3 ==== %p", cls3);
复制代码

打印结果:

2021-06-26 10:44:29.889553+0800 [1452:53722] cls1 ==== 0x1000082d8
2021-06-26 10:44:29.889632+0800 [1452:53722] cls2 ==== 0x1000082d8
2021-06-26 10:44:31.411735+0800 [1452:53722] cls3 ==== 0x1000082d8
复制代码

可以看出来,这三种不同的创建类的方式,得出的类的内存地址都是一样的,说明类在内存中是唯一的。那么第一次探索的时候,为何会有两个名字一样的LGPerson呢,那个地址不一样的LGPerson到底是什么呢?接下来继续探索吧。

将可执行文件放到MachOView查看,找到符号表,在根据地址找到对应的符号所在的位置:0x00000001000082d80x00000001000082b0

Screen Shot 2021-06-26 at 10.59.05 AM.png

可以看出来一开始得到的两个类里面的第一个类是我们的LGPerson_OBJC_CLASS_$_LGPerson,第二个类LGPerson_OBJC_METACLASS_$_LGPerson我们称他为LGPerson的元类,是由系统生成的。

isa的走位

基于上的分析,进一步探索isa的指向

(lldb) x/4gx 0x00000001000082b0
0x1000082b0: 0x00007fff88b86ca0 0x00007fff88b86ca0
0x1000082c0: 0x00000001078c71a0 0x0002e03500000003

(lldb) p/x 0x00007fff88b86ca0 & 0x00007ffffffffff8UL
(unsigned long) $4 = 0x00007fff88b86ca0

(lldb) po $4
NSObject

(lldb) x/4gx 0x00007fff88b86ca0
0x7fff88b86ca0: 0x00007fff88b86ca0 0x00007fff88b86cc8
0x7fff88b86cb0: 0x00000001078c74c0 0x0003e03100000007

复制代码

从上面的结果可以看到,元类的isa指向NSObjectNSObjectisa指向它本身,我们称NSObject为根元类。

通过以上的探索,可以验证isa的流程图:

isa流程图.png

元类的继承链

先补充一个知识点: object_getClass 该方法返回的是自身的isa指向的地址。

通过实例来分析元类的继承链

LGPerson *object = [LGPerson alloc];
Class class1 = object_getClass(object);
Class metaClass = object_getClass(class1);
Class rootMetaClass = object_getClass(metaClass);
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"实例对象:%p",object);
NSLog(@"类: %p",class1);
NSLog(@"元类:%p",metaClass);
NSLog(@"元类的父类:%p",class_getSuperclass(metaClass));
NSLog(@"根元类:%p",rootMetaClass);
NSLog(@"根元类的父类:%p",class_getSuperclass(rootMetaClass));
NSLog(@"根元类的根元类:%p",rootRootMetaClass);
        
复制代码
2021-06-26 16:36:11.113527+0800[2512:198583] 实例对象:0x1074040c0
2021-06-26 16:36:11.113853+0800[2512:198583] 类: 0x1000082d0
2021-06-26 16:36:11.113889+0800[2512:198583] 元类:0x1000082a8
2021-06-26 16:36:11.113930+0800[2512:198583] 元类的父类:0x7fff88b86ca0
2021-06-26 16:36:11.113963+0800[2512:198583] 根元类:0x7fff88b86ca0
2021-06-26 16:36:11.113989+0800[2512:198583] 根元类的父类:0x7fff88b86cc8
2021-06-26 16:36:24.996991+0800[2512:198583] 根元类的根元类:0x7fff88b86ca0
复制代码

从上面可以看出,元类的父类根原类是一样的,也就是说 对象的元类的父类就是当前对象的根元类

类的结构分析

类在底层是一个objc_class的结构体

objc/runtime.hobjc_class结构体的定义如下:

struct objc_class {

        Class isa  OBJC_ISA_AVAILABILITY; //指向元类的objc_class结构体指针,iOS中的类也是对象,元类中储存有类对象的类方法;
        #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                                OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
        long info                                      OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
        struct objc_ivar_list *ivars           OBJC2_UNAVAILABLE;  // 该类的成员变量链表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
        struct objc_cache *cache                       OBJC2_UNAVAILABLE;  // 方法缓存
        struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE;  // 协议链表
        #endif
} OBJC2_UNAVAILABLE;

复制代码

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 getSuperclass() const {
·····
复制代码

objc_object的定义:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
复制代码

类的结构内存计算

分析LGPerson的内存情况

(lldb) x/4gx LGPerson.class
0x100008600: 0x0000000100008628 0x000000010036a140
0x100008610: 0x0000000100640fd0 0x000780480000000f
复制代码

Class ISA ,占用8字节,因此0x0000000100008628存的是isa;

Class superClass ,占用8字节,因此0x000000010036a140存的是superClass;

cache_t 结构体比较复杂,需要分析一下:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // 4
#if __LP64__
            uint16_t                   _flags;  // 2
#endif
            uint16_t                   _occupied; // 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
    };

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    
    ···
复制代码

分析占用内存大小,影响内存的因素是成员变量,static修饰的变量存在全局区,方法存在方法区也不存在结构体内存中,不影响结构体内存的大小,因此只需要考虑前面几个成员变量占用的内存情况就可以了。

_bucketsAndMaybeMaskuintptr_t 类型 ,占用8字节; union 为联合体,成员变量互斥,内部成员变量占用同一块内存,较大的成员变量占用的内存大小就是联合体的大小

union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // mask_t为uint32_t 占4字节
#if __LP64__
            uint16_t                   _flags;     // uint16_t 占2字节
#endif
            uint16_t                   _occupied;  // uint16_t 占2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  // 占8字节
    };
复制代码

因此,union 占用了8个字节 ,cache_t 占用了16个字节。

bits的地址即为: 类的首地址偏移8 + 8 + 16 = 32字节及偏移 0x20

LLDB 分析类的结构

LGPerson

@interface LGPerson : NSObject

@property (nonatomic) int age;
@property (nonatomic, strong) NSString *hobby;

- (void)saySomething;

+ (void)doSomething;
@end



#import "LGPerson.h"

@interface LGPerson ()
{
    NSString *name;
}
@end

@implementation LGPerson

- (instancetype)init{
    if (self = [super init]) {
        
    }
    return self;
}

- (void)saySomething{
    
}

+ (void)doSomething{
    
}
@end
复制代码

main函数

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
    }
    return 0;
}

复制代码

lldb调试:

(lldb) x/4gx person
0x1013040a0: 0x011d8001000083e1 0x0000000000000000
0x1013040b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x1013040a0 + 0x20 
(long) $1 = 0x00000001013040c0
(lldb) p (class_data_bits_t *)0x00000001013040c0
(class_data_bits_t *) $2 = 0x00000001013040c0
(lldb) p $2
(class_data_bits_t *) $2 = 0x00000001013040c0
(lldb) p $2->data() //这里是指针所以用 -> ,结构体的话用 .
(class_rw_t *) $3 = 0x00006957534e5b28
(lldb) p *$3
复制代码

上面这种方式打印不出来$3的值,报错内容是违反了对象的唯一性约束。换下面这种方式打印:

(lldb) x/4gx LGPerson.class
0x1000083e0: 0x0000000100008408 0x000000010036a140
0x1000083f0: 0x0000000100362380 0x0000802800000000
(lldb) p/x 0x1000083e0+0x20
(long) $1 = 0x0000000100008400
(lldb) p (class_data_bits_t *)0x0000000100008400
(class_data_bits_t *) $2 = 0x0000000100008400
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101211190
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000440
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
复制代码

属性获取

(lldb) p $4.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000082c0
      }
      arrayAndFlag = 4295000768
    }
  }
}

(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
  ptr = 0x00000001000082c0
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x00000001000082c0
(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}

//get是C++方法
(lldb) p $8.get(0)
(property_t) $9 = (name = "age", attributes = "Ti,N,V_age")
(lldb) p $8.get(1)
(property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",&,N,V_hobby")

复制代码

通过上面的调试得到了该类的属性列表。

源码的探索过程:

image.png

方法获取

(lldb) p $4.methods()
(const method_array_t) $11 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000081c0
      }
      arrayAndFlag = 4295000512
    }
  }
}
(lldb) p $11.list
(const method_list_t_authed_ptr<method_list_t>) $12 = {
  ptr = 0x00000001000081c0
}
(lldb) p $12.ptr
(method_list_t *const) $13 = 0x00000001000081c0
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $14.get(0)
(method_t) $15 = {}
复制代码

用跟属性同样的方法去获取方法列表,结果获取不到,这是为什么呢?推测,他们内部的存储结构可能不一样。

进入methods的源码去查看:

struct class_rw_t{
    ...
    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 *>(&ro_or_rw_ext)->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
            }
        }
     ...
}
复制代码

返回的类型为 method_array_t ,进入定义查看:

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
复制代码

method_array_t 继承自 list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>,查看 method_tmethod_list_t

method_list_t的定义:

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
    bool isUniqued() const;
    bool isFixedUp() const;
    void setFixedUp();

    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return i;
    }

    bool isSmallList() const {
        return flags() & method_t::smallMethodListFlag;
    }

    bool isExpectedSize() const {
        if (isSmallList())
            return entsize() == method_t::smallSize;
        else
            return entsize() == method_t::bigSize;
    }

    method_list_t *duplicate() const {
        method_list_t *dup;
        if (isSmallList()) {
            dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1);
            dup->entsizeAndFlags = method_t::bigSize;
        } else {
            dup = (method_list_t *)calloc(this->byteSize(), 1);
            dup->entsizeAndFlags = this->entsizeAndFlags;
        }
        dup->count = this->count;
        std::copy(begin(), end(), dup->begin());
        return dup;
    }
};
复制代码

method_t的定义:

struct method_t {
...
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    ...
}
复制代码

这里可以看到method_t里面的bigproperty_t的结构很详细,推测是不是可以通过big获取方法,验证如下:

(lldb) p $14.get(0).big()
(method_t::big) $16 = {
  name = "hobby"
  types = 0x0000000100003f67 "@16@0:8"
  imp = 0x0000000100003da0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $14.get(1).big()
(method_t::big) $18 = {
  name = "setHobby:"
  types = 0x0000000100003f77 "v24@0:8@16"
  imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $14.get(2).big()
(method_t::big) $19 = {
  name = "saySomething"
  types = 0x0000000100003f6f "v16@0:8"
  imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson saySomething])
}
(lldb) p $14.get(3).big()
(method_t::big) $20 = {
  name = "init"
  types = 0x0000000100003f67 "@16@0:8"
  imp = 0x0000000100003d00 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $14.get(4).big()
(method_t::big) $21 = {
  name = "age"
  types = 0x0000000100003f84 "i16@0:8"
  imp = 0x0000000100003d60 (KCObjcBuild`-[LGPerson age])
}
(lldb) p $14.get(5).big()
(method_t::big) $22 = {
  name = "setAge:"
  types = 0x0000000100003f8c "v20@0:8i16"
  imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setAge:])
}
复制代码

最终成功获取到了相应的方法。

文章分类
iOS
文章标签