mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
feat: 精准测试(OC、Swift)
This commit is contained in:
@@ -14,9 +14,253 @@ BSS段(bss segment):通常用来存储程序中未被初始化的全局变
|
||||
|
||||

|
||||
|
||||
## 二、研究下对象在内存中如何存储?
|
||||
|
||||
```Objective-C
|
||||
|
||||
## 二、 类的本质
|
||||
|
||||
Demo1
|
||||
|
||||
```objective-c
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
NSObject *obj = [[NSObject alloc] init];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
因为 OC 本质就是 c/c++,所以转成 c/c++ 来窥探下,采用指令 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp`(不要采用 `clang -rewrite-objc main.m -o main.cpp` 因为这样生成的 c++ 文件由于没有指明设备和对应的架构指令集,不够准确)。
|
||||
|
||||
产看生成的 `main-arm64.cpp` 其中有段代码是定义结构体。
|
||||
|
||||
```c++
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
};
|
||||
```
|
||||
|
||||
然后点击 NSObject 跳转官方的声明可以看到
|
||||
|
||||
```c++
|
||||
@interface NSObject <NSObject> {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
|
||||
Class isa OBJC_ISA_AVAILABILITY;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
// 剔除一些无效信息后
|
||||
@interface NSObject {
|
||||
Class isa;
|
||||
}
|
||||
```
|
||||
|
||||
因此可以知道,OC 的类底层是由 c/c++ 的继承实现的。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCObjectLayoutWhenISA.png" style="zoom:45%">
|
||||
|
||||
由于 obj 对象没有任何属性和方法,只有一个 isa 指针,且类的本质就是结构体,所以当结构体只有1个成员时,该成员的地址值,就是该结构体的地址。
|
||||
|
||||
即 isa 指针值为 0x100200110,结构体地址为 0x100200110,obj 指针值为 0x100200110。
|
||||
|
||||
|
||||
|
||||
Demo2
|
||||
|
||||
```objective-c
|
||||
@interface Student : NSObject
|
||||
{
|
||||
@public
|
||||
int _no;
|
||||
int _age;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation Student
|
||||
@end
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
Student *st = [[Student alloc] init];
|
||||
st->_no = 1;
|
||||
st->_age = 29;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
采用指令 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp` 转换为 c++ 代码
|
||||
|
||||
```c++
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
};
|
||||
|
||||
struct Student_IMPL {
|
||||
struct NSObject_IMPL NSObject_IVARS; // 父类的所有 ivars。由于 Student 父类是 NSObject,所以其实只有 isa
|
||||
int _no;
|
||||
int _age;
|
||||
};
|
||||
```
|
||||
|
||||
`struct NSObject_IMPL NSObject_IVARS;` 代表父类的所有 ivars。由于 Student 父类是 NSObject,所以其实只有 isa
|
||||
|
||||
由于 `NSObject_IMPL` 结构体只有1个 isa 成员,所以上面代码等价于
|
||||
|
||||
```c++
|
||||
struct Student_IMPL {
|
||||
Class isa;
|
||||
int _no;
|
||||
int _age;
|
||||
};
|
||||
```
|
||||
|
||||
类的本质是结构体,结构体成员内存紧挨着。内存布局如图所示:
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/StudentClassLayout.png" style="zoom:45%">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
如果上述结论正确,那是不是可以声明一个 ` Student_IMPL` 类型的结构体指针,指向 st 指针指向的对象。然后通过结构体指针访问成员变量,看看取值是不是正确的
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/StructPointerVistorClassIvars.png" style="zoom:25%">
|
||||
|
||||
发现是可以正确访问的。
|
||||
|
||||
为什么 `class_getInstanceSize` 打印出16?因为本质是计算所有 ivars 的内存大小,1个 isa,2个 int 就是16.
|
||||
|
||||
而 `malloc_size` 是系统真正分配的,由于最小分配16,所以刚好就是16.
|
||||
|
||||
|
||||
|
||||
Demo3
|
||||
|
||||
```objective-c
|
||||
@interface Person : NSObject
|
||||
{
|
||||
@public
|
||||
int _age;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation Person
|
||||
@end
|
||||
|
||||
@interface Student : Person
|
||||
{
|
||||
@public
|
||||
int _no;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation Student
|
||||
|
||||
@end
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
Person *p = [[Person alloc] init];
|
||||
NSLog(@"%zd", class_getInstanceSize([Person class])); // 16
|
||||
NSLog(@"%zd", malloc_size((__bridge const void *)p)); // 16
|
||||
Student *st = [[Student alloc] init];
|
||||
NSLog(@"%zd", class_getInstanceSize([Student class])); // 16
|
||||
NSLog(@"%zd", malloc_size((__bridge const void *)st)); // 16
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
采用指令 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp` 转换为 c++ 代码
|
||||
|
||||
```c++
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
};
|
||||
|
||||
struct Person_IMPL {
|
||||
struct NSObject_IMPL NSObject_IVARS;
|
||||
int _age;
|
||||
};
|
||||
|
||||
struct Student_IMPL {
|
||||
struct Person_IMPL Person_IVARS;
|
||||
int _no;
|
||||
};
|
||||
```
|
||||
|
||||
为什么 `class_getInstanceSize([Person class])` 也是16,不是8+4吗?因为存在内存对齐,结构体的大小必须是最大成员大小的倍数(Person 中也就是8的倍数)
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/StudentClassExtendsFromPersonClass.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
Demo4
|
||||
|
||||
```objective-c
|
||||
@interface Person : NSObject
|
||||
{
|
||||
@public
|
||||
int _age;
|
||||
int _no;
|
||||
int _height;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation Person
|
||||
@end
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
Person *p = [[Person alloc] init];
|
||||
NSLog(@"%zd", class_getInstanceSize([Person class])); // 24
|
||||
NSLog(@"%zd", malloc_size((__bridge const void *)p)); // 32
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
采用指令 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp` 转换为 c++ 代码
|
||||
|
||||
```c++
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
};
|
||||
struct Person_IMPL {
|
||||
struct NSObject_IMPL NSObject_IVARS;
|
||||
int _age;
|
||||
int _no;
|
||||
int _height;
|
||||
};
|
||||
// ->
|
||||
struct Person_IMPL {
|
||||
Class isa;
|
||||
int _age;
|
||||
int _no;
|
||||
int _height;
|
||||
};
|
||||
```
|
||||
|
||||
2个问题:
|
||||
|
||||
- `class_getInstanceSize` 不是一个 isa 8 + 3个 Int 4 = 8 + 3 * 4 = 20吗?查看源码知道 `class_getInstanceSize` 返回的是内存对齐后的成员变量内存大小
|
||||
- `malloc_size` 为什么是32?只需要24字节,为什么分配32?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 三、研究下对象在内存中如何存储?
|
||||
|
||||
```objective-c
|
||||
Person *p1 = [Person new]
|
||||
```
|
||||
|
||||
@@ -25,10 +269,16 @@ Person *p1 = [Person new]
|
||||
new底层做的事情:
|
||||
|
||||
* 在堆内存中申请1块合适大小的空间
|
||||
* 在这块内存上根据类模版创建对象。类模版中定义了什么属性就依次把这些属性声明在对象中;对象中还存在一个属性叫做**isa**,是一个指针,指向对象所属的类在代码段中地址
|
||||
* 初始化对象的属性。这里初始化有几个原则:a、如果属性的数据类型是基本数据类型则赋值为0;b、如果属性的数据类型是C语言的指针类型则赋值为NULL;c、如果属性的数据类型为OC的指针类型则赋值为nil。
|
||||
* 在这块内存上根据类模版创建对象。类模版中定义了什么属性就依次把这些属性声明在对象中;对象中还存在一个属性叫做 **isa**,是一个指针,指向对象所属的类在代码段中地址
|
||||
* 初始化对象的属性。这里初始化有几个原则:
|
||||
* 如果属性的数据类型是基本数据类型则赋值为 0
|
||||
* 如果属性的数据类型是 C 语言的指针类型则赋值为 NULL
|
||||
* 如果属性的数据类型为 OC 的指针类型则赋值为 nil
|
||||
|
||||
* 返回堆空间上对象的地址
|
||||
|
||||
|
||||
|
||||
注意:
|
||||
|
||||
* 对象只有属性,没有方法。包括类本身的属性和一个指向代码段中的类isa指针
|
||||
@@ -78,7 +328,7 @@ int main(int argc, const char * argv[]) {
|
||||
}
|
||||
```
|
||||
|
||||
`Person *p1 = [Person new];` 这句代码在内存分配原理如下图所示
|
||||
`Person *p1 = [Person new];` 这句代码在内存分配原理如下图所示
|
||||
|
||||

|
||||
|
||||
@@ -89,7 +339,26 @@ int main(int argc, const char * argv[]) {
|
||||
|
||||
**可以 看到Person类的3个对象p1、p2、p3的isa的值相同。**
|
||||
|
||||
## 三、一个对象占用多少内存空间?
|
||||
|
||||
|
||||
## 四、一个对象占用多少内存空间?
|
||||
|
||||
Demo
|
||||
|
||||
```objective-c
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
NSObject *obj = [[NSObject alloc] init];
|
||||
// 获取类的实例对象的成员变量所占用内存大小
|
||||
NSLog(@"%zd", class_getInstanceSize([NSObject class])); // 8
|
||||
// 获取 obj 指针,所指向内存大小
|
||||
NSLog(@"%zd", malloc_size((__bridge const void *)obj)); // 16
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
为什么一个是8,一个是16?查看 objc 源代码可以看到:
|
||||
|
||||
```objectivec
|
||||
size_t class_getInstanceSize(Class cls)
|
||||
@@ -161,38 +430,164 @@ size_t instanceSize(size_t extraBytes) {
|
||||
}
|
||||
```
|
||||
|
||||
用2种方式获取:
|
||||
`alloc` 本质上调用的就是 `_objc_rootAllocWithZone` ,继续查看源码
|
||||
|
||||
- class_getInstanceSize([NSObject class]):8。返回实例对象的成员变量所占用的内存大小,一个空对象,只有 isa 指针,所以只有8字节
|
||||
- malloc_size((__bridge const void *)obj):16。Apple 规定,对象至少16个字节。但是只有一个 isa,所以只占用8个字节。
|
||||
内存对齐:结构体的最终大小必须是最大成员的倍数。比如
|
||||
```c++
|
||||
// NSObject.mm
|
||||
id
|
||||
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
|
||||
{
|
||||
id obj;
|
||||
|
||||
```c
|
||||
struct Person_IMPL {
|
||||
struct NSObject_IMPL NSObject_IVARS; // 8字节
|
||||
int age; // 4字节
|
||||
#if __OBJC2__
|
||||
// allocWithZone under __OBJC2__ ignores the zone parameter
|
||||
(void)zone;
|
||||
obj = class_createInstance(cls, 0);
|
||||
#else
|
||||
if (!zone) {
|
||||
obj = class_createInstance(cls, 0);
|
||||
}
|
||||
else {
|
||||
obj = class_createInstanceFromZone(cls, 0, zone);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
|
||||
return obj;
|
||||
}
|
||||
```
|
||||
|
||||
继续调用的是 `class_createInstance`
|
||||
|
||||
```c++
|
||||
// objc-runtime-new.mm
|
||||
id
|
||||
class_createInstance(Class cls, size_t extraBytes)
|
||||
{
|
||||
return _class_createInstanceFromZone(cls, extraBytes, nil);
|
||||
}
|
||||
|
||||
static __attribute__((always_inline))
|
||||
id
|
||||
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
|
||||
bool cxxConstruct = true,
|
||||
size_t *outAllocatedSize = nil)
|
||||
{
|
||||
if (!cls) return nil;
|
||||
|
||||
assert(cls->isRealized());
|
||||
|
||||
// Read class's info bits all at once for performance
|
||||
bool hasCxxCtor = cls->hasCxxCtor();
|
||||
bool hasCxxDtor = cls->hasCxxDtor();
|
||||
bool fast = cls->canAllocNonpointer();
|
||||
|
||||
size_t size = cls->instanceSize(extraBytes);
|
||||
if (outAllocatedSize) *outAllocatedSize = size;
|
||||
|
||||
id obj;
|
||||
if (!zone && fast) {
|
||||
obj = (id)calloc(1, size);
|
||||
if (!obj) return nil;
|
||||
obj->initInstanceIsa(cls, hasCxxDtor);
|
||||
}
|
||||
else {
|
||||
if (zone) {
|
||||
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
|
||||
} else {
|
||||
obj = (id)calloc(1, size);
|
||||
}
|
||||
if (!obj) return nil;
|
||||
|
||||
// Use raw pointer isa on the assumption that they might be
|
||||
// doing something weird with the zone or RR.
|
||||
obj->initIsa(cls);
|
||||
}
|
||||
|
||||
if (cxxConstruct && hasCxxCtor) {
|
||||
obj = _objc_constructOrFree(obj, cls);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
```
|
||||
|
||||
可以看到调用的是 c 的 `obj = (id)calloc(1, size);`,其中 size 是前面计算好的,继续看看这个 size 的计算 `size_t size = cls->instanceSize(extraBytes);`
|
||||
|
||||
```c++
|
||||
// objc-runtime-new.h
|
||||
size_t instanceSize(size_t extraBytes) {
|
||||
size_t size = alignedInstanceSize() + extraBytes;
|
||||
// CF requires all objects be at least 16 bytes.
|
||||
if (size < 16) size = 16;
|
||||
return size;
|
||||
}
|
||||
```
|
||||
|
||||
可以看到计算好 size 之后有个判断,如果小于16,则赋值为16,也就是最小为16(`CF requires all objects be at least 16 bytes`)。
|
||||
|
||||
结论:我们用2种方式获取内存大小,其中
|
||||
|
||||
- `class_getInstanceSize([NSObject class])` :8,返回实例对象内存对齐后的的成员变量所占用的内存大小(即代码注释的 `Class's ivar size rounded up to a pointer-size boundary.` ),一个空对象,只有 isa 指针,所以只有8字节。可以理解为 **创建一个对象,至少需要多少内存**
|
||||
- `malloc_size((__bridge const void *)obj)` :16,Apple 规定,对象至少16个字节。但是只有一个 isa,所以只占用8个字节。
|
||||
内存对齐:结构体的最终大小必须是最大成员的倍数。可以理解为**创建一个对象,实际上分配了多少内存**
|
||||
|
||||
- 系统分配了16个字节给 NSObject 对象(通过 malloc_size 函数获得)
|
||||
- 但 NSObject 对象内部只使用了8个字节的空间(64位环境下,通过 class_getInstanceSize 函数获得)
|
||||
|
||||
|
||||
|
||||
## 五、属性和方法
|
||||
|
||||
```objective-c
|
||||
@interface Person : NSObject
|
||||
{
|
||||
@public
|
||||
int _age;
|
||||
}
|
||||
@property (nonatomic, assign) NSInteger height;
|
||||
@end
|
||||
```
|
||||
|
||||
采用指令 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp`
|
||||
|
||||
```c++
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
};
|
||||
|
||||
struct Person_IMPL {
|
||||
struct NSObject_IMPL NSObject_IVARS;
|
||||
int _age;
|
||||
NSInteger _height;
|
||||
};
|
||||
```
|
||||
|
||||
8*2=16字节
|
||||
我们知道 `@property` 的本质是生成一个带下划线的 ivar,即 `_height`,还有 height 的 getter、setter 方法。为什么在结构体里没有看到方法?因为对象可以存在多个,这些方法的实现需要公用,没必要每个对象里都保存一份。
|
||||
|
||||
## 四、类继承的本质
|
||||
|
||||
写一个最基础的类
|
||||
|
||||
## 五、类继承的本质
|
||||
|
||||
QA: 结构体计算大小为什么需要内存对齐?
|
||||
|
||||
iOS 分配内存,为什么需要内存对齐?libmalloc 可以看到至少是16的倍数。
|
||||
|
||||
写一个最基础的 Person 类
|
||||
|
||||
```objectivec
|
||||
@interface Person:NSObject
|
||||
@interface Person : NSObject
|
||||
@end
|
||||
|
||||
@implementation Person
|
||||
@end
|
||||
```
|
||||
|
||||
clang 转为 c 代码看看, `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp`
|
||||
clang 转为 c 代码看看,因为同样的代码经过 clang 后转成 c/c++ 后,不同平台具有不同的实现,所以为了精确研究 iOS,最好指明 arm64 架构后再研究,具体指令为:`xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp`
|
||||
|
||||
```c
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
Class isa;
|
||||
}
|
||||
|
||||
struct Person_IMPL {
|
||||
@@ -200,20 +595,81 @@ struct Person_IMPL {
|
||||
};
|
||||
```
|
||||
|
||||
如果创建一个继承自 Person 的 Student 类呢
|
||||
如果给 Person 增加属性
|
||||
|
||||
```objective-c
|
||||
@interface Person : NSObject
|
||||
@property (nonatomic, assign) double height;
|
||||
@property (nonatomic, assign) double weight;
|
||||
@property (nonatomic, assign) int salary;
|
||||
- (void)test;
|
||||
@end
|
||||
|
||||
@implementation Person
|
||||
@end
|
||||
```
|
||||
|
||||
创建一个继承自 Person 的 Student 类
|
||||
|
||||
```objective-c
|
||||
@interface Student : Person
|
||||
@property (nonatomic, assign) NSInteger score;
|
||||
- (void)test;
|
||||
@end
|
||||
|
||||
@implementation Student
|
||||
@end
|
||||
```
|
||||
|
||||
利用 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc Person.m -o Person-arm64.cpp` 转换为 c++,将主要的摘出来
|
||||
|
||||
```c++
|
||||
struct NSObject_IMPL {
|
||||
Class isa;
|
||||
};
|
||||
|
||||
struct Person_IMPL {
|
||||
struct NSObject_IMPL NSObject_IVARS;
|
||||
int _salary;
|
||||
double _height;
|
||||
double _weight;
|
||||
};
|
||||
|
||||
```c
|
||||
struct Student_IMPL {
|
||||
struct Person_IMPL Person_IVARS;
|
||||
NSInteger _age;
|
||||
NSString *_name;
|
||||
struct Person_IMPL Person_IVARS;
|
||||
NSInteger _score;
|
||||
};
|
||||
```
|
||||
|
||||
首先我们可以知道一个 OC 类的属性可以写多种数据类型,那么大概率是 C 结构体实现。用 clang 转为 c 可以得到印证。另外存在类的继承关系的时候,子类结构体中第一个信息是父类结构体对象。其次是当前子类自己的信息。根节点一定是 NSObject_IMPL 结构体,且其中只有 `Class isa`
|
||||
结构体 `Person_IMPL` 等价于
|
||||
|
||||
```c++
|
||||
struct Person_IMPL {
|
||||
Class isa;
|
||||
int _salary;
|
||||
double _height;
|
||||
double _weight;
|
||||
};
|
||||
```
|
||||
|
||||
结构体 `Student_IMPL` 等价于
|
||||
|
||||
```c++
|
||||
struct Student_IMPL {
|
||||
Class isa;
|
||||
int _salary;
|
||||
double _height;
|
||||
double _weight;
|
||||
NSInteger _score;
|
||||
};
|
||||
```
|
||||
|
||||
首先我们可以知道一个 OC 类的属性可以写多种数据类型,那么大概率是 C 结构体实现。用 clang 转为 c 可以得到印证。另外存在类的继承关系的时候:**子类结构体中第一个信息是父类结构体对象;其次是当前子类自己的信息;根节点一定是 NSObject_IMPL 结构体;且其中只有 `Class isa`**。也就是说,**一个实例对象,内部的第一个成员就是 isa 指针,其次是父类属性,最后的自己的属性。且 isa 指针地址就是当前实例对象的地址**。
|
||||
|
||||
|
||||
|
||||
观察 clang 转换后的 c 代码,发现 property 没有看到 setter、getter 方法。为什么这么设计?
|
||||
方法不会存储到实例对象中去的。因为方法在各个对象中是通用的,方法存储在类对象的方法列表中。
|
||||
**方法不会存储到实例对象中去的。因为方法在各个对象中是通用的,方法存储在类对象的方法列表中。**
|
||||
|
||||
```objectivec
|
||||
@interface Person:NSObject
|
||||
@@ -245,10 +701,15 @@ NSLog(@"%zd", sizeof(person)); // 24,这个数值代表我们这个类,这
|
||||
NSLog(@"%zd", malloc_size((__bridge const void *)p)); // 32。iOS 系统会做优化,比如为了加速访问速度,会按照16的倍数进行分配。
|
||||
```
|
||||
|
||||
`class_getInstanceSize`这个数值代表我们这个类,这个结构体,创建出来至少只需要24字节. `malloc_size` iOS 系统会做优化,比如为了加速访问速度,会按照16的倍数进行分配。
|
||||
`class_getInstanceSize`这个数值代表我们这个类,这个结构体,创建出来至少只需要24字节,`malloc_size` iOS 系统会做优化,比如为了加速访问速度,会按照16的倍数进行分配。
|
||||
|
||||
iOS 中,系统分配内存,都是16的倍数。pageSize?系统在分配内存的时候也存在内存对齐。
|
||||
GUN 都存在内存对齐这个概念。
|
||||
sizeof 是运算符。
|
||||
`sizeof` 本质是运算符。在 Xcode 编译后就替换为真正的值。通过指令 `xcrun --sdk iphoneos clang -arch arm64 -S -emit-llvm ViewController.m -o ViewController.ll` 查看 IR。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/XcoedeViewSizeOfViaAssembly.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
实例对象:
|
||||
类对象:isa、superclass、属性信息、对象方法信息、协议信息、成员变量信息...
|
||||
@@ -469,7 +930,10 @@ class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
|
||||
class_rw_t *personMetaClassData = personClass->metaClass()->data();
|
||||
```
|
||||
|
||||
## 五、 内存对齐
|
||||
|
||||
|
||||
## 六、 内存对齐
|
||||
|
||||
内存对齐是指数据在内存中存储时按照一定规则对齐到特定的地址上。在 iOS 开发中,内存对齐是为了提高内存访问的效率和性能。内存对齐的原因主要包括以下几点:
|
||||
1. 提高访问速度:内存对齐可以使数据在内存中的存储更加高效,因为大部分计算机体系结构都要求数据按照特定的边界对齐,这样可以减少内存访问的次数,提高访问速度。CPU访问非对齐的内存时需要进行多次拼接。
|
||||
如下图,比如需要读取从[2, 5]的内存,需要分别读取两次,然后还需要做位移的运算,最后才能得到需要的数据。这中间的损耗就会影响访问速度。
|
||||
@@ -542,7 +1006,7 @@ NSLog(@"%zd", malloc_size(temp));
|
||||
|
||||
可以看到 malloc 申请了4个字节,但是打印却看到16个字节。
|
||||
|
||||
查看 libmalloc源码也可以出来分配内存最小是以16的倍数为基准进行分配的。
|
||||
查看 libmalloc 源码也可以出来分配内存最小是以16的倍数为基准进行分配的。
|
||||
|
||||
```c
|
||||
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
|
||||
@@ -552,6 +1016,141 @@ NSLog(@"%zd", malloc_size(temp));
|
||||
|
||||
成员变量占用8字节对齐,每个对象的第一个都是 isa 指针,必须要占用8字节。举例一个极端 case,假设 n 个对象,其中 m 个对象没有成员变量,只有 isa 指针占用8字节,其中的 n-m个对象既有 isa 指针,又有成员变量。每个类交错排列,那么 CPU 在访问对象的时候会耗费大量时间去识别具体的对象。很多时候会取舍,这个 case 就是时间换空间。以16字节对齐,会加快访问速度(参考链表和数组的设计)
|
||||
|
||||
上述是 Apple 官方的角度出发探究的,其他系统,比如 Linux 也是存在内存对齐的。由于 Linux 也是采用 GNU 的东西,所以探索下 GNU 下 glibc malloc 的实现。从[这里](https://ftp.gnu.org/gnu/glibc/)下载 glibc 源码。然后拖到 Xcode 中查看
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/GlibcInXcodeProject.png" style="zoom:25%">
|
||||
|
||||
可以看到 GNU 源码里面,内存对齐 `MALLOC_ALIGNMENT`
|
||||
在 i386 里面是16,在非 i386 里面有个判断
|
||||
```c
|
||||
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
|
||||
? __alignof__ (long double) : 2 * SIZE_SZ)
|
||||
```
|
||||
三目运算符的后2个结果分别是 `__alignof__ (long double)` 和 `2 * SIZE_SZ`。其中 `SIZE_SZ` 又是一个宏定义,等价于 `(sizeof (INTERNAL_SIZE_T))`,即 `2*sizeof(size_t)`
|
||||
```c
|
||||
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))
|
||||
# define INTERNAL_SIZE_T size_t
|
||||
```
|
||||
在 Xcode 打印输出, `__alignof__ (long double)` 为16,`sizeof(size_t)` 为8,即 `2 * SIZE_SZ` = 16,所以不管怎么看,在 GUN 里面内存对齐一定都是16.
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/GLibcMallocAlignment.png" style="zoom:25%">
|
||||
Todo: 研究探索 libmalloc 源码
|
||||
|
||||
|
||||
Todo: 研究探索 glibc 源代码,分析内存对齐、内存分配的原理
|
||||
|
||||
## 七、class 对象
|
||||
|
||||
```objective-c
|
||||
// instance 对象,实例对象
|
||||
NSObject *obj1 = [[NSObject alloc] init];
|
||||
NSObject *obj2 = [[NSObject alloc] init];
|
||||
// class 对象,类对象
|
||||
Class cls1 = [obj1 class];
|
||||
Class cls2 = [obj2 class];
|
||||
Class cls3 = object_getClass(obj1);
|
||||
Class cls4 = object_getClass(obj2);
|
||||
Class cls5 = [NSObject class];
|
||||
NSLog(@"%p %p", obj1, obj2); // 0x600000004040 0x600000004050
|
||||
NSLog(@"%p %p %p %p %p", cls1, cls2, cls3, cls4, cls5); // 0x7ff85ca74270 0x7ff85ca74270 0x7ff85ca74270 0x7ff85ca74270 0x7ff85ca74270
|
||||
```
|
||||
|
||||
- cls1...cls5 都是 NSObject 的 class 对象,也就是类对象。
|
||||
- 它都是同一个对象,每个类在内存中有且只有一个 class 对象
|
||||
|
||||
|
||||
|
||||
Class 对象在内存中存储的信息主要包括:
|
||||
|
||||
- isa 指针
|
||||
- superclass 指针
|
||||
- 类的属性信息(@property)、类的对象方法信息(instance method)
|
||||
- 类的协议信息(@protocol、)类的成员变量信息(ivars)
|
||||
|
||||
|
||||
|
||||
## 八、元类对象
|
||||
|
||||
Objective-C 中对象分维三类:
|
||||
|
||||
- instance 对象,实例对象。例如 `NSObject *obj1 = [[NSObject alloc] init];`
|
||||
- class 对象,类对象。例如 `Class cls1 = [obj1 class];`
|
||||
- 元类对象(meta-class)。例如 `Class metaClass = object_getClass(cls1);`
|
||||
|
||||
如何获取元类对象?
|
||||
|
||||
利用 runtime `object_getClass`API,传入类对象获取。例如 `Class objectMetaClass = object_getClass([NSObject class])`
|
||||
|
||||
不可以通过2次调用 class 方法获取 meta-class 对象。调用 class 方法只可以获取到 class 对象。
|
||||
|
||||
- 每个类在 内存中有且只有一个 meta-class 对象
|
||||
|
||||
- meta-class 对象和 Class 对象的内存结构是一样的(都是 Class),但是用途不一样,在内存中存储的信息主要包括:
|
||||
|
||||
- isa 指针
|
||||
- superclass 指针
|
||||
- 类的类方法信息(class method)
|
||||
|
||||
如何判断是否是元类对象 `class_isMetaClass()`
|
||||
|
||||
Demo:
|
||||
|
||||
```objective-c
|
||||
// instance 对象,实例对象
|
||||
NSObject *obj1 = [[NSObject alloc] init];
|
||||
NSObject *obj2 = [[NSObject alloc] init];
|
||||
// class 对象,类对象
|
||||
// class 方法返回的就是类对象
|
||||
Class cls1 = [obj1 class];
|
||||
Class cls2 = [obj2 class];
|
||||
Class cls3 = object_getClass(obj1);
|
||||
Class cls4 = object_getClass(obj2);
|
||||
Class cls5 = [NSObject class];
|
||||
NSLog(@"%p %p", obj1, obj2);
|
||||
NSLog(@"%p %p %p %p %p", cls1, cls2, cls3, cls4, cls5);
|
||||
|
||||
// 将类对象当作参数传入,获取元类对象(meta-class)
|
||||
Class metaClass = object_getClass(cls1);
|
||||
Class metaClass2 = [cls2 class];
|
||||
NSLog(@"%p %p", metaClass, metaClass2);
|
||||
|
||||
BOOL isMetaClass = class_isMetaClass(metaClass);
|
||||
NSLog(@"%@", isMetaClass ? @"是元类对象" : @"不是元类对象");
|
||||
|
||||
isMetaClass = class_isMetaClass(metaClass2);
|
||||
NSLog(@"%@", isMetaClass ? @"是元类对象" : @"不是元类对象");
|
||||
|
||||
// console
|
||||
0x60000000c030 0x60000000c040
|
||||
0x7ff85ec7c270 0x7ff85ec7c270 0x7ff85ec7c270 0x7ff85ec7c270 0x7ff85ec7c270
|
||||
0x7ff85ec7c220 0x7ff85ec7c270
|
||||
是元类对象
|
||||
不是元类对象
|
||||
```
|
||||
|
||||
objc 源代码中:
|
||||
|
||||
```c++
|
||||
Class object_getClass(id obj)
|
||||
{
|
||||
// 传入的如果是实例对象,则返回 class 类对象
|
||||
// 传入的如果是 class 类对象,则返回 meta-class 元类对象
|
||||
// 传入的如果是 meta-class 元类对象,返回 NSObject(基类)的 meta-class 元类对象
|
||||
if (obj) return obj->getIsa();
|
||||
else return Nil;
|
||||
}
|
||||
|
||||
Class objc_getClass(const char *aClassName)
|
||||
{
|
||||
if (!aClassName) return Nil;
|
||||
|
||||
// NO unconnected, YES class handler
|
||||
return look_up_class(aClassName, NO, YES);
|
||||
}
|
||||
```
|
||||
|
||||
`object_getClass`:
|
||||
|
||||
- 传入的如果是实例对象,则返回 class 类对象
|
||||
- 传入的如果是 class 类对象,则返回 meta-class 元类对象
|
||||
- 传入的如果是 meta-class 元类对象,返回 NSObject(基类)的 meta-class 元类对象
|
||||
|
||||
`-(Class)class`、`+(Class)class`:返回的是类对象
|
||||
|
||||
|
||||
Reference in New Issue
Block a user