探寻底层原理的三种方法
1.符号断点
先在创建对象方法处添加断点,当程序走到断点位置时,按住control键,点击step into,程序走到了objc_alloc。再依次添加objc_alloc、_objc_rootAllocWithZone符号断点,查看调用过程
2.汇编
打开 Xcode -> Debug -> Debug Workflow -> Always Show Disassembly 开关,
断点会直接进入汇编,快速定位到objc_alloc方法,再结合符号断点调试
3.objc源码分析
查看苹果源码 Apple Open Source、Source Browser (在这两个网站中搜索objc即可)
下载下来的源码是无法直接编译的,这里有KC老师配置好的 可编译的objc源码 ,打开最新的objc4-818.2工程,运行 KCObjcBuid,轻松调试。
源码分析alloc方法调用过程
从创建对象的alloc点进去可以看到alloc方法的实现
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
+ (id)alloc {
return _objc_rootAlloc(self);
}
但是结合断点,发现其实是先调用了objc_alloc方法,再调用的alloc方法
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
而关键方法callAlloc调用了两次,第一次走的是objc_msgSend,第二次走_objc_rootAllocWithZone
static ALWAYS_INLINE id
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));
}
注释:
slowpath(x):一般就是基本流程,不被优化的,执行效率会慢一些
fastpath(x):在编译器优化,更容易被执行
为何会这样呢,搜索源码发现,原来苹果在底层llvm对alloc、allocWithZone、等关键方法进行了一次拦截处理。
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;
}
}
...
}
注释:
sel:方法编号
imp:方法实现首地址
由此,可以总结出alloc底层调用过程
下面再来具体研究核心方法_class_createInstanceFromZone
_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);
}
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.初始化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 计算所需内存大小
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
可以看到,计算内存大小分为有无缓存两种情况,下面以无缓存情况为例,研究下计算内存大小的算法
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) { //WORD_MASK = 7
return (x + WORD_MASK) & ~WORD_MASK;
}
// May be unaligned depending on class's ivars. //内存大小由ivars决定
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
字节对齐算法
(x + WORD_MASK) & ~WORD_MASK -> 8+7 & ~7
0000 1111 15
0000 0111 7
1111 1000 ~7
0000 1000 15 & ~7 -> 8
相当于 15 >> 3 << 3
总结:对象内存大小由成员变量决定,以8字节对齐(取8的整数倍),最少为16字节
isa(结构体指针类型)占8字节
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
为什么最少是16?
没有任何成员变量时,isa已经占了8字节,所以额外开辟空间,以做容错处理
为什么是8字节对齐?
8字节的对象是最多的,CPU以8字节固定长度读取,以空间换时间
BOOL、int、char 等存储8字节浪费,是否可以合并到一起来读取呢?
移步下一篇博客