内存偏移
- 定义一个数组并打印数组中的元素地址:
int a[4] = {
1,2,3,4};
int *b = a;
NSLog(@"%p - %p - %p - %p", &a, &a[0], &a[1], &a[2]);
NSLog(@"%p - %p - %p - %p", b, b+1, b+2, b+3);
- 打印结果如下:
0x7ffeefbff510 - 0x7ffeefbff510 - 0x7ffeefbff514 - 0x7ffeefbff518
0x7ffeefbff510 - 0x7ffeefbff514 - 0x7ffeefbff518 - 0x7ffeefbff51c
-
由上面结果分析可知:
- 由 &a 与 &a[0] 的打印结果相同可知, 数组的首地址存着数组的第一个元素 ;
- int 占用 4 个字节,由打印 b 的指针可以看出,0x7ffeefbff510 -> 0x7ffeefbff51 4地址偏移 4 个字节, 通过对地址的偏移,一样可以找到数组a中的元素 ;
-
再通过 lldb 测试由 b 拿到数组 a 中的元素:
(lldb) po *b
1
(lldb) po *(b+1)
2
(lldb) po *(b+2)
3
(lldb) po *(b+3)
4
- 可以得出内存地址对应的关系如下:
- 可以通过 地址偏移指向接下来连续的内存地址 ,取到自己需要的相应元素。
类的结构
一、类结构组成
-
要分析类的结构,可以 运用 clang 将目标文件编译成 cpp(C++文件) 。
编译 cpp(C++文件) 的方法请参考之前的博文:iOS之深入解析对象isa的底层原理。 -
新建一个 YDWPerson 类,并声明属性和方法,并新建一个 YDWDriver 继承于YDWPerson,如下:
@interface YDWPerson : NSObject {
NSString *name;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
- (void)goShopping;
@end
@interface YDWDriver : YDWPerson
@end
- 通过 LLDB 调试:
// person 的内存
(lldb) x/4gx person
// 0x001d800100002325 为 person 的 isa 指针地址
0x101415f70: 0x001d800100002325 0x0000000000000000
0x101415f80: 0x0000000000000000 0x0000000000000000
// person 首地址
(lldb) p/x person
(YDWPerson *) $1 = 0x0000000101415f70
// 获取类信息,即类的指针地址:创建的 person 的类 YDWPerson
(lldb) p/x 0x001d800100002325 & 0x00007ffffffffff8ULL
// 0x0000000100002320 为类的指针地址
(unsigned long long) $2 = 0x0000000100002320
// 根据类的指针地址获取类信息
(lldb) po 0x0000000100002320
YDWPerson
// 通过类信息地址获取类的内存分布
(lldb) x 0x0000000100002320
0x100002320: f8 22 00 00 01 00 00 00 40 41 33 00 01 00 00 00 ."......@A3.....
0x100002330: 40 e4 32 00 01 00 00 00 00 00 00 00 2c 80 00 00 @.2.........,...
// 通过类 YDWPerson 获取类的内存分布
(lldb) x YDWPerson.class
0x100002320: f8 22 00 00 01 00 00 00 40 41 33 00 01 00 00 00 ."......@A3.....
0x100002330: 40 e4 32 00 01 00 00 00 00 00 00 00 2c 80 00 00 @.2.........,...
// 通过object_getClass方法获取类的内存分布
(lldb) x object_getClass(person)
0x100002320: f8 22 00 00 01 00 00 00 40 41 33 00 01 00 00 00 ."......@A3.....
0x100002330: 40 e4 32 00 01 00 00 00 00 00 00 00 2c 80 00 00 @.2.........,...
// YDWPerson 的内存信息(0x100002320为类的首地址)
(lldb) x/4gx 0x100002320
0x100002320: 0x00000001000022f8 0x0000000100334140
0x100002330: 0x000000010032e440 0x0000802c00000000
// YDWPerson内存中isa的指针指向(0x00000001000022f8为类的isa指针地址)
(lldb) po 0x00000001000022f8
YDWPerson
// 类信息(指针地址)
(lldb) p/x 0x00000001000022f8 & 0x00007ffffffffff8ULL
// 0x00000001000022f8 为元类的指针地址
(unsigned long long) $9 = 0x00000001000022f8
// 元类的信息
(lldb) po 0x00000001000022f8
YDWPerson
-
分析以上LLDB调试,不难发现:p/x 0x001d800100002325 & 0x00007ffffffffff8ULL 与 p/x 0x00000001000022f8 & 0x00007ffffffffff8ULL 的结果都是 YDWPerson,这是为什么呢?其实,这是因为 元类 导致的。
- 0x001d800100002325 是 person 对象的 isa 指针地址 ,其&后得到的结果是创建 person 的类 YDWPerson;
- 0x00000001000022f8 是 isa 中 获取的类信息所指的类的 isa 的指针地址 ,即 YDWPerson 类的类的 isa 指针地址 ,在Apple中,可以称 YDWPerson 类的类为 元类 。
-
打开 cpp 文件,类的结构定义如下:
struct objc_class : objc_object {
Class ISA; // 8字节
Class superclass; // 结构体指针8字节
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();
}
/**此处省略代码*/
}
typedef struct objc_class *Class;
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
...
}
- 从上面可以看出类有四个成员:
- 第一个是isa;
- 第二个父类指针;
- 第三个是缓存;
- 第四个是bits,是一个结构体;
- 分析上面的 objc 源码可得:
- 类 Class 是 objc_class 类型, objc_class 类型继承自 objc_object 类型, objc_object 类型有一个 isa 的成员变量。
- objc_class 结构体是继承于 objc_object 结构体,自然也就有 isa 的成员变量,这是源自于父类,并且 objc_class 结构体的 isa 是指向父类 objc_object 结构体的,这也就说明了类也是一种 类对象 。
- 由上面内存偏移的分析可知,如果要获取 class_data_bits_t bits ,只需要知道对首地址偏移多少便能获取到,Class 定义为结构体,我们可以知道 isa、superclass 各占8个字节,那么 bitsclass_data_bits_t 又占多少字节呢?
- cache_t 结构体如下:
struct cache_t {
struct bucket_t *_buckets;// 结构体8个字节
mask_t _mask;//typedef uint32_t mask_t; 由此可知mask_t占用4个字节
mask_t _occupied; // 4个字节
public: // 方法不占内存
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
-
计算前两个属性 bucket_t、mask 或 _maskAndBuckets、_mask_unused 的内存大小,有以下两种情况:
- ① buckets 类型是 struct bucket_t * ,是结构体指针类型,占 8 字节;
mask 是 mask_t 类型,而 mask_t 是 unsigned int 的别名,占 4 字节; - ② _maskAndBuckets 是 uintptr_t 类型,它是一个指针,占 8 字节;
_mask_unused 是 mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占 4 字节; - ③ bucket_t、mask 或 _maskAndBuckets、_mask_unused 两种情况,所占的内存都是 12 字节;
- ① buckets 类型是 struct bucket_t * ,是结构体指针类型,占 8 字节;
-
_flags 是 uint16_t 类型, uint16_t 是 unsigned short 的别名,占 2 字节;
-
_occupied 是 uint16_t 类型, uint16_t 是 unsigned short 的别名,占 2 字节;
-
分析可以看出: cache_t 所占的字节说为 12 + 2 + 2 = 16 个字节,因此要拿到 bits 只需 将首地址偏移 8 + 8 +16 = 32 个字节 便可得到;
二、类结构成员分析
- C/OC 的数据类型所占的内存如下:
C | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL(64位) | 1 | 1 |
signed char | (_signed char)int8_t、BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int16_t | 2 | 2 |
unsigned short | unichar | 2 | 2 |
int、int32_t | NSInteger(32位)、boolean_t(32位) | 4 | 4 |
unsigned int | NSUInteger(32位)、boolean_t(64位) | 4 | 4 |
long | NSInteger(64位) | 4 | 8 |
unsigned long | NSUInteger(64位) | 4 | 8 |
long long | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |
- 类 objc_class 的结构成员如下:
struct objc_class : objc_object {
Class ISA; // 8字节
Class superclass; // 结构体指针8字节
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();
}
/**此处省略代码*/
}
typedef struct objc_class *Class;
- 进入 objc_object 里面可以看到,占用 8 个字节;
struct objc_object {
private:
isa_t isa;
...
}
- Class superclass 父类,是一个指针,占用 8 个字节;
typedef struct objc_class *Class;
- cache_t cache 结构体,占16个字节;
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;
... 都是静态变量,不计入结构体大小
#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;
... 都是静态变量,不计入结构体大小
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
...都是方法和静态变量,不计入结构体大小
}
- buckets、mask、flags 如下:
// buckets:指针类型占8个字节
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
// mask:uint32_t类型,4个字节
typedef unsigned int uint32_t;
// flags:uint16_t类型,2个字节
typedef unsigned short uint16_t;
- occupied:uint16_t类型,占 2 个字节;
- class_data_bits_t bits 是一个结构体,结构体 bits 有一个方法 bits.data() ,可以看到方法 data() 是 class_rw_t 类型的,查看 class_rw_t 类型,会发现我们要找的属性和方法就在里面;
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
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;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
void setFlags(uint32_t set)
{
OSAtomicOr32Barrier(set, &flags);
}
void clearFlags(uint32_t clear)
{
OSAtomicXor32Barrier(clear, &flags);
}
......省略其他信息......
- 那么类的属性列表,即 property_list 最可能是存在 bits 里面,继续点击 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 *>()->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};
}
}
- 根据上面的指针和内存偏移内容可以知道,如果我们知道类的地址,并且属性是按照顺序依次排列的,只要我们知道isa、superclass、cache的内存大小,那我们就可以通过内存偏移来得到bits的内存地址,然后取出bits里面的内容。
- 成员变量操作函数,主要包含以下函数:
- class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
- class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。
- class_addIvar只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。
- class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
- 属性操作函数,主要包含以下函数:
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );
// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
实例对象、类、元类关系分析
一、什么是元类?
- 对象的 isa 是指向类,类的其实也是一个对象,可以称为 类对象 ,其 isa 的位域 指向苹果定义的元类 ;
- 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于 元类 ;
- 元类是 类对象 的类,每个类都有一个独一无二的元类用来存储类方法的相关信息;
- 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称。
二、Apple 官方图分析
- Apple官方的实例对象、类、元类关系图如下:
-
isa 的指向关系:
- 实例对象(Instance of Subclass) 的isa指向的是 类(class) ;
- 类对象(class) 的isa指向的 元类(Meta class) ;
- 元类(Meta class) 指向 根元类(Root metal class) ;
- 根元类(Root metal class) 指向 自己 ;
- NSObject的父类是 nil ,根元类的父类是 NSObject 。
-
superclass 的指向关系:
- 类(subClass) 继承于 父类(superClass) ;
- 父类(superClass) 继承于 根类(RootClass) ,此时的根类是指 NSObject ;
- 根类继承于 nil 。
-
元类之间的继承关系如下:
- 子类的元类(metal SubClass) 继承于父类的 元类(metal SuperClass) ;
- 父类的元类(metal SuperClass) 继承于 根元类(Root metal Class) ;
- 根元类(Root metal Class) 继承于 根类(Root class) ,此时的根类是 NSObject 。
三、源码分析
在 objc/runtime.h 中 objc_class 结构体如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#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-runtime-new.h 中 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();
}
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);
}
// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear) {
ASSERT(isFuture() || isRealized());
ASSERT((set & clear) == 0);
data()->changeFlags(set, clear);
}
// 此处省略以下源码...
- 继续查看 objc_object 源码如下:
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_object {
private:
isa_t isa;
public:
- objc_class 与 objc_object 关系如下:
- 结构体类型 objc_class 继承于 objc_object 类型,其中 objc_object 也是一个结构体,且有一个 isa 属性,所以 objc_class 也拥有了 isa 属性;
- NSObject 中的 isa 在底层是由 Class 定义的,其中 class 的底层编码来自 objc_class 类型,所以 NSObject 也拥有了 isa 属性;
- NSObject 是一个类,用它初始化一个实例对象 objc,objc 满足 objc_object 的特性(即有 isa 属性),主要是因为 isa 是由 NSObject 从 objc_class 继承过来的,而 objc_class 继承于 objc_object,objc_object 有 isa 属性。所以对象都有一个 isa,isa 表示指向来自于当前的 objc_object;
- objc_object 是当前的根对象,所有的对象都有这样一个特性 objc_object,即拥有 isa 属性;
- 指向元类的指针(isa):在OC中所有的类其实也是一个对象,那么这个对象也会有一个所属的类,这个类就是元类,也就是结构体里面isa指针所指的类。
- 元类的定义:元类就是类对象的类。每个类都有自己的元类,因为每个类都有自己独一无二的方法。
① 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表(实例方法)。
② 当你给类发消息时,消息是在寻找这个类的元类的方法列表(类方法)。 - 那元类的类是什么呢?元类,就像之前的类一样,它也是一个对象。也可以调用它的方法,自然的,这就意味着它必须也有一个类。
所有的元类都使用根元类(继承体系中处于顶端的类的元类)作为它们的类,这就意味着所有NSObject的子类(大多数类)的元类都会以NSObject的元类作为他们的类
根据这个规则,所有的元类使用根元类作为他们的类,根元类的元类则就是它自己,也就是说基类的元类的isa指针指向他自己。 - class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
- 元类的定义:元类就是类对象的类。每个类都有自己的元类,因为每个类都有自己独一无二的方法。
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
- 指向元类的指针(isa)关系如下:
- 指向父类的指针(super_class):指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
// 获取类的父类
Class class_getSuperclass ( Class cls );
- objc_class、objc_object、isa、object、NSObject 的相互关系如下:
四、NSObject 的唯一性
- 通过以下代码查看运行结果:
Class class1 = [YDWPerson class];
Class class2 = [YDWPerson alloc].class;
Class class3 = object_getClass([YDWPerson alloc]);
NSLog(@"\n%p \n%p \n%p", class1, class2, class3);
- 打印结果如下:
0x100002328
0x100002328
0x100002328
- 明显看出:打印的地址都是同一个,所以 NSObject 只有一份,即 NSObject(根元类)在内存中永远只存在一份。
总结
- objc_class 与 NSObject 的关系:
NSObject 就是一个类,其本质是 objc_class,也就是说 NSObject 是 objc_class 的一种类型。 - objc_object 与NSObject 的关系:
NSObject 是 OC 的类型,objc_object 是 c 的类型。
NSObject 是对 objc_object的封装,OC 的底层编译是 C,也就是会转成 objc_object。