一、NSObject的底层结构
NSObject的底层结构是一个结构体,先来看两个结构体:
objc_object
是所有结构体的祖先结构体吧,而我们所有NS开头的oc对象都是class,也就是objc_class
,objc_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;
}
从上面的源码可以看出:
- id是struct objc_object结构体指针,可以指向任何OC对象,除了NSProxy。
NSObject是绝大数OC对象的基类,但是还有个NSProxy虚类。所以不能说id类型和NSObject *是等价的。
- id类型的实例在编译阶段不会做类型检测,会在运行时确定,所以id类型是运行时的动态类型。类NSObject的实例会编译期要做编译检查,保证指针指向是其NSObject类或其子类,当然实例的具体类型要在运行期确定,这也是iOS的多态的体现。
- id指针的会有局限性:调用方法,只能使用中括弧,不能使用点语法。
另外NSObject类和NSProxy类都是实现了协议NSObject。
2:cache_t cache
cache_t其实就是一个开放地址法的hash表
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
- _buckets:数组,是bucket_t结构体的数组,bucket_t是用来存放方法的SEL内存地址和IMP的
- _mask的大小是数组大小 - 1,用作掩码。(
index & _mask
就是取余) - _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;
};
####### a:先找class_rw_t
由struct objc_class
的结构可知,bits
的地址是class
首地址偏移32位
bits的地址为:0x1006350A4
所以class_rw_t
的结构体存储地址为0x00000001006350a0
(lldb) p/x 0x1006350A4 & 0x0000000ffffffff8ULL
(unsigned long long) $2 = 0x00000001006350a0
####### b:再找class_ro_t
前8 字节是flags
和version
,所以class_ro_t
的地址偏移是8 ,地址为:0x1000080E0
获取方法数组的地址
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
,发现就是类名
查看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
};
第一个方法是构造函数,跳过,直接看第二个方法
struct method_t {
SEL name; //8 -> 0x7FFF7BABCD40
const char *types; //8 -> 0x100003FA3
IMP imp; //8 -> 0x100003E10
};
SEL:0x7FFF7BABCD40
types:0x100003FA3
IMP :0x100003e10
在结合macho文件,可以知道这就是方法的实现
ok