本文介绍: 对象本质一个结构体,而id,是指向这个结构体的指针,也就是我们平时Person*p=[Personalloc]init];的话,代表我们创建一个Person对象分配地址,并且把他的地址给了p这个指针我们通过p来操作对象,或者说p代表了对象,但p并不是对象本身。而对象是存在内存里的一个结构体。}*id;};每个对象都有一个类,所以对象的isa指针指向哪个类,此对象就属于哪个类。OC对象在发送消息时,运行时库会追寻着对象的isa指针得到对象所属的类。,…

类与对象

在了解类与对象的本质我们需要了解一个知识点,那就是什么是类,父类元类以及它们之间有啥 关系接下来带着问题去探究:

类,元类,根类

类的定义

对象的本质是一个结构体,而id,是指向这个结构体的指针,也就是我们平时Person *p = [Person alloc] init]; 的话,代表我们创建一个Person对象分配了地址,并且把他的地址给了p这个指针,我们是通过p来操作对象,或者说p代表了对象,但p并不是对象本身。而对象是存在于内存里的一个结构体。

OC中对象是这样定义的:

typedef struct objc_object {
    Class isa;
} *id;

同样,我们知道其实类也是一个对象(可以称为类对象), 所以类的本质也是一个结构体而Class是这个结构体的指针:

typedef struct objc_class *Class;  
struct objc_class {  
    Class isa;  
    Class super_class;  
    const char *name;  
    long version;  
    long info;  
    long instance_size;  
    struct objc_ivar_list *ivars;  
    struct objc_method_list **methodLists;  
    struct objc_cache *cache;  
    struct objc_protocol_list *protocols;  
}; 

每个对象都有一个类,所以对象的isa指针指向哪个类,此对象就属于哪个类。

元类定义

学习消息发送机制时,我们应该知道消息发送的过程中在第二步动态解析时候判断是类对象还是元类对象。其原理就是 OC对象在发送消息时,运行时库会追寻着对象的isa指针得到对象所属的类。这个类包含了能应用于这个类的所有实例方法以及指向父类的指针,以便可以找到父类实例方法运行时库检查这个类和其父类方法列表,找到与消息对应方法编译器会将消息转换为消息函数objc_msgSend进行调用

其实我们就可以理解前面提到的类也是一个对象(类对象),因为对象一定会有一个对应的类,并且有一个指针isa指向它对应的类所以类也会有一个指针isa,指向它所属的类,那么听起来比较绕,总结起来就是类的类是什么?是元类(MetaClass)

这下我们在重新理解一下关于动态解析时遇到的那个需要判断问题

  • 总的来说就是如果你给对象发消息,消息会寻找对象的类的方法列表
  • 如果你是给类发消息,消息就会寻找类的类的方法列表,也就是元类的方法列表

元类的类(根类)

有一个问题,如果按类的类是元类这个逻辑理解是否可以一直循环下去?答案是肯定不行的,因为一定会有一个坐为最原始的那个类,那么这个类就是元类的类:所有的元类都使用根元类作为他们的类。根元类的 isa 指针指向了它自己

这个图可以帮助我们理解类,元类,根类的关系
在这里插入图片描述

关于alloc

我们平时创建对象都会写alloc init或者new,那么我们可以想想为什么是alloc init,光alloc行吗或者光写init行吗?我们不妨可以先试着写一下:

 Person *p = [Person alloc];
 Person *p1 = [p init];
 Person *p2 = [p init];       
 NSLog(@"%@ %p",p,&p);
 NSLog(@"%@ %p",p1,&p1);
 NSLog(@"%@ %p",p2,&p2);

我们看一下打印结果
在这里插入图片描述

通过打印结果我们可以发现p,p1,p2指向同一个地址,所以就是说他们是同一个对象。我们来分析一下代码,首先Person类使用alloc方法系统申请开辟内存空间后,init方法并不会对内存空间做任何的处理,地址指针的创建是来自于alloc方法。
但是我们可以发现一个不一样的就是p, p1, p2都是相差了8个字节。 这是因为,指针占内存空间大小为8字节,p, p1, p2 都是从栈内存空间申请的,且栈内存空间是连续的。同时,他们都指向了同一个内存地址。

那么alloc方法执行过程什么呢?

alloc方法的执行过程

看看源码,我们发现调用alloc会先跳转到(第一步):

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

这个函数会给我们返回一个_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函数,所以下一步就该走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));
}

终于不再只是一个简单return了,但是此时我们就需要去先了解一下callAlloc里边一些没出现过的名词了。

bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }

发现了FAST_CACHE_HAS_DEFAULT_AWZ我们接着跳转

// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)

利用有道翻译翻译一下class or superclass has default alloc/allocWithZone: implementation
Note this is is stored in the metaclass.:

在这里插入图片描述
其实cls-&gt;ISA()-&gt;hasCustomAWZ()就是用来获取类或父类中,是否有alloc/allocWithZone:的实现(注意我们的例子中Person类并没有alloc/allocWithZone的:实现)。

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

两个宏都调用了__builtin_expect

__builtin_expect(bool exp, probability)的主要作用是进行条件分支预测

我们可以看到这个函数有两个参数

  1. 第一个参数:是一个布尔达表达式(布尔表达式(Boolean expression)是一段代码声明,它最终只有true(真)和false(假)两个取值。最简单的布尔表达式是等式(equality),这种布尔表达式用来测试一个值是否与另一个值相同。)
  2. 第二个参数:表明第一个参数为真值的概率,这个参数只能是1或0;当取值为1时,表示布尔表达式大部分情况下的值为真值;当取值为0时,表示布尔表达式大部分情况下的值是假值。

函数的返回值,就是第一个参数的表达式的值

那也就是说fastpath(!cls->ISA()->hasCustomAWZ())结果,其实就是!cls->ISA()->hasCustomAWZ()结果。那么「第四步」就是进入if里面的_objc_rootAllocWithZone。(这一点根据断点调试也可以判断出来)

接着跳转到下一步_objc_rootAllocWithZone(第四步)

NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    // __OBJC2__ 下的 allocWithZone 忽略 zone 参数
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

这个也就比较简单了就是为了调转到_class_createInstanceFromZone

_class_createInstanceFromZone是alloc方法的核心,先看代码

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)
{
    // 'Realized' --> '实现'
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    // 为了提高性能一次读取类的信息
    bool hasCxxCtor = cxxConstruct &amp;&amp; cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    // 计算需要开辟的内存大小,根据上一个函数`_objc_rootAllocWithZone`可以知道,此时‘extraBytes’==‘0’
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 同样的根据`_objc_rootAllocWithZone`可以知道,`__OBJC2__`情况下,会进入这里
        // calloc --> 申请内存,大小为`size`.
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags &amp; OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone &amp;&amp; fast) {
        // 关联 cls(类) 与 objc指针(isa指针)
        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);
}

代码很长,但是几个重要的部分一定要知道

  1. size = cls->instanceSize(extraBytes);计算需要开辟的内存大小。
  2. obj = (id)calloc(1, size);申请刚刚计算过的内存
  3. obj->initInstanceIsa(cls, hasCxxDtor);关联类和isa指针。在这一步,isa将类信息和之前系统为对象分配的内存空间关联起来,即完成了对象的实例化。

最后object_cxxConstructFromClass这一步比较简单了,相当于最后一步总结工作
在这里插入图片描述

基本上走到这一步我们只需要知道如果return self的话就是构造成功了,如果return nil表示构造失败了。

整个alloc方法就是这样,我们在根据流程图看一下:
在这里插入图片描述

关于这个流程图先来说一下不一样的地方,那就是本图的上半部分,也就是虚线方框上的部分,首先我们可以看到创建对象调用alloc方法时,会先走_objc_rootAlloc然后callAlloc在callAlloc中有第一个判断hasCustomAWZ前边介绍了,这个方法主要是看类或父类没有重写alloc方法,如果重写了就会进行变判断是否重写了allocWithZone:方方法如果重写了就直接可以进行下一步的_objc_rootAllocWithZone方法,如果没有就重新回到alloc方法,接着就到了流程图的下半部分,走到_class_createInstanceFromZone数中进行上面讲过的三个最重要的方法:

  1. size = cls->instanceSize(extraBytes);计算需要开辟的内存大小。
    1. obj = (id)calloc(1, size);申请刚刚计算过的内存
  2. obj->initInstanceIsa(cls, hasCxxDtor);关联类和isa指针。在这一步,isa将类信息和之前系统为对象分配的内存空间关联起来,即完成了对象的实例化。

最后就只需要知道创建好了还是失败了就行了。

原文地址:https://blog.csdn.net/weixin_51638861/article/details/125775411

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_22490.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注