前言
每天都在使用对象,创建对象。但并知道苹果底层是怎样去创建的对象,alloc方法是实现是怎么样。这次决定静下来心来,探究一下苹果的底层代码
目标
- 我们要找到alloc的源码调用顺序是怎么样?
第一步 --- 要先学会调试办法,找到入口,一步步探究下去。
准备工作:我们创建一个新的ios项目,在ViewController.m的viewDidLoad方法中写以下代码(此篇文章所涉及到代码都基于arm64真机进行调试
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
方法-1.我们先通过断点的方式,control+stepInfo 进入方法内部
一步步往下走,又看到一些有意思的函数,dyld_stub_binder 这是干什么得呢,先留在这里后面我百度看看,看名称就是动态库做什么绑定操作,我们继续往下走
然后继续往下走,我们又发现了objc_msgSend
哎呀 sorry,我断点打在了init函数那行,stepInfo进入得是init函数的底层触发,重新在alloc行进行断点
这时候我们看到调用了objc_alloc方法,继续往下走走看
我们又发现了_objc_rootAllocWithZone和obj_msgSend.
方法-2.我们通过汇编进行调试,我们也可以快速定位到了objc_alloc的方法
方法-3.通过增加符号断点,增加一个alloc的符号断点,我们可以看到
第二步 追踪alloc流程
方法掌握了,我们开始探究,首页我们运行一下代码,看一下打印结果
**2021-09-01 10:27:27.374802+0800 001-alloc&init探索[368:122896] <LGPerson: 0x283f04000>-0x283f04000-0x16ced1c08**
**2021-09-01 10:27:33.671104+0800 001-alloc&init探索[368:122896] <LGPerson: 0x283f04000>-0x283f04000-0x16ced1c00**
**2021-09-01 10:28:27.939738+0800 001-alloc&init探索[368:122896] <LGPerson: 0x283f04000>-0x283f04000-0x16ced1bf8**
结论:p1 p2 p3 内存中的对象地址是一样的,但指针地址完全不一样
扩展一下:
1.指针存在stack栈区,对象存在heap堆区 2.栈区由系统申请,开辟地址由大到小,堆区由程序员去申请,开辟地址由小到大。 3.另外我们还有静态变量区,常量字符串区,代码区 4.我们开辟的对象内存地址,其实是虚拟内存地址,并不是真正的内存地址,我们是没有办法知道真正的内存地址。
好了,我们要开始追溯alloc的流程,我们现在定位到了alloc会调用objc_alloc方法,那我们就要进入到读源码的环节[objc4-818.2],苹果开放了蛮多的源码,对于我们学习还是很有帮助的,大家应该去看看源码,学习大神们的思路。
callAlloc方法看看
核心代码内容
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);//我们看这里
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
_class_createInstanceFromZone看看
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);//1.计算申请内存空间
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);//2.开辟空间
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);//3.初始化ISA和cls关联起来
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
看看instanceSize(extraBytes)如何实现
inline size_t instanceSize(size_t extraBytes) const {
//快速计算内存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
//计算类中所有变量需要的内存大小 extraBytes额外字节数一般是0
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//最小返回16字节
if (size < 16) size = 16;
return size;
}
再往里去探究,我们发现了这个方法,就算出对象的size后,做了一个16字节对齐.
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
开辟好了内存后,继续往下但是这里会发现一个问题这里打印的obj的地址和我们之前打印p1 p2的地址格式并不相同,我们先继续往下走进入到initInstanceIsa方法中
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
发现初始化是initIsa方法干的时候,我们就继续跟进去,这个时候我们发现了一个isa_t,这是一个联合体,有属性互斥的特征
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD) //这里是我们重点要看的一个宏定义
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
我们看到了一个宏ISA_BITFIELD,这个isa_t存了比较多重要的信息,我们一起看一下
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ 类信息
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \弱引用
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8 计数器
之后就返回了obj对象了。一个调用的流程就基本走完。后面再对整个流程的知识点独立分析看看 在这个流程中我们遇到了一个问题,通过源码断点,我们发现对象的alloc方法走的是,这个部分是LLVM做了一些优化,苹果的目的应该是对于内存申请做了一些标记和监控。
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
第三步 内存分析(对象大小空间)
LGPerson *person = [LGPerson alloc];
person.name = @"stone";
person.nickName = @"ios";
NSLog(@"对象类型的内存大小--%lu",sizeof(person));
NSLog(@"对象实际的内存大小--%lu",class_getInstanceSize([person class]));
NSLog(@"系统分配的内存大小--%lu",malloc_size((__bridge const void *)(person)));
2021-09-01 16:04:36.477765+0800 001-内存对齐原则[404:180545] 对象类型的内存大小--8
2021-09-01 16:04:36.478278+0800 001-内存对齐原则[404:180545] 对象实际的内存大小--24
2021-09-01 16:04:36.478312+0800 001-内存对齐原则[404:180545] 系统分配的内存大小--32
结论: 1.sizeof 是返回类型的size 例如double占8字节,int4字节,void(*)代表指针占8字节,对象这个类型也是占8个字节 2.class_getInstanceSize 动态计算对象size Person 24个字节 其实isa占8个+name占8个字节+nickName占8个字节 3.malloc_size系统开辟的内存空间,按照16个字节进行对齐,要存下24个字节的person,需要开辟32个字节的空间
不同类型占用内存的情况
未完待续:
第四步 对象本质 nonPoiterIsa