iOS:NSObject对象的内存布局

一、NSObject的底层结构

NSObject的底层结构是一个结构体,先来看两个结构体:

objc_object是所有结构体的祖先结构体吧,而我们所有NS开头的oc对象都是class,也就是objc_classobjc_class继承自objc_object,对没错C++的结构体可以继承。不想看细节可以直接跳到1

struct objc_object {
private:
    isa_t isa;
public:
    Class ISA();// ISA() assumes this is NOT a tagged pointer object
    Class getIsa(); // getIsa() allows this to be a tagged pointer object
    bool isTaggedPointer();
    bool hasAssociatedObjects(); // object may have associated objects?
    void setHasAssociatedObjects();
    bool isWeaklyReferenced();    // object may be weakly referenced?
    void setWeaklyReferenced_nolock();
    bool hasCxxDtor(); // object may have -.cxx_destruct implementation?
};


union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;

#   define ISA_MASK        0x0000000ffffffff8ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };

};

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() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
}
1:id和NSObject对比
typedef struct objc_class *Class;
typedef struct objc_object *id;


// id
struct objc_object {
    isa_t isa;
}

// class
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;       
    class_data_bits_t bits;  
}

从上面的源码可以看出:

  1. id是struct objc_object结构体指针,可以指向任何OC对象,除了NSProxy。

NSObject是绝大数OC对象的基类,但是还有个NSProxy虚类。所以不能说id类型和NSObject *是等价的。

  1. id类型的实例在编译阶段不会做类型检测,会在运行时确定,所以id类型是运行时的动态类型。类NSObject的实例会编译期要做编译检查,保证指针指向是其NSObject类或其子类,当然实例的具体类型要在运行期确定,这也是iOS的多态的体现。
  2. id指针的会有局限性:调用方法,只能使用中括弧,不能使用点语法。

另外NSObject类和NSProxy类都是实现了协议NSObject。

2:cache_t cache

cache_t其实就是一个开放地址法的hash表

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};
  1. _buckets:数组,是bucket_t结构体的数组,bucket_t是用来存放方法的SEL内存地址和IMP的
  2. _mask的大小是数组大小 - 1,用作掩码。(index & _mask 就是取余)
  3. _occupied是当前已缓存的方法数。即数组中已使用了多少位置。

cache_t的方法添加时: 当前缓存大小还可以使用,无需扩容。
如果缓存太满,需要扩容,扩容为原来大小的2倍。放弃旧的缓存,新扩容的缓存为空。

如果得到的位置已经被占用,则往后寻找,直到找到空的位置,把缓存设置到这个位置

二、NSObject的对象在内存中的布局

变量指针 -> 实例对象 -> 类对象 -> 元类对象

@interface OBAnimal:NSObject
{
    int _age;
}
@end

@implementation OBAnimal

- (instancetype)init
{
    self = [super init];
    if (self) {
        _age = 10;
    }
    return self;
}
- (void)sleep {
    printf("%s",__func__);
}

@end



int main(int argc, const char * argv[]) {
    @autoreleasepool {  
    	Class cls = [OBAnimal class];
        OBAnimal *cat = [[OBAnimal alloc] init];
        printf("%p",cls);
        [cat sleep];
        printf("-----");
    }
    return 0;
}
1:对比cat和cls的地址
(lldb) p/x *cat
(OBAnimal) $1 = {
  NSObject = {
    isa = 0x001d800100008151 OBAnimal
  }
  _age = 0x0000000a
}

cls = 0x100008150

发现cat->isa 地址和cls不一样?
因为新版64位操作系统进行了优化,需要 & ISA_MASK

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#endif

cat->isa地址 & ISA_MASK

(lldb) p/x 0x001d800100008151 & 0x00007ffffffffff8UL
(unsigned long) $2 = 0x0000000100008150

//发现和 cls 一样 0x100008150
2:如何找到方法
struct objc_class : objc_object {
    // Class ISA;			// 8
    Class superclass;		//8
    cache_t cache;       	//16
    class_data_bits_t bits;  // 8
}

其中的 class_data_bits_t 就是 class_rw_t *的指针,而方法在class_ro_t *中,所以要找到class_ro_t *的内存地址

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

class_ro_t的结构


struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

png1

####### a:先找class_rw_t

struct objc_class的结构可知,bits 的地址是class首地址偏移32位
png2

bits的地址为:0x1006350A4

所以class_rw_t的结构体存储地址为0x00000001006350a0

(lldb) p/x 0x1006350A4 & 0x0000000ffffffff8ULL
(unsigned long long) $2 = 0x00000001006350a0

png3

####### b:再找class_ro_t
前8 字节是flagsversion,所以class_ro_t的地址偏移是8 ,地址为:0x1000080E0
png4

获取方法数组的地址

struct class_ro_t {
    uint32_t flags;  					//4
    uint32_t instanceStart;				//4
    uint32_t instanceSize;				//4
    uint32_t reserved;					//4
    const uint8_t * ivarLayout;			//8
    const char * name;					//8  -> 0x100003F92
    method_list_t * baseMethodList; 	//8  -> 0x100008080
    protocol_list_t * baseProtocols; 	//8
    //...
}

查看name 的地址0x100003F92,发现就是类名
png6

查看baseMethodList 的地址0x100008080,发现就是类名

// Two bits of entsize are used for fixup markers.
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    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;
    }
};
		|
		|
		|
struct method_list_t {
	uint32_t flag;  
	uint32_t conut; //方法个数
}		

后面紧跟这方法数组的第一个元素


struct method_t {
    SEL name; 				//8
    const char *types;		//8
    IMP imp;				//8
};

png8

第一个方法是构造函数,跳过,直接看第二个方法

struct method_t {
    SEL name; 				//8  -> 0x7FFF7BABCD40
    const char *types;		//8	 -> 0x100003FA3
    IMP imp;				//8	 -> 0x100003E10
};

SEL:0x7FFF7BABCD40
png9

types:0x100003FA3
png10

IMP :0x100003e10
png12

在结合macho文件,可以知道这就是方法的实现
png11

ok

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值