【iOS开发】底层探索之对象的底层结构(上)——关于类与对象

【iOS开发】底层探索之对象的底层结构(上)——关于类与对象

类与对象

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

类,元类,根类

类的定义

对象的本质是一个结构体,而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里边一些没出现过的名词了。

  • hasCustomAWZ:我们先跳转到hasCustomAWZ的实现函数里:
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->ISA()->hasCustomAWZ()就是用来获取类或父类中,是否有alloc/allocWithZone:的实现。(注意我们的例子中Person类并没有alloc/allocWithZone的:实现)。

  • 我们知道了fastpath的参数是什么了之后,我们就该了解一下什么是fastpath
    我们跳转可发现两个宏定义(fastpath和slowpath):
#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。(这一点根据断点调试也可以判断出来)

  • 还有一个判断__OBJC2__
    根据底下的注释 // No shortcuts available.我们可以知道这个__OBJC2__是用来判断是否有编译优化。

接着跳转到下一步_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 && 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 & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && 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将类信息和之前系统为对象分配的内存空间关联起来,即完成了对象的实例化。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值