美文网首页
IOS 对象的创建

IOS 对象的创建

作者: Sam_1bb7 | 来源:发表于2020-09-11 00:01 被阅读0次

OC 中创建对象是基础的操作,但我们是否有了解过OC对象是怎么创建的,这篇文章就是从最基础的对象创建起,看一下OC创建对象的过程中到底都做了什么。

对象的创建方式

我们通常创建对象有两种方式

LGPerson *p1 = [[LGPerson alloc] init];
LGPerson *p2 = [LGPerson new];

一. alloc和init

我们先看第一种,第一种创建对象的方式分为了两步,alloc和init, 我分别打印出来这两步的返回值,看看alloc和init分别做了什么,对象是在哪一步创建成功的。

    LGPerson *p1 = [LGPerson alloc];
    LGPerson *p2 = [p1 init];
    LGPerson *p3 = [p1 init];
    LGNSLog(@"%@ - %p - %p",p1,p1,&p1);
    LGNSLog(@"%@ - %p - %p",p2,p2,&p2);
    LGNSLog(@"%@ - %p - %p",p3,p3,&p3);
<LGPerson: 0x600003dc4210> - 0x600003dc4210 - 0x7ffeea1f81d8
<LGPerson: 0x600003dc4210> - 0x600003dc4210 - 0x7ffeea1f81d0
<LGPerson: 0x600003dc4210> - 0x600003dc4210 - 0x7ffeea1f81c8

我们通过打印的日志信息可以发现,在alloc后,p1进行多次init后,p1的值并没有和只是alloc后返回的指针有什么不同,说明在alloc后,对象其实已经创建成功了,所以分析系统怎么创建对象,我们可以分析alloc的流程就知道对象是怎么创建的

注:&p1,&p2,&p3的值不同,只是代表存储p1,p2,p3这三个变量是在不同的地址,但是它们所指向的地址是同一个区域,是同一个变量。

1. alloc 调试

我们想要分析alloc做了什么,就要去对alloc进行断点调试,但是正常的代码调试并不能跑进到alloc里面,所以需要一些特殊的调试方式去查看alloc的底层函数,有三种方式去调试alloc函数。

1)control+stepInto

在断点调试到alloc方法后,按住control健,stepInto的按钮会变化,此时点击进入,就可以看到alloc进入的函数。


断点调试 进入后的界面

2)符号断点

在断点调试到alloc方法后,添加alloc方法的符号断点,进行调试,就会看到alloc进入的函数(一定要在断点到了alloc函数的断点后再放开alloc方法的符号断点,因为在我们alloc之前,会有其他类的alloc。


WechatIMG502.jpeg

3)查看汇编

在断点调试到alloc方法后,Debug->Debug WorkFlow->Always Show Disassembly,会显示出当前的汇编信息,可以通过汇编信息查看当前alloc执行进入的方法。

WechatIMG503.jpeg
当然之前的方法只能进入到一层,再底层无法在看到,但是可以看到alloc进入的函数在那个lib中,苹果对底层的一些库是开源的,我们可在苹果开源库的网站中找到对应的代码。可以参考Cooci 老师的源码调试
objc4-779.1源码编译调试

2. alloc 流程分析

拿到源码后,我们就可以查看源码,并对源码进行调试,首先是NSObject的alloc

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

然后进入到_objc_rootAlloc函数

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

再是callAlloc 函数

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
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.
    
    printf("objc_msgSend\r\n");
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

callAlloc 函数有两个流程_objc_rootAllocWithZone 和 objc_msgSend
callAlloc里面有个宏定义

//是1的几率更大一点
#define fastpath(x) (__builtin_expect(bool(x), 1))
//是0的几率更大一点
#define slowpath(x) (__builtin_expect(bool(x), 0))

这两个宏定义的作用只是为了告诉编译器进行优化,因为编译读取指令的时候是一次读多条,而读取指令是非常耗时的,如果没有这两个宏的话,编译器读取指令的时候就会直接读入,而如果编译器知道了这段代码的执行几率的话,读取这段指令的时候就会进行优化。

如果进行objc4源码调试的时候会发现,如果类第一次alloc,会直接走进callAlloc类,然后走到objc_msgSend,然后再重新从NSObject alloc函数进入,一直走到这个callAlloc函数中,再走进_objc_rootAllocWithZone流程中,后面如果类如果再次进行alloc操作的话直接进入callAlloc,走_objc_rootAllocWithZone流程,也就是说类第一次alloc走了两次callAlloc函数。\color{#FF0000}{这是因为LLVM编译器会hook住类的alloc函数,并进行重定向,直接定向到了callAlloc函数}
_objc_rootAllocWithZone 函数

NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    
    printf("_objc_rootAllocWithZone\r\n");
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                    OBJECT_CONSTRUCT_CALL_BADALLOC);
}

再进入_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 (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // alloc 开辟内存的地方
        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);
}

查看_class_createInstanceFromZone函数,发现创建函数主要有三步:
计算类需要开辟的空间.

 size = cls->instanceSize(extraBytes);

开辟内存空间

obj = (id)calloc(1, size);

把开辟的空间和类关联

obj->initInstanceIsa(cls, hasCxxDtor);

因此,可以可以知道了从alloc函数进入后的主要流程


alloc 流程

2. init 做了什么

从上面的分析可以看出,在alloc了以后,其实对象都已经创建成功了,我们已经可以对对象进行操作了

    LGPerson *p1 = [LGPerson alloc];
    p1.name = @"sam";
    NSLog(@"%@",p1.name);
2020-09-10 22:45:10.903283+0800 001-alloc&init探索[71840:1374047] sam

那么我们为什么还要进行init的操作了,我们查看源码发现init函数只是返回了对象本身,其实init函数只是给我们提供了一个类的入口函数,类似C++里面的一些指针,让我们可以进行一些初始化操作。

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

二. new函数

使用new函数也可以创建对象,new函数是通过callAlloc+init函数实现的。

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

我们上面分析了alloc,alloc函数也是通过调用了callAlloc函数实现的,因此new其实就相当于alloc+init的创建对象方式。

相关文章

  • alloc init 与 new 的区别

    iOS开发中经常遇到创建对象,我们都知道iOS创建对象有两种方式 alloc init new 这两种创建对象的方...

  • IOS 对象的创建

    OC 中创建对象是基础的操作,但我们是否有了解过OC对象是怎么创建的,这篇文章就是从最基础的对象创建起,看一下OC...

  • iOS编程读书笔记之Objective-c

    iOS编程读书笔记之Objective-C 对象 使用对象创建对象Party *partyInstance = [...

  • ios 动态创建和复用结构

    在iOS开发中,UI对象的创建我一值坚持的动态创建对象。顶部菜单为例根据数据完成UI对象的创建,动态完成和修改。保...

  • iOS FMDB

    iOS FMDB 数据库创建、增、删、改、查。 创建对象 创建表 integer,text:类型 primary ...

  • 如何合适的构建Model

    Arek HolkoiOS创建对象的姿势Building and managing iOS model objec...

  • iOS视图的生命周期

    iOS中视图的生命周期 alloc 创建对象,分配空间 init (initWithNibName)初始化对象,...

  • iOS 创建对象的姿势

    在写 iOS 代码的时候,怎么样去 new 一个新对象出来,都有一些讲究在里面。使用不同的姿势去创建对象,对后期维...

  • ios 模型对象创建

    创建一个类,继承NSObject .h文件 #import @i...

  • UIApplication对象

    UIApplication对象基本使用 一个iOS程序启动后,创建的第一个对象就是UIApplication对象 ...

网友评论

      本文标题:IOS 对象的创建

      本文链接:https://www.haomeiwen.com/subject/tkcuektx.html