iOS底层原理_03:OC对象原理(下)

第三节课 OC对象原理(下)

全篇开始之前我们想一个问题,研究了这么久对象,究竟什么是对象呢??

对象本质以及拓展

Clang

探索对象的本质前,我们先了解一个编译器:clang
Clang是一个C语言、C++、OC语言的轻量级编译器。源代码发布于BSD协议下。Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。

clang是一个由Apple主导编写,基于LLVM的C/C++/OC的编译器

探索本质

1、在main中自定义一个类LGPerson,有一个属性name

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end

2、通过终端,利用clangmain.m编译成 main.cpp

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

3、打开编译好的main.cpp,找到LGPerson的定义,发现LGPerson在底层会被编译成struct 结构体

03-对象的本质是结构体.png

我们可以看到,这是一个结构体,里面嵌套了一个结构体,结构体能够继承嘛?其实是可以的,但是是属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson拥有NSObject中的所有成员变量。

LGPerson_IMPL中的第一个属性,其实就是isa,是继承自NSObject

03-IVARS.png

通过上图我们可以看到成员变量是Class isa。通常叫isa叫做isa指针,那么这里的Class应该是个指针类型,在main.cpp文件中全局搜索*Class。代码如下

03-class.png

LGPerson的类型是objc_object,在OC层面 我们的LGPerson是继承NSObject,其实在下层真正的实现就是objc_object

03-objc_object.png

对象的本质拓展

在刚才看class的过程中,还发现了两个东西


03-id、sel.png

熟悉的idSEL。常用的id原来是一个objc_object结构体指针,这就解释了id修饰变量和作为返回值的时候为什么不加*,下面的SEL也是结构体指针这个也是之前不知道的呢,就稍微了解下吧~

在稍下面一点我们看到一些奇奇怪怪的一些东西

03-方法的get、set.png

这其实就是我们的Get方法Set方法,但是参数部分呢?我们可在OC中没有看到,这其实就 是我们的隐藏参数。
这个Get方法中的参数self+OBJC_IVAR_$_LGPerson$_name这一步就是我们之前讲过的,要获取一个类的对象的地址,是通过获取类的首地址+对象的偏移量的方法来最终取得对象的。

小结

所以从上述探索过程中可以得出:

  • OC对象的本质其实就是结构体
  • LGPerson中的isa继承NSObject中的isa

联合体位域拓展补充

位域(位段)

位域:在C语言中允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单元的成员称为位域。

struct HZMCar1 {
    BOOL front; // 0 1
    BOOL back;
    BOOL left;
    BOOL right;
};
struct HZMCar1 car1;
NSLog(@"%ld",sizeof(car1));
<--打印输出-->
4

在开发当中遇到这种情况,其实BOOL就只有两种情况,但是却使用了4个字节32位,其实我们只用4位就可以了,这样我们就只使用了1字节,还有3字节其实是浪费的。接下来我们进行改进一下

struct HZMCar2 {
    BOOL front: 1;
    BOOL back : 1;
    BOOL left : 1;
    BOOL right: 1;
};
struct HZMCar2 car2;
NSLog(@"%ld",sizeof(car2));
<--打印输出-->
1

其实就是指定对象所占内存长度

联合体(共用体)

struct LGStudent {
    char        *name;
    int         age;
    double      height ;
};

union LGStudent2 {
    char        *name;
    int         age;
    double      height ;
};
struct LGStudent   student;
student.name = "HZM";
student.age  = 18;

union LGStudent2    student2;
student2.name = "HZM";
student2.age  = 18;
NSLog(@"%ld-%ld",sizeof(student),sizeof(student2));
<--打印输出-->
24-8

首先我们能观察到,同样的成员变量,内存大小天差地别,这是为什么呢?我们来通过断点查看下。

03-联合体.png

可以看到联合的成员变量在未赋值的情况下是一块脏数据、脏内存,当第二个变量被赋值的时候又将第一个变量清理了,所以联合体的各种变量互斥,每次只能赋值一个,这样就可以接受大幅空间,这也就是我们之前看到的为什么联合体的内存大小比较小的原因。

小结:

位域和联合体对比
位域:优点是所有变量可以共存,缺点是内存空间粗放,不管用不用,都分配
联合体:优点是内存更精细灵活,节省空间,缺点是各种变量互斥

isa的类型 isa_t

以下是isa指针的类型isa_t的定义,从定义中可以看出是通过联合体(union)定义的。

union isa_t { //联合体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //提供了cls 和 bits ,两者是互斥关系
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};  

isa_t类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能
从isa_t的定义中可以看出:

  • 提供了两个成员,clsbits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,只有一个变量有值

    • 通过cls初始化,bits无默认值
    • 通过bits初始化,cls有默认值
  • 还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 __arm64__(对应ios 移动端) 和 __x86_64__(对应macOS),以下是它们的一些宏定义,如下图

    03-ISA_BITFIELD.png

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

03-isa存储情况.png

ISA_MASK

ISA_MASK是一个宏,一个掩码。__x86_64__ 的值等于 0x00007ffffffffff8ULL__arm64__ 的值等于0x0000000ffffffff8ULL。通过x/4g p获取到的首位地址就是isa的值是0x001d800100008275,验证下0x001d800100008275&0x00007ffffffffff8ULL结果

03-MASK.png

对象通过 isa & 掩码 得到类的信息

isa的位运算

通过上面的学习我们已经知道了isashiftcls是用来存储类指针,所以我们降妖获取类指针其实也可以通过位运算获取shiftcls的最终值

03-位运算.png

通过上图我们发现,其实我们要获取的shiftcls正好在中间,左边有3位,右边有28位,那我们如果要获取的话就可以将shiftcls整体向左移3位,将多余部分挤出去,向右移20位,将左边的多余部分挤出去,最后左移17位恢复原位,这个时候剩下的不就是我们需要的shiftcls了。我们来验证下

03-验证位运算.png

可以看到位运算结束后的结果与我们输出的class相同。

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

推荐阅读更多精彩内容