人狠话不多,直接上干货。这是第一篇,之后还会持续更新,当作自己学习的笔记,也同时分享给大家,希望帮助更多人。
首先,我们来思考,下面这段代码的输出是否相同。答案很明显,p1、p2、p3是指向相同的对象,但是指针地址是不同的。那么问题来了,为啥呢。保留这个疑问,我们来探索alloc和init到底在底层到底做了什么。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
FFPerson *p1 = [FFPerson alloc];
FFPerson *p2 = [p1 init];
FFPerson *p3 = [p1 init];
FFLog(@"p1 --- %@ - %p",p1,&p1);
FFLog(@"p2 --- %@ - %p",p2,&p2);
FFLog(@"p3 --- %@ - %p",p3,&p3);
}
p1 --- <FFPerson: 0x2818cb7e0> - 0x16f501c68
p2 --- <FFPerson: 0x2818cb7e0> - 0x16f501c60
p3 --- <FFPerson: 0x2818cb7e0> - 0x16f501c58
我们在alloc这里打断点,这里要用真机测试,为了arm64架构。断点到这后,按住control这个调试按钮会变,点进去,好像要点两下,就会进入一段汇编,圈中的就是底层的实现入口了。
接下来我们就进入源码解析了,苹果这个源码是开源的,苹果开源库,从这里下载objc4的最新源码。直接全局搜索objc_alloc,我们发现会有很多,下面那张图展示为实现部分。
一步步找下去,_objc_rootAllocWithZone --> _class_createInstanceFromZone。这里大致内容就是创建对象,并且分配了大小,初始化isa。这里有个算法,实例化的对象的大小为8的倍数。
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;
}
// 初始化isa
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_t instanceSize(size_t extraBytes) {
// size = 8 + 0
size_t size = alignedInstanceSize() + extraBytes;
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() const {
// 内存对齐,为了读取方便
return word_align(unalignedInstanceSize());
}
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
// 内部含有一个isa
return data()->ro()->instanceSize;
}
// 进行内存对齐,最后为8的倍数
static inline uint32_t word_align(uint32_t x) {
// 7 + 8 = 15
// 0000 1111
// 1111 1000
//&0000 1000
// 0000 0111
return (x + WORD_MASK) & ~WORD_MASK;
}
同理,我们在init那边打断点,发现进入的是objc_retain,这里我们发现,非taggedPointer的,直接引用计数加一,但是对象还是原来的对象,所以为什么p1、p2、p3指向的对象相同。
id
objc_retain(id obj)
{
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain();
}
根据源码分析,实例对象的大小会不等于实际分配的大小,就是因为内存对齐,详情可以看我源码的注释。接着我们来探索calloc底层到底做了什么。这里将移步libmalloc源码库了。
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
// 删除部分不需要的判断代码
// 这里会跳转default_zone_calloc
ptr = zone->calloc(zone, num_items, size);
// 删除部分不需要的判断代码
return ptr;
}
static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
zone = runtime_default_zone();
// nano_calloc
return zone->calloc(zone, num_items, size);
}
static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
size_t total_bytes;
// 删除部分不需要的判断代码
if (total_bytes <= NANO_MAX_SIZE) {
// 这里是核心
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);
void *ptr;
size_t slot_key;
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
// 删除部分不需要的判断代码
return ptr;
}
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
// NANO_REGIME_QUANTA_SIZE 为16
// SHIFT_NANO_QUANTUM 4
// 这里就是左移右移算法,最终为16的倍数
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
对象创建原理已经知道了,接下来就是对象的结构了。下一篇文章将会分析对象的本质,isa的结构,已经对象与isa的关系。