iOS开发:alloc、init、new

我们实例化一个NSObject对象,最常见的代码就是

NXPerson *person = [[NXPerson alloc] init];

对于调用alloc/init等方法底层究竟发生了什么,你是否知道呢?今天我们就来一探究竟:

以下调试基于Apple开源的objc4-818.2代码进行。

1.alloc的代码执行流程

1.1.首先在上述代码开始开始位置打第1个断点,然后运行代码使得代码断在这个断点。
1.2.然后点击alloc,跳转到了[NSObject.mm alloc],并在这里设置第2个断点。
1.3.此时我们观察Xcode左侧的调用栈

截屏1.png

仔细看,我们会发现一个奇怪的问题:在main之后,[NSObject.mm alloc]之前,还有两个方法的调用
1.3.1.调用objc_alloc方法:

id objc_alloc(Class cls){
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

1.3.2. objc_alloc的内部调用了callAlloc方法:

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));
}

1.3.3.callAlloc内部通过((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc))调用[NSObject.mm alloc]方法:

+ (id)alloc {
    return _objc_rootAlloc(self);
}

1.3.4. alloc内部调用了_objc_rootAlloc方法:

id _objc_rootAlloc(Class cls){
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

1.3.5. _objc_rootAlloc内部调用callAlloc方法:

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));
}

1.3.6. callAlloc内部调用了_objc_rootAllocWithZone方法,这里明确了objc2忽略了allocWithZone,所以这里以及后续都传的是nil.

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);
}

1.3.7. _objc_rootAllocWithZone内部调用_class_createInstanceFromZone方法:

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 {
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

整个流程一共经历了7步,在第1.3.7步中返回了obj对象。也就是前面的一些步骤只是一些通用接口,根据环境变量等的不同来获得第七步所需要的一些参数,接下来我们探究obj初始化初始化的流程

2.obj初始化流程

2.1.obj初始化流程中,我们看到对其进行了声明, 接下来执行的代码如下,根据上下问参数可以知道zone参数为nil,所以就走到了else分支的obj = (id)calloc(1, size)中,calloc函数是一个分配内存的底层函数,第二个参数是size,表示需要开辟的内存空间的大小,第一个参数是1,表示申请一倍于size的大小。

id obj;
if (zone) {
       obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} 
else {
       obj = (id)calloc(1, size);
}

2.2.size的代码如下,我们根据上下参数可以看到outAllocatedSize参数为nil,所以if条件就可以不看了。那么size = cls->instanceSize(extraBytes),这个就是获取初始化这个对象需要分配的内存大小了。具体怎么计算的,我们后面再继续探究,这里拿到size之后就可以完成对象的初始化了。

size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;

打印下sizeobj,我们发现打印的只有一个地址,没有描述信息呀,我们平时打印个对象,都是这种格式的啊,在继续探索,我们传入的NXPerson类并没有派上用场,也就是他还没有跟类的信息绑定起来。

截屏2.png

2.3.继续往下,断点走到了这里,具体怎么绑定的我们后面在再进行探究。我们接着再打印下obj,到这里我们看到了已经有类名相关的信息了

obj->initInstanceIsa(cls, hasCxxDtor);
截屏3.png

综上所属,类的实例初始化可以分为三步,第一步通过cls->instanceSize获得分配的内存大小;第二步通过calloc分配内存空间;第三步通过obj->initInstanceIsa绑定类信息到实例上。完整的流程可以查看下方的流程图:

截屏NSObject初始化流程.png

3.重点探究

3.1内存大小的计算
调用如下objc_class的方法instanceSize,参数extraBytes = 0,

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;
    }

再继续调用cache_tfastInstanceSize方法,走到这一步size=40,然后再减去FAST_CACHE_ALLOC_DELTA16=0x0008,得到32。值得注意的是align16是表示16字节对齐,算法为(x + size_t(15)) & ~size_t(15).

16位对齐也就是在开辟内存空间的时候,只能开辟16的整数倍大小的内存空间,这样以16字节为基础单位开辟的内存空间,就可以保证在内存连续上的整齐度。采取16字节对齐,是因为OC的对象中,第一位isa指针,是必然存在的,而且它就占了8字节,就算你的对象中没有其他的属性了,那对象也至少要占用8位字节。如果以8位字节对齐的话,如果连续的两块内存都是没有属性的对象,那么它们的内存空间就会完全的挨在一起,是容易混乱的。采用16字节对齐,按块读取效率更高,同时还不容易混乱。

size_t fastInstanceSize(size_t extra) const{
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } 
        else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
   }

3.2.设置对象的isa,调用的函数如下

截屏4.png

截屏5.png

断点调试发现走到了图5打断点的位置,newisa.setClass(cls, this);就是用来设置对象的isa信息(isa中存储了类的信息),这样就把实例和对应的类关联起来了。

3.init流程

调试中,首先来到了[NsObject.mm init],紧接着调用了_objc_rootInit方法,_objc_rootInit方法中直接返回了obj对象自身。也就是NSObject提供的init方法,底层并没有做什么事情,直接返回了对象本身。其实这是为了给开发者提供一个初始化构造函数,方便开发者直接在init中初始化的对象的相关属性信息。

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj){
    return obj;
}

4. [[X alloc] init]

调试过程中你会发现一个很有趣的问题,就是如果你直接NXPerson *person = [[NXPerson alloc] init],这样调用,实际上会直接来到

id objc_alloc_init(Class cls){
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}

5.new

id
objc_opt_new(Class cls)
{
#if __OBJC2__
    if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
        return [callAlloc(cls, false/*checkNil*/) init];
    }
#endif
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

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));
}

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);
}

6. 调试相关:

  • 代码转cpp文件
// 简单方式
clang -rewrite-objc main.m

// 如果有引用的UIKit等框架则需要:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

// 包含weak出错:
clang -x objective-c -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

// 第二种方式
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

// 包含weak
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
  • 设置断点:
//c函数
void test(){
}
breakpoint set -n test

// oc方法, 可以连续-n
breakpoint set -n "[ViewController viewWillAppear:]"

//查看断点列表
breakpoint list

//断点禁用/启用(组/分支)
breakpoint disable/enable 1/1.1 ..

//删除断点(只能删除整个组,不带数字,删除全部)
breakpoint delete 1/2 

//添加selector
breakpoint set --selector touchesBegan:withEvent:
breakpoint set --file ViewController.m --selector touchesBegan:withEvent:

//带Game的都打上断点
breakpoint set -r Game:
  • p 及po
# 打印类型及地址(p是expression的简写)
p obj 

# 打印对象的description方法
po obj //
  • 查看堆栈
#查看当前堆栈
bt

#查看上一级/下一级
up/down

#查看指定堆栈
frame select 3

#查看指定堆栈下的参数列表
frame variable

#可以使用p指令修改参数
p str = @"123456"

# 堆栈回滚(不会继续往下执行)
thread return
  • 内存断点
# 设置p1.name的断点给p1.name赋值时会断注
watchpoint set variable p1->_name

# 通过p指令获取指针的地址,然后通过地址来设置
p &p1->_name // (NSString **) $0 = 0x0000000.....30
watchpoint set expression 0x0000000.....30

  • target stop-hook
//在每一个断点位置打印参数列表
target stop-hook add -o "frame variable"

//查看列表
target stop-hook list

//删除
target stop-hook delete

  • image
//查看类信息
image lookup -t NXPerson

//查看image列表
image list

你可能感兴趣的:(iOS开发:alloc、init、new)