聊一聊对象(一)
一、相关内容
1.1LLDB 知多少
1.2 读取函数寄存器
register read x0 : 函数的第一个参数
register read x1 : 函数的第二个参数
memory read : 读取内存
二、alloc的流程
案例代码:
@interface Person : NSObject
@property(assign, nonatomic) int age;
@end
Person *p = [Person alloc]; // objc_msgSend(Person, SEL(alloc));
2.1 alloc做了什么:(源码分析)
alloc已经生成了对象,并返回对象 obj
p.age = 10;
NSLog(@"p.age--%ld",(long)p.age);
p.age--:10 // 没有发送init消息,依然可以创建p对象,并进行赋值操作。
// alloc内部实现
+(id)alloc {
return _objc_rootAlloc(self);
}
_objc_rootAlloc(Class cls) {
return callAlloc(cls, false/*checkNil*/,true/*allocWithZone*/)
}
callAlloc(Class cls, bool checkNil, bool allocWithZone = false) {
...
id obj = class_createInstance(cls, 0);
...
return obj;
}
到此为止,alloc完成了创建对象对的过程。
2.2 init的流程(源码分析)
p1 = [p init]; // objc_msgSend(p, SEL(init));
NSLog(@"p1.age--%ld",(long)p1.age);
p1.age--:10
- (id)init {
return _objc_rootInit(self);
}
_objc_rootInit(id obj) {
// In practice ,it will be hard to rely on this function
// Many class do not properly chain -init calls
return obj;
}
2.3 init做了什么:
直接return obj。init相当于一个工厂类,是为了让重写它,实现自己的方法。既然alloc已经生成了对象,是不是我们也可以不写init?不可以。因为Foundation框架在init里做了必要工作。如果没有init,那么你创建就是一个没有任何功能的对象。
三、什么是字节对齐?为什么要字节对齐?
3.1 字节对齐:
iOS在arm中按照8字节对齐,开辟的内存一定是8的倍数。比如我需要9个字节,那我开辟的自己是 16个字节。字节对齐一定是浪费空间的。
3.2 为什么要字节对齐:
因为硬件决定的。有些cpu只能从偶数开始读数据。提高读取效率。
代码对齐的源码:
WORD_MASK = 7;
int word_align(int x) {
// (x + 7) / 8 * 8
// (x + 7) >> 3 << 3
return (x + WORD_MASK) & ~WORD_MASK;
}
字节对齐案例:
@interface Person : NSObject
@property(assign, nonatomic) int age;
@property(assign, nonatomic) double height;
@end
Person *p = [Person alloc];
p.age = 1;
p.height = 1.85;
p共有 24个字节
age : 4个字节
height : 8 个字节
isa : 8个字节
4 + 8 + 8 = 20
因为iOS在arm中按照8字节对齐,所以p供分配了 24个字节
p中存在三个指针:
0x100f4fd90: isa: 8字节 age: 4字节
0x100f4fda0: height: 8字节
16进制代码如下:
0x100f4fd90: c1 11 00 00 01 80 1d 00 00 00 00 01 00 00 00 00
0x100f4fda0: 9a 99 99 99 99 99 fo 3f
打印double地址:
p *(double *)0x100f4fda0
// (double) $1 = 1.8500000000000001
3.3 结构体的大小必须是最大成员大小的倍数
@interface CustomPerson : NSObject{
int _age;
}
@end
@implementation CustomPerson
@end
@interface Student : CustomPerson{
int _no;
}
@end
@implementation Student
@end
Student *student = [[Student alloc] init];
NSLog(@"%zu", class_getInstanceSize([Student class]));
NSLog(@"%zu", malloc_size((__bridge const void *)student));
2020-12-16 15:32:53.592040+0800 16
2020-12-16 15:32:53.592230+0800 16
将student中的_no换成double类型
@interface CustomPerson : NSObject{
int _age;
}
@end
@implementation CustomPerson
@end
@interface Student : CustomPerson{
double _no;
}
@end
@implementation Student
@end
Student *student = [[Student alloc] init];
NSLog(@"%zu", class_getInstanceSize([Student class]));
NSLog(@"%zu", malloc_size((__bridge const void *)student));
2020-12-16 15:34:28.133872+0800 24
2020-12-16 15:34:28.134047+0800 32
将Person 和 Student转换为结构体
// int _no
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
int _age; // 4
}
struct Student_IMPL {
struct Person_IMPL NSObject_IVARS; // 16
int _no; // 4
}
因为 NSObject_IVARS 只使用了 12个字节,所以还剩4个字节,直接将_age赋值过去。所以实例变量的占用内存大小是16,系统分配给对象的内存大小也是16
// double _no
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
int _age; // 4
}
struct Student_IMPL {
struct Person_IMPL NSObject_IVARS; // 16
double _no; // 8
}
因为 NSObject_IVARS 只使用了 12个字节,所以还剩4个字节,剩下的自己不足以分配8。又因为结构体的大小必须是最大成员大小的倍数,所以实例变量的占用内存大小是16 + 8 = 24,系统分配给对象的内存大小是 16 * 2 = 32。
四、什么是对象:
Objective-C对象是结构体
NSObject转化为C/C++代码源码:
struct NSObject_IMPL {
Class isa; // 8个字节
};
因为alloc的时候最小分配16字节,所以系统为一个对象分配了16字节,NSObject对象仅占用8字节。
一个指针占用八个字节
一个空的Objective-C对象被创建出来占用8个字节
struct objc_object {
Class *isa;
} id;
将代码编译成c++代码:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc "Class.m" -o "Class.cpp"
命令 | 解释 |
---|---|
xcrun | Xcoderun |
-sdk | 选择支持平台 |
iphoneos | iphone平台 |
clang | llvm编辑器命令 |
-arch | 架构 |
arm64 | 模拟器(i386) 32bit(arm32) 64bit(arm64) |
-rewrite-objc | 重写 |
-o | 保存到某个文件 |
实例对象里只放成员变量,不放方法。方法是放在类的方法列表里。