作为一名iOS开发者,alloc对我们来说是一个熟悉又陌生的单词。在代码的世界里,撩动着心弦。像极了公司楼下总擦肩而过的姑娘,温柔并且风情万种。但是你不知道她的名字,年龄,有没有对象...
西楼月下当时见
泪粉偷匀
歌罢还颦
恨隔炉烟看未真
别来楼外垂杨缕
几换青春
倦客红尘
长记楼中粉泪人
嗯,要勇敢。让我们来认识一下这位可爱的姑娘吧。
1. alloc是谁
Girl *girl = [[Girl alloc] init];
girl.name = @"铁锤妹妹";
NSLog(@"girl's name is %@", girl.name);
// girl's name is 铁锤妹妹
这是创建对象最常用的代码。alloc和init总是成对出现。很显然,专一是一种优秀的品质。那不如把init先去掉。
Girl *girl = [Girl alloc];
girl.name = @"铁锤妹妹";
NSLog(@"girl's name is %@", girl.name);
// girl's name is 铁锤妹妹
从打印来看,有没有init好像并不影响对象的创建。我们可以拿到Girl对象并且可以访问对象的属性。
2. alloc做了什么
首先我们先把Always Show Disassembly打开。在Girl *girl = [Girl alloc];处加上一个断点。
可以看到,当我们调用alloc时,会调用objc_alloc函数。
源码总是靠近真相最直接的武器。objc源码。
关于objc源码的编译调试,相关文章很多,也有已编译好的代码。
通过加断点分析,我们可以得到函数的调用顺序如下:
alloc
objc_alloc
callAlloc
[NSObject alloc]
_objc_rootAlloc
callAlloc
_objc_rootAllocWithZone
_class_createInstanceFromZone
到这里我们可以明白,_class_createInstanceFromZone才是alloc真正做的事。
3. _class_createInstanceFromZone
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);
}
// extraBytes == 0, zone == nil
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);
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);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} 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);
}
这里需要注意的是size的计算,cls->instanceSize(extraBytes)是计算创建cls对象所需的字节大小,内部会做字节对齐处理。在64位操作系统是8字节对齐,32位操作系统4字节对齐。并且如果所计算的字节数小于16,会直接返回16字节。
字节对齐的相关代码:
inline size_t instanceSize(size_t extraBytes) const {
// 先从cache中查找,有的话直接返回
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;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
// 字节对齐算法
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
看到这里,我们是不是就可以认为在创建对象进行字节对齐的时候采取的是8字节对齐的方式呢。其实不然,在(id)calloc(1, size)方法中,会重新对内存进行对齐,这里采用16字节对齐的方式。
calloc的实现并不在objc源码中,我们需要去下载libmalloc的源码。
我们可以得到一个结论,那就是alloc在调用的时候就开辟了内存空间,并且返回了对象。那么init又做了什么呢。
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;
}
我们看到,在NSObject类的init方法中,什么都没有做。直接返回obj对象。就是为了给到我们去重写。
alloc方法是创建对象的关键,在底层做了很多的工作。后面我们来探究一下对象的本质又是什么。
待续...