iOS启动流程
- dyld start-----libsystem-----libdispatch----GCD环境准备------libObjc(_objc_init)
源码下载
alloc方法调试
方法1:下符号断点,
首先断点在alloc行, 同时下符号断点alloc(开始设置为disable,否则会先断住NSObject的alloc),commond + setp into调试获取
结论:p1、p2、p3指向了同一片内存空间,它们自身指针地址不同,但在栈中是一片连续的内存空间
方法2:通过汇编调试
开启汇编调试模式
control + step into(单步调试),确定下步调用函数objc_alloc
下符号断点
再次运行程序,又可获得一个符号断点_objc_rootAllocWithZone,依次类推获取整个调用流程
源码分析
alloc入口
+ (id)alloc {
return _objc_rootAlloc(self);
}
点击_objc_rootAlloc
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
点击callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ //判断是不是 objc2.0版本
if (slowpath(checkNil && !cls)) return nil;
//判断该类是否实现自自定义的 +allocWithZone,没有则进入if条件句
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));
}
点击_objc_rootAllocWithZone
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
点击_class_createInstanceFromZone,进入核心代码,三处核心点
- 对象的大小由成员变量决定,开辟内存时,
- 8字节对齐,取8的整数倍(原因:没有任何变量时,只存在isa,8个字节),cpu读取速度变快了,以空间换取时间
- 干净内存ro data()->ro()->instanceSize
- 内存对齐,即不浪费8字节空间,如int占4字节、char占1字节,则它们会被分配到同一8字节空间内
- po就是以当前对象的description方法打印,浮点型对象没有description方法,打印不出来,可以用格式化e -f f -- 对象 打印
static ALWAYS_INLINE id
_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:初始化指针,和类关联
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);//初始化isa指针和类关联
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
alloc流程总结
- 编译器优化过程中,callAlloc可能会被优化掉 例如:寄存处X0 64位,W0 32位,存储字节数小的数据,编译器会优化为w寄存器
- createInstance主要流程,根据变量大小开辟内存空间 -> 实例化isa(isa与类的绑定)
- alloc流程指向_objc_rootAlloc或者objc_rootAlloc
- callAlloc走两次原因
- 最终结论:程序在LLVM编译阶段就已经完成了
objc_alloc的替换,这里不止替换掉alloc,还有很多函数release、retain、autorelease等等,至于为什么要hook掉这些函数,推测系统对对象的创建、释放做了很多监控。 - 流程总结:
alloc等一些方法在编译阶段LLVM会对alloc方法进行Hook,此函数会被替换成objc_alloc函数,这样在运行时声明一个对象LGPerson并且为其开辟内存空间的时候调用alloc函数,第一响应方法为objc_alloc,接着会进入callAlloc函数,第一次永远不满足此判断条件fastpath(!cls->ISA()->hasCustomAWZ())会触发((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc))objc_msgSend消息转发,为LGPerson对象发送了alloc消息,这个时候alloc函数才会真正被调用,然后进入_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone此方法里面做三件事:字节对齐、开辟内存空间、与对象绑定
init
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
- init方法返回的是对象本身
- init可以提供给开发者更多的自由去自定义 ,通过id实现强转,返回我们需要的类型
new
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
源码显示 走了callAlloc的方法流程,然后走了init方法 ,所以 new看做是alloc + init