美文网首页
OC对象的本质

OC对象的本质

作者: Nulll | 来源:发表于2021-06-14 21:30 被阅读0次

    前言

    面向对象的语言那么万物皆对象,那么到底OC中什么是对象呢?那么今天带着这个问题来讨论一下什么是对象。

    之前我们提到过,所有的对象都是通过+alloc方法初始化的。

    什么才是OC对象。

    通过clang 分析我们的对象

    下面这段代码是将OC代码编译成c++源码:clang -rewrite-objc main.m -o main.cpp

    1、然后新建一个CDPerson对象.并且声明两个属性。

    @interface CDPerson : NSObject
    @property (nonatomic, copy) NSString *cd_name;
    @property (nonatomic, assign) NSInteger cd_age;
    @end
    
    @implementation CDPerson
    @end
    

    2、然后通过clang编译成c++源码。

    clang -rewrite-objc CDPerson.m -o CDPerson.cpp
    

    3、然后在c++源码里面我们发现我们的CDPerson 变成了下面这个;

    #ifndef _REWRITER_typedef_CDPerson
    #define _REWRITER_typedef_CDPerson
    typedef struct objc_object CDPerson;
    typedef struct {} _objc_exc_CDPerson;
    #endif
    
    extern "C" unsigned long OBJC_IVAR_$_CDPerson$_cd_name;
    extern "C" unsigned long OBJC_IVAR_$_CDPerson$_cd_age;
    struct CDPerson_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString * _Nonnull _cd_name;
        NSInteger _cd_age;
    };
    .....
    里面还有些属性的setter/getter。等等
    

    4、结论:通过这个源码我们不难看出,对象在底层被变异成了一个结构体 objc_object那么我们再来看看 objc_object 到底是什么东西。

    在objc 源码我们可以找到下面这个

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    重源码里面不难发现objc_object 里面只有一个isa(结构体指针);

    通过上前面的+alloc方法我们可以找到下面的方法。在这里面有一个方法 _class_createInstanceFromZone 下面有一个obj->initInstanceIsa(cls, hasCxxDtor);这样的方法。这个方法就是将申请的内存空间和当前类进行了绑定。

    1、看看下面两个:


    只是申请的内存空间

    2、当进行了类信息绑定后:


    绑定后的实列对象

    3、在看看 obj->initInstanceIsa(cls, hasCxxDtor)里面到底发生了些什么事情;里面调用了 objc_object::initIsa 这个方法。

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
    { 
        ASSERT(!isTaggedPointer()); 
        
        isa_t newisa(0);
        //
        if (!nonpointer) {
            newisa.setClass(cls, this);
        } else {
            ASSERT(!DisableNonpointerIsa);
            ASSERT(!cls->instancesRequireRawIsa());
            newisa.bits = ISA_MAGIC_VALUE;
            // isa.magic is part of ISA_MAGIC_VALUE
            // isa.nonpointer is part of ISA_MAGIC_VALUE
    #   if ISA_HAS_CXX_DTOR_BIT
            newisa.has_cxx_dtor = hasCxxDtor;
    #   endif
            newisa.setClass(cls, this);
    
            newisa.extra_rc = 1;
        } 
        isa = newisa;
    }
    

    4、从这个源码里面我们可以知道,对象下面最终为isa_t那现在我们来分析一下这个isa_t;下图为is a_t的结构信息。

    isa_t结构

    名词解释:typedef unsigned long uintptr_t;

    里面有一个宏定义:ISA_BITFIELD具体定义为:

    #     define ISA_MASK        0x0000000ffffffff8ULL
    #     define ISA_MAGIC_MASK  0x000003f000000001ULL
    #     define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 1
    #     define ISA_BITFIELD                                                      \
            uintptr_t nonpointer        : 1;                                       \
            uintptr_t has_assoc         : 1;                                       \
            uintptr_t has_cxx_dtor      : 1;                                       \
            uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
            uintptr_t magic             : 6;                                       \
            uintptr_t weakly_referenced : 1;                                       \
            uintptr_t unused            : 1;                                       \
            uintptr_t has_sidetable_rc  : 1;                                       \
            uintptr_t extra_rc          : 19
    
    //
    

    通过这个宏我们发现
    第一部分(nonpointer ):表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等;
    第二部分(has_assoc):关联对象标志位,0没有,1存在;
    第三部分(has_cxx_dtor):该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象;
    第四部分(shiftcls):存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针;
    第五部分(magic):用于调试器判断当前对象是真的对象还是没有初始化的空间;
    第六部分(weakly_referenced):志对象是否被指向或者曾经指向一个 ARC 的弱变量,
    没有弱引用的对象可以更快释放;
    第七部分(has_sidetable_rc):当对象引用技术大于 10 时,则需要借用该变量存储进位;
    第八部分(extra_rc):当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc;

    5、验证一下:
    5.1还是上面那个CDPerson,通过下面打印获取到 class,用ISA_MASK 获取isa信息


    ISA_MASK

    5.2查看二进制这个首地址的二进制文件


    二进制

    在为CDPerson创建一个分类,实现一个关联对象;

    @interface CDPerson (Test)
    @property (nonatomic, copy) NSString *nick_name;
    @end
    @implementation CDPerson (Test)
    - (NSString *)nick_name {
        return objc_getAssociatedObject(self, @selector(nickname));
    }
    - (void)setNick_name:(NSString *)nick_name {
        objc_setAssociatedObject(self, @selector(nickname), nick_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    @end
    
    关联对象

    通过结果我们可以发现:如果有关联对象那么 has_assoc 确实为1;

    5.3引用计数的值( extra_rc 和 has_sidetable_rc )


    小引用数的时候 大引用数的时候

    补充

    位域

    有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。

    在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:

    struct CDStruct1 {
        BOOL a;
        BOOL b;
        BOOL c;
        BOOL d;
    };
    struct CDStruct2 {
        BOOL a:1;
        BOOL b:1;
        BOOL c:1;
        BOOL d:1;
    };
    

    按照之前我们讲的结构体的内存大小问题一问,我们可以指知道 CDStruct1 的大小为 4字节;那么结构体 CDStruct2 呢?


    位域

    通过打印结果可以发现:这种情况可以极大的优化内存。

    联合体

    结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

    结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

    struct CDStruct {
        char    *name;
        double  a;
        int     age;
    };
    union CDUnion {
        char    *name;
        double  a;
        int     age;
    };
    
    联合体

    相关文章

      网友评论

          本文标题:OC对象的本质

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