通过这篇文章可以获得什么:
- isa指向图分析
- 类、元类、根元类的继承关系
- 类的结构分析
- 在源码角度分析cache_t占用的内存
- 在类对象中通过地址偏移找到属性(properties)
- 在类对象中通过地址偏移找到实例方法(methods)
- 在类对象中通过地址偏移找到ivars(成员变量)
- 在类对象中通过地址偏移找到类方法
isa指向图分析
isa的掩码
用来获取类信息的掩码,将isa的地址
&
、ISA_MASK
,可以解析得到类的信息,ISA_MASK
是系统定义的,不同架构不同,我在objc4
源码内拿到的
x86_64:define ISA_MASK 0x00007ffffffffff8ULL
arm64:define ISA_MASK 0x0000000ffffffff8ULL
arm64(simulators):define ISA_MASK 0x007ffffffffffff8ULL
我这里探索过程是创建了一个FFPerson对象的实例person,通过lldb动态调试类探索isa的指向关系:
案例代码:
#import <Foundation/Foundation.h>
#import "FFPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//isa_mask 0x00007ffffffffff8ULL
FFPerson *person = [FFPerson alloc];
NSLog(@"%@",person);
}
return 0;
}
复制代码
实例对象的isa
指向类对象
p/x person
格式化打印person实例对象的内存地址x/4gx 0x000000010058ccc0
格式化打印0x000000010058ccc0
地址下的连续地址空间内存储的数据,拿到了person
对象的首地址
,也就是isa指针地址
p/x 0x011d8001000080e9 & 0x00007ffffffffff8
通过isa指针
和ISA_MASK
的与
操作,解析了FFPerson
类对象po 0x00000001000080e8
打印这个地址数据,得到了FFPerson
(lldb) p/x person
(FFPerson *) $5 = 0x000000010058ccc0
(lldb) x/4gx 0x000000010058ccc0
0x10058ccc0: 0x011d8001000080e9 0x0000000000000000
0x10058ccd0: 0x656c7552534e5b2d 0x7020726f74696445
(lldb) p/x 0x011d8001000080e9 & 0x00007ffffffffff8
(long) $6 = 0x00000001000080e8
(lldb) po 0x00000001000080e8
FFPerson
复制代码
类对象的isa
指向元类(metaClass)
x/4gx 0x00000001000080e8
通过实例对象的isa
指向类对象
,我拿到了类对象内存地址0x00000001000080e8
,格式化输出类对象的内存地址p/x 0x00000001000080c0 & 0x00007ffffffffff8
将类对象的首地址(isa指针地址)0x00000001000080c0
和ISA_MASK
做与操作,得到了一个新的内存地址0x00000001000080c0
po 0x00000001000080c0
打印这个地址数据,得到了FFPerson
(lldb) x/4gx 0x00000001000080e8
0x1000080e8: 0x00000001000080c0 0x00007fff88c10cc8
0x1000080f8: 0x000000010063dd70 0x0002801000000003
(lldb) p/x 0x00000001000080c0 & 0x00007ffffffffff8
(long) $8 = 0x00000001000080c0
(lldb) po 0x00000001000080c0
FFPerson
复制代码
结论一:0x00000001000080e8
和0x00000001000080c0
明显是两个内存地址,但是输出的却是一个对象,0x00000001000080e8
是类对象即class
,0x00000001000080c0
是元类对象即MetaClass
图解补充:
元类对象的isa
指向根元类(rootMetaClass)
x/4gx 0x00000001000080c0
通过类对象的isa
指针找到了元类对象地址,格式化输出元类对象地址,得到了元类对象的首地址
,即isa指针0x00007fff88c10ca0
p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8
同样,将isa
指针地址和ISA_MASK
做与
操作,得到了一个地址0x00007fff88c10ca0
po 0x00007fff88c10ca0
输出地址0x00007fff88c10ca0
,打印了NSObject
对象。
(lldb) x/4gx 0x00000001000080c0
0x1000080c0: 0x00007fff88c10ca0 0x00007fff88c10ca0
0x1000080d0: 0x0000000100704520 0x0002e03100000003
(lldb) p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8
(long) $10 = 0x00007fff88c10ca0
(lldb) po 0x00007fff88c10ca0
NSObject
复制代码
结论二:所有类对象的元类对象的isa
指针指向的根源类NSObject
图解补充:
根元类的isa指针指向自己
x/4gx 0x00007fff88c10ca0
格式化输出根元类
的地址,得到的对象依然是根元类自己
p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8
使用ISA_MAS
K的与
操作验证一下,得到了0x00007fff88c10ca0
po 0x00007fff88c10ca0
打印得到结果依然是NSObject
(lldb) x/4gx 0x00007fff88c10ca0
0x7fff88c10ca0: 0x00007fff88c10ca0 0x00007fff88c10cc8
0x7fff88c10cb0: 0x000000010063e590 0x0003e03100000007
(lldb) p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8
(long) $16 = 0x00007fff88c10ca0
(lldb) po 0x00007fff88c10ca0
NSObject
复制代码
superclass指向图分析
案例代码:
void ffSuperclass(void) {
//NSObjcet实例对象
NSObject *objectInstance = [NSObject alloc];
//NSObject类
Class object = object_getClass(objectInstance);
//NSobject元类
Class metaClass = object_getClass(object);
//NSObjct根元类
Class rootMetaClass = object_getClass(metaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n",objectInstance,object,metaClass,rootMetaClass);
//FFPerson的元类
Class personMetaClass = object_getClass(FFPerson.class);
NSLog(@"FFPerson的元类 : %@ - %p",personMetaClass, personMetaClass);
//FFPerson元类的父类
Class personSuperMetaclass = class_getSuperclass(personMetaClass);
NSLog(@"FFPerson元类的父类 : %@ - %p",personSuperMetaclass, personSuperMetaclass);
//FFChildren的元类
Class childrenMetaClass = object_getClass(FFChildren.class);
NSLog(@"FFChildren的元类 : %@ - %p:",childrenMetaClass,childrenMetaClass);
//FFchildren元类的父类
Class childrenSuperMetaClass = class_getSuperclass(childrenMetaClass);
NSLog(@"FFChildren元类的父类 : %@ - %p:",childrenSuperMetaClass,childrenSuperMetaClass);
//NSObject类的父类
Class superObject = class_getSuperclass(NSObject.class);
NSLog(@"NSObject的父类 : %@ - %p:",superObject,superObject);
//NSObject根元类的父类
Class rootSuperMetaObject = class_getSuperclass(rootMetaClass);
NSLog(@"NSObject根元类的父类 : %@ - %p:",rootSuperMetaObject,rootSuperMetaObject);
}
复制代码
测试结果:
2021-06-17 23:21:51.513376+0800 002-isa分析[2803:167864]
0x103804470 实例对象
0x7fff88c10cc8 类
0x7fff88c10ca0 元类
0x7fff88c10ca0 根元类
002-isa分析[2803:167864] FFPerson的元类 : FFPerson - 0x1000081c8
002-isa分析[2803:167864] FFPerson元类的父类 : NSObject - 0x7fff88c10ca0
002-isa分析[2803:167864] FFChildren的元类 : FFChildren - 0x100008178:
002-isa分析[2803:167864] FFChildren元类的父类 : FFPerson - 0x1000081c8:
002-isa分析[2803:167864] NSObject的父类 : (null) - 0x0:
002-isa分析[2803:167864] NSObject根元类的父类 : NSObject - 0x7fff88c10cc8:
复制代码
结论:
- NSOject对象的元类与根元类是一个
- 元类间也存在着继承的关系,跟类是一样的,通过打印FFChildren的元类的superClass与FFPerson的元类都得到了一样的地址0x1000081c8
- 根元类的父类指向了NSObject,通过打印NSObject的superClass与NSObject类得到了0x7fff88c10cc8地址
- NSObject的父类是(null),地址为0x0,即NSObject没有父类
类的结构分析
object1.0:已经不被使用
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;
复制代码
object2.0(基于objc4(818版本)的源码)
省略了部分源码:源码位置为objc-runtime-new.h文件第1688行-2173行
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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 {
#if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
if (superclass == Nil)
return Nil;
//省略了大部分源码
}
复制代码
Class ISA:8字节
CLass superclass:8字节
cache_t cache:16字节
typedef unsigned long uintptr_t;
无符号长整形,占8字节
preopt_cache_t *
指针占8字节
由于preopt_cache_t *
这个指针类型在union联合体中,是互斥的,所以这个联合体占8字节
所以struct cache_t
一共是16字节,即cache占用16字节
源码分析:省略了部分源码:源码位置为objc-runtime-new.h文件第338行-550行
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
//省略了跟内存大小无关的代码
}
复制代码
结论一:探索ISA、superclass、cache是为了通过内存偏移找到bits
class_data_bits_t bits:
源码分析:省略了部分源码:源码位置为objc-runtime-new.h文件第1577行-1685行
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
//此处省略了部分代码
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
//此处省略了部分代码
复制代码
结论二:通过获取class_rw_t*
类型的data(),将会拿到这个类的methods
、properties
、protocols
、deepCopy
、ro
等等信息
class_rw_t
源码分析:省略了部分源码:源码位置为objc-runtime-new.h文件第1458行-1574行
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
//省略了部分代码
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
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()};
}
}
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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
复制代码
结论三:通过解析class_rw_t这个结构体可以拿到类的信息,比如这个类的methods
、properties
、protocols
、deepCopy
、ro
等等信息
获取类的属性
成员变量获取流程:NSObject.class
-> class_data_bits_t
-> class_rw_t
-> property_array_t
-> property_list_t
-> property_t
案例代码:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end
复制代码
操作流程:
步骤:
x/4gx LGPerson.class
格式化输出LGPerson.class
,拿到类的首地址p/x 0x100008380 + 0x20
首地址偏移32个字节
(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址p (class_data_bits_t *)0x00000001000083a0
将地址转化成class_data_bits_t
类型,为了使用class_data_bits_t
的函数p $2->data()
使用class_data_bits_t
的data()
函数,拿到class_rw_t
类型的地址p $3->properties()
通过properties()
函数获取LGPerson
的成员变量p $4.list
和p $5.ptr
解析出property_list_t
的地址p *$6
通过取地址的方式获取成员变量property_list_t
p $7.get(0)
与p $7.get(1)
通过c++
函数单个获取类的成员变量name
、age
获取类的实例方法
实例方法获取流程:NSObject.class
-> class_data_bits_t
-> class_rw_t
-> method_array_t
-> method_list_t
-> method_t
-> big
与properties()一样,但是是获取class_rw_t对象的method()方法,有区别的地方在于当拿到method之后并不能直接向properties一样就可以解析出来,在method_t结构体中还有一层结构体big(),所以在获取实例方法的时候得多解析一层结构体
源码如下:省略了部分本文章未探索的源码,源码位置为objc-runtime-new.h文件第726行-861行
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
//此处省略了部分源码
}
复制代码
案例源码:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
- (void)saySomething;
@end
复制代码
lldb调试流程
步骤:
x/4gx LGPerson.class
格式化输出LGPerson.class
,拿到类的首地址p/x 0x1000083b8 + 0x20
首地址偏移32个字节
(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址p (class_data_bits_t *)0x00000001000083d8
将地址转化成class_data_bits_t
类型,为了使用class_data_bits_t
的函数p $2->data()
使用class_data_bits_t
的data()
函数,拿到class_rw_t
类型的地址p $3->properties()
通过methods()
函数获取LGPerson
的实例方法p $4.list
和p $5.ptr
解析出method_list_t
的地址p *$6
通过取地址的方式获实例变量数组method_list_t
,entsize_list_tt<method_t, method_list_t, 4294901763,method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 5)
,非常明显有5
个实例方法- 通过
c++
函数get()
与big()
单个获取类的实例方法:
p $7.get(0).big()
:-[LGPerson saySomething]自定义实例方法saySomething
p $7.get(1).big()
:-[LGPerson name]成员变量name的getter方法,是系统生成的
p $7.get(2).big()
:-[LGPerson setName:]成员变量name的setter方法,是系统生成的
p $7.get(3).big()
:-[LGPerson age]成员变量age的getter方法,是系统生成的
p $7.get(0).big()
:-[LGPerson setAge:]成员变量age的setter方法,是系统生成的
ivars(成员变量)在哪里
ivars获取流程源码:NSObject.class
-> class_data_bits_t
-> class_rw_t
-> class_ro_t
-> ivar_list_t
-> ivar_t
class_ro_t源码:省略了部分本文章未探索的源码,源码位置为objc-runtime-new.h文件第1037行-1171行
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
//省略了部分代码
}
复制代码
ivar_t源码:
struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms.
// We read and write only 32 bits of it.
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values.
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit.
#endif
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
复制代码
案例代码:
@interface LGPerson : NSObject
{
NSString * hobby;
float weight;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end
复制代码
lldb动态调试
步骤
x/4gx LGPerson.clas
s 格式化输出LGPerson.class
,获取到首地址p/x 0x100008408 + 0x20
首地址偏移32字节
(ISA8字节、superclass8字节、cache_t16字节),拿到包含类属性方法成员变量的对象class_data_bits_t的地址p (class_data_bits_t *)$1
将地址转换为class_data_bits_t
,为了使用class_data_bits_t
的函数p $2->data()
使用class_data_bits_t
的data()
函数,拿到class_rw_t
类型的地址p $3->ro
使用class_rw_t
的ro函数,拿到class_ro_t
类型的地址p *$4
取lass_ro_t
类型地址的值,拿到了class_ro_t对象p $5.ivars
使用ivars
函数获取class_ro_t
对象的ivars,得到了指向ivar_list_t
地址的指针p *$6
通过取地址的方式获实例变量数组ivar_list_t
,entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
,可以看到有4
个ivars- 通过
c++
函数get()
单个获取类的实例方法:
p $7.get(0)
:(ivar_t) $8 = {
offset = 0x0000000100008370
name = 0x0000000100003eec “hobby”
type = 0x0000000100003f60 “@”NSString””
alignment_raw = 3
size = 8
}
p $7.get(1)
:(ivar_t) $9 = {
offset = 0x0000000100008378
name = 0x0000000100003f27 “weight”
type = 0x0000000100003f87 “f”
alignment_raw = 2
size = 4
}
p $7.get(2)
:(ivar_t) $10 = {
offset = 0x0000000100008380
name = 0x0000000100003f2e “_age”
type = 0x0000000100003f89 “i”
alignment_raw = 2
size = 4
}
p $7.get(3)
:(ivar_t) $11 = {
offset = 0x0000000100008388
name = 0x0000000100003f33 “_name”
type = 0x0000000100003f60 “@”NSString””
alignment_raw = 3
size = 8
}
**结论:ivars
存在ro
中,成员变量自动生成了属性_name
,_age**
类方法在哪里
类方法获取流程:NSObject.class
-> metaClass
-> class_data_bits_t
-> class_rw_t
-> method_array_t
-> method_list_t
-> method_t
-> big
案例代码:
@interface LGPerson : NSObject
+ (void)eatFood;
@end
@implementation LGPerson
+ (void)eatFood{
NSLog(@"%s",__func__);
}
@end
复制代码
lldb动态调试
步骤
x/4gx LGPerson.class
格式化打印类LGPerson
,得到类的首地址\p/x 0x00000001000083e0 & 0x00007ffffffffff8
将isa
指针和ISA_MASK
做与
操作,拿到LGPerson
的metaClass
x/4gx 0x00000001000083e0
,格式化打印LGPerson
的metaClass
,拿到元类的首地址p/x 0x1000083e0 + 0x20
,将元类的首地址偏移32个字节(ISA8字节、superclass8字节、cache_t16字节),那多元类的class_data_bits_t对象地址p (class_data_bits_t *)0x0000000100008400
将地址转化为class_data_bits_t对象,方便调用函数p $3->data()
调用class_data_bits_t的data函数,拿到class_rw_t对象p $4->methods()
获取class_rw_t的methods方法列表p $5.list
和p $5.ptr
拿到指向method_list_t地址的指针p *$7
取地址,拿到了method_list_t
对象,count为1
,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
, 有一个类方法- 通过
c++
函数get()
与big()
单个获取类的类方法:
p $7.get(0).big()
:(method_t::big) $9 = {
name = “eatFood”
types = 0x0000000100003f74 “v16@0:8”
imp = 0x0000000100003ce0 (KCObjcBuild`+[LGPerson eatFood])
}
结论:类的类方法存在元类的methods里面,等同于类的类方法是元类的实例方法
补充:
小端模式
iOS是小端模式,内存上从右往左读取
高、低地址
高、低代表的是方向,高是左面、低是右面,通常以中间划分。例如一个8字节的64bit位,从左往右为高32位,从右往左位低32位
ULL
ULL:unsigned long long 无符号长整形
源码来源
我这里查看的源码是objc4-818.2.tar.gz
,github.com/LGCooci/obj…,有需求的伙伴可以自行获取,素质三连