美文网首页
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