OC底层原理(一):alloc探索

214 阅读4分钟

OC对象的指针与内存地址关系

新建一个OC的工程,再新建一个NSObjectPCJPerson;打印一下这个三个对象的对象地址指针地址指针变量地址

PCJPerson *p1 = [PCJPerson alloc];
PCJPerson *p2 = [p1 init];    
PCJPerson *p3 = [p1 init];   
PCJPerson *p4 = [[PCJPerson alloc]init];       
NSLog(@"%@-%p-%p",p1,p1,&p1);   
NSLog(@"%@-%p-%p",p2,p2,&p2);   
NSLog(@"%@-%p-%p",p3,p3,&p3);   
NSLog(@"%@-%p-%p",p4,p4,&p4);

由于p1、p2、p3的对象地址、指针地址一致,证明这个是指向同一个对象(堆内存);但是指针变量不同那是因为他们是不同的局部变量,指向不同的地址(栈内存)。p4的dui x

补充点

  • (对象地址指针地址内存16byte)、(指针变量内存8byte)。
  • :栈是向低地址扩展的数据结构,
  • :堆是向高地址扩展的数据结构,是不连续的内存区域。

结论

  • 1、alloc让对象有了内存空间,有了指针指向。

  • 2、init后内存没有变化,证明init没有对指针做什么操作,并不具备开辟堆内存空间。

三种底层探索分析方式

断点进入alloc方法

在需要调试的alloc地方断点,当代码执行到断点处,按住control + step into(单步)进入即可进入到汇编代码部分,如图:

符号断点

从上一步断点alloc进入得知之后进入objc_alloc方法,把objc_alloc添加符号断点如下:

汇编方式

当断点停留在alloc时,选择Xcode -> Debug -> Debug Workflow -> Always Show Disassembly进入汇编找到objc_alloc方法,操作如下图:

源码调试探索

根据objc4源码编译工程,断点进入alloc调试,流程如下:

  • 1.[Person alloc]通过消息发送调用obc_alloccls终端打印自定义类Person
// Calls [cls alloc].
id objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
    1. 第一次进入callAlloc方法,走objc_msgSend消息发送方法alloc
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id 
callAlloc(Class cls, bool checkNil, bool allocWithZone = false) 
{

    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }

    // 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));
}
  • 3.由于Person没有alloc类方法,慢速查找NSObjectalloc类方法,self终端打印的是我们自定义的类Person
@implementation NSObject
···
+ (id)alloc {
    return _objc_rootAlloc(self);
    // [CJPerson alloc]终端 po self => Person
}
···
@end
  • 4.进入_objc_rootAlloc方法,id就是objc_object的结构指针
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil]
id _objc_rootAlloc(Class cls) {
      return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
  • 5.接着进入callAlloc方法,
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
  • 6.进入callAlloc方法,通过hasCusomAWZ()判断classsuperclass是否有自定义的 alloc/allocWithZone方法,有走消息转发objc_msgSend;无走方法_objc_rootAllocWithZone
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.

static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone = false) {

    if (slowpath(checkNil && !cls)) return nil;
    // class or superclass has default alloc/allocWithZone: implementation
    // Note this is is stored in the metaclass.
    // 通过FAST_CACHE_HAS_DEFAULT_AWZ标识位判断是类或者父类否有自定义的alloc方法
    // alloc/allocWithZone的类方法是存储在metaClass元类
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // 无自定义的alloc
        return _objc_rootAllocWithZone(cls, nil);
    }

    // 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));
}
  • 7.进入_objc_rootAllocWithZone方法。
NEVER_INLINE id _objc_rootAllocWithZone(Class cls, objc_zone_t zone __unused) {
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}
  • 8.进入_class_createInstanceFromZone方法。
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
************************************************************************ ** **/**

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 SUPPORT_ZONES
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
#endif
        obj = (id)calloc(1, size);
#if SUPPORT_ZONES
    }
#endif
    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);
}
  • 9.进入alloc核心三步1-instanceSize,计算开辟内存空间所需的大小
struct objc_class: objc_object {
 ...
    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;
    }
 ...
}
  • 10.进入alloc核心三步2-calloc,开辟内存,返回地址指针
// C 开辟内存方法
obj = (id)calloc(1, size);
  • 11 .进入alloc核心三步3-initInstanceIsa,初始化指针,和类关联起来
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {

    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());
    
    initIsa(cls, true, hasCxxDtor);
}

附上调用流程图如下:

总结

alloc 主要作用就是向系统申请开辟内存,通过isa指针与类进行关联。