iOS底层原理之OC对象本质

一、涉及知识点

1.共用体(联合体)

定义

在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中,也就是使用覆盖技术,几个变量互相覆盖,以达到节省空间的目的,这种几个不同的变量共同占用一段内存的结构叫共用体,又称联合体。

特点:

共用体和结构体有下列区别:
a.结构体的成员之间是共存的:各个成员占用不同的内存,它们互相之间没有影响。

b.联合体的成员之间是互斥的:所有成员共用同一段内存,修改一个成员的值,会影响其余所有成员。

c.结构体占用的内存:大于等于所有成员占用内存的总和(需要内存对齐)

d.联合体占用的内存:等于最大的成员占用的内存,同一时刻只能保存一个成员的值

声明
#pragma mark 共用体
union WJUnion {
    int     a;
    float   b;
    char    c;
};

2.位域

定义

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称位域( bit field) 。利用位段能够用较少的位数存储数据。

特点:

a.位段成员的类型必须指定为unsigned或int类型;
b.若某一位段要从另一个字开始存放,用:0长度为0的空位段,作用就是使下一个位段从下一个存储单位(视不同编译系统而异)开始存放;
c.一个位段必须存储在同一存储单元中,不能跨两个单元;
d.可以定义无名字段例如":2";
e.位段的长度不能大于存储单元的长度,也不能定义位段数组;
f.位段可以用整形格式符输出;
g.位段可以在数值表达式中引用,它会被系统自动地转换成整形数;

声明
#pragma mark 结构体
struct WJCarStruct {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};

#pragma mark 位域
struct WJCarStructBit {
    BOOL front  : 1;
    BOOL back   : 1;
    BOOL left   : 1;
    BOOL right  : 1;
};

分析:
结构体WJCarStruct占4个字节,因为每个BOOL各占1个字节;
位域WJCarStructBit占1个字节,对比WJCarStruct结构体节省3个字节。

二、OC对象底层分析辅助工具(clang编译源码成底层代码)

1.clang定义

clang简而言之是一个C语言、C++、Objective-C语言的轻量级编译器,源代码发布于BSD协议下。Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字

2.在终端使用clang相关命令编译WJPerson.m生成WJPerson.cpp底层文件

2.1 使用clang命令

# clang -rewrite-objc WJPerson.m -o WJPerson.cpp

2.2 使用Xcode中的xcrun命令

# xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc WJPerson.m -o WJPerson.cpp
3.打开WJPerson.cpp并找到WJPerson类
typedef struct objc_object WJPerson;
typedef struct {} _objc_exc_WJPerson;

extern "C" unsigned long OBJC_IVAR_$_WJPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_WJPerson$_nickName;
struct WJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;  //就是isa指针
    NSString * _Nonnull _name;        //属性name对应的成员变量
    NSString * _Nonnull _nickName;    //属性nickName对应的成员变量
};
// @property (nonatomic, copy)NSString *name;
// @property (nonatomic, copy)NSString *nickName;
/* @end */

从编译之后的文件找到WJPerson_IMPL,可以看出WJPerson类实际上是一个结构体;由此可见,在OC中类的本质就是结构体(struct)。

3.1 来看结构体中的成员变量_name和_nickName,其实就是WJPerson头文件中声明的两个属性name和nickName所对应的。
@interface WJPerson : NSObject

@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *nickName;

@end
3.2 再来看结构体NSObject_IMPL是什么呢?

在WJPerson.cpp中搜索NSObject_IMPL,发现其实就是isa指针,

struct NSObject_IMPL {
    Class isa;
};

在OC中基本上所有的对象都是继承NSObject,但是真正的底层实现是objc_object的结构体类型

3.3 对象的本质拓展

在WJPerson.cpp文件中全局搜索*Class时,发现有这样几行代码如下:

typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

typedef struct objc_object *id;

typedef struct objc_selector *SEL;

源码分析:
常用的id原来是也是一个objc_object结构体指针别称,这就解释了id修饰变量和作为返回值的时候为什么不加*了,此外还有Class、SEL等也是结构体指针。

三、nonPointerIsa分析

对象alloc流程核心三步曲

a.instanceSize,计算开辟内存需要的大小。
b.calloc,向系统申请开辟内存,返回地址指针。
c.initInstanceIsa,初始化指针和类关联起来。

这里主要介绍initInstanceIsa流程,见objc源码对应处如下:
4761623767547_.pic_hd.jpg
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

进入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 {
      ......
    }
    isa = newisa;
}

进入isa_t,发现isa_t就是一个共用体,源码如下:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};
ISA_BITFIELD分析

在表现一个类的地址时,会出现一个词:nonPointerIsa。就比如一个类,也就可以作为一个指针,类上面是可以有很多内容是能够被存储的。类的指针是8字节,8字节 * 8 bit = 64 bit(64位)。那么如果只是用来存储一个指针,就会造成大大的浪费,因为每个类都有一个isa指针。苹果就对这个isa做了优化,就把和类息息相关的一些内容存在里面,比如:是否正在释放、引用计数、weak、关联对象、析构函数等等(所以,OC在底层,就是C++,像OC的释放,并不是真正的释放,而是其下层的C++释放,才是真正的释放),这些都和类先关,所以,可以把这些内容存储到那64位里面去。那么就出现了nonPointerIsa。nonPointerIsa也不是一个简单的地址。我们可以通过查看isa_t的位域,来了解里面存的是什么。

在arm64中:

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     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
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

字段说明如下:
nonpointer:
表示是否对isa指针开启指针优化 0:纯isa指针,1:不⽌是类对象地址,isa中包含了类信息、对象的引⽤计数等;

has_assoc:
关联对象标志位,0没有,1存在;

has_cxx_dtor:
该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;

shiftcls:
存储类指针的值,开启指针优化的情况下,在arm64架构中有33位⽤来存储类指针;

magic:
⽤于调试器判断当前对象是真的对象还是没有初始化的空间;
weakly_referenced:志对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放;

deallocating:
标志对象是否正在释放内存;

has_sidetable_rc:
当对象引⽤技术⼤于10时,则需要借⽤该变量存储进位;

extra_rc:
当表示该对象的引⽤计数值,实际上是引⽤计数值减1,例如,如果对象的引⽤计数为10,那么extra_rc为9。如果引⽤计数⼤于10,则需要使⽤到下⾯的has_sidetable_rc。

以arm64为例,堆中,8字节对齐排列,8字节 * 8 bit = 64 bit(64位)。
那么,nonpointer占[1]号位置,has_assoc占[2]号位置,has_cxx_dtor占[3]号位置,shiftcls占[4 ~ 36]号位置,magic占[37~42]号位置,weakly_referenced占[43]号位置,deallocating占[44]号位置,has_sidetable_rc占[45]号位置,extra_rc占[46 ~ 64]号位置。

总结:
1.OC中类的本质就是结构体。
2.ISA是通过共用体(联合体)互斥的特性,确定ISA是纯的ISA还是NONPOINTER_ISA,并通过位域来实现节约空间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,682评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,277评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,083评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,763评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,785评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,624评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,358评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,261评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,722评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,030评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,737评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,360评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,941评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,057评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,237评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,976评论 2 355

推荐阅读更多精彩内容