谈谈我对Objective-C对象本质的理解

【原创博文,转载请注明出处!】
Objective-C的本质
我们平时编写的Objective-C代码,底层实现其实都是C、C++代码,所以Objective-C的面向对象是基于C、C++的数据结构实现的。
思考:Objective-C的对象、类主要是基于C、C++的什么数据结构实现的呢?
因为对象或类可以有各种类型的实例(NSString *, CGFloat, NSArray *),能存放不同类型的数据结构,无非就是结构体了。显然,Objective-C的对象、类主要是基于C、C++的结构体实现的。
为了证明这一点,我们试图将OC代码转成更为底层一点的代码来一探究竟。
因为底层C++代码对C是完全兼容的,所以暂且将OC转成C++代码
借助终端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp来实现。

**
解析Mac程序
xcrun -sdk macosx clang -arch x86_64 -rewrite-objc main.m -o main.cpp
**

Attention Please~ O(∩_∩)O~~

如果电脑上面安装了多个版本的Xcode,转换为C++代码的时候会提示各种框架找不到的错误,一般是因为多个版本的Xcode路径冲突导致的,我们需要在终端指定一个Xcode的路径,如:sudo xcode-select --switch /Applications/Xcode10.1.app/Contents/Developer/,因为我本人桌面上另一个Xcode自定义名为Xcode10.1,所以这里填入Xcode10.1.app,注意并不是指Xcode的版本号哦😁

解释一下:
xc就是Xcode的缩写。
xcrun是Xcode的一种工具。
-sdk iphoneos规定sdk需要运行在iOS系统上面。
clang是Xcode内置的llvm编译器前端,也是编译器的一种。
-arch xxx(arm64、i386、armv7...)指出iOS设备的架构。
参数 -rewrite-objc xx.m 是重写objc代码的指令(即重写xx.m文件) 。
-o newFileName.cpp 表示输出新的.cpp文件。
终端指令成功生成.cpp文件.png
NSObject对象的实现.png
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end

将系统NSObject的定义简化为:

@interface NSObject <NSObject> {
      Class isa ;
}
@end

点击Class后进入objc.h,Class 定义为 : typedef struct objc_class *Class; 也就是说Class是个结构体指针,因此系统的NSObject类仅有唯一的一个成员变量,即isa指针。

一个NSObject对象占用多少内存?
了解了OC对象的基本结构之后,再讨论一下对象所占用的内存情况。

<objc/runtime.h>文件提供class_getInstanceSize(Class _Nullable cls)方法,返回我们一个OC对象的实例所占用的内存大小;
<malloc/malloc.h>文件提供 size_t malloc_size(const void *ptr)方法返回系统为这个对象分配的内存大小。

测试:

  • 看一个没有成员变量的类的实例(以NSObject为例)
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"NSObject实例大小--> %zd",class_getInstanceSize([obj class]));
    NSLog(@"obj实际分配的内存%zd",malloc_size((const void *)obj));

//    2018-07-18 00:30:28.033 LJMahjong-mobile[41409:3330831] NSObject实例大小--> 8
//    2018-07-18 00:30:28.034 LJMahjong-mobile[41409:3330831] obj实际分配的内存16

  • 再来看看一个普通的类的实例,并且实例有自己的成员变量(新建一个Student类,为其添加属性age、name等)
@interface Student: NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL male;

@end;

@implementation  Student

@end;


  Student *stu = [Student new];
  stu.name = @"Rephontil.ZHou";
  stu.age = 25;
  stu.male = YES;
  NSLog(@"Student实例大小--> %zd",class_getInstanceSize([stu class]));
  NSLog(@"stu实际分配的内存%zd",malloc_size((const void *)stu));
//    2018-07-18 00:30:28.035 LJMahjong-mobile[41409:3330831] Student实例大小--> 32
//    2018-07-18 00:30:28.035 LJMahjong-mobile[41409:3330831] stu实际分配的内存32
  • 再来看看一个普通的类的实例,并且实例有且仅有唯一的成员变量(如Student只有一个name属性)
@interface Student: NSObject

@property (nonatomic, copy) NSString *name;
//@property (nonatomic, assign) NSInteger age;
//@property (nonatomic, assign) BOOL male;

@end;

@implementation  Student

@end;


  Student *stu = [Student new];
  stu.name = @"Rephontil.ZHou";
  //  stu.age = 25;
  //  stu.male = YES;
  NSLog(@"Student实例大小--> %zd",class_getInstanceSize([stu class]));
  NSLog(@"stu实际分配的内存%zd",malloc_size((const void *)stu));
//    2018-07-18 00:50:18.106 LJMahjong-mobile[41842:3456140] Student实例大小--> 16
//    2018-07-18 00:50:18.107 LJMahjong-mobile[41842:3456140] stu实际分配的内存16

由以上三次次结果的不同可推测:一个OC对象所占用的内存取决于这个对象成员变量的多少。但是同时,系统为其分配内存时,默认会分配最少16个字节的大小。

有人对指针的长度不清楚,对不同位数系统的机器总是记忆混淆☹️,还有些人说系统就是那么规定的,一句话就能囊括宇宙!其实有些事情真的有很清楚的理由让你搞明白,比如你看看我的解释:
在64bit系统上,指针占用8个字节。
在32bit系统上,指针占用4个字节。
这个很好理解,指针说白了也就是内存的地址,在64bit计算机上,内存地址显然是由64位“0”或“1”这样的二进制数组成的,因为1byte = 8bit,所以64位计算机,地址内存地址为8byte,32位计算机内存地址也就是4byte。

关于“在绝大多数情况下:OC对象所占用内存大小与对象所拥有的属性或成员变量的大小之和不相等”这一事实,可以参考内存对齐方面的知识。
关于什么是内存对齐❓以及内存对齐对CPU效率的影响❓可以参考这篇文章内存对齐

在iOS里面,创建一个OC对象,系统分配的内存大小都是16byte(字节)的倍数,最大为256byte(字节)。具体为什么这样分配,参考多方资料得出的结论就是:iOS系统下,这种内存分配方式可以使CPU工作效率最高。

OC对象的分类

OC对象分为3类 :
1 (instance对象)实例对象 ;
2 (class对象)类对象 ;
3(meta-class对象)元类对象。

instance对象就是通过alloc出来的,一个类可以通过alloc init得到多个instance对象。instance对象在内存中存储的信息包括:该实例对象的成员变量(包括isa指针),不包含方法。
所有的实例对象里面都有isa指针。因为几乎所有的OC对象都继承自NSObject,所以包含isa指针。

相比较类的instance对象,每个类的类对象和元类对象都只有一个。
类对象存放的内容包含:
isa指针;
superClass指针;
这个类的对象方法(也就是"-"开头的实例方法);
类的属性信息和成员变量信息(NSString、int、float…);
类的协议等信息。
元类对象存放的信息包含:isa指针 、superclass指针以及类的类方法(“+”方法)。

A.对一个类或一个类的实例对象执行class方法、或对一个类的实例对象调用object_getClass()方法,都可以得到这个类的类对象;
如:
1、Class objectClass1 = [NSObject class];
2、NSObject *object = [[NSObject alloc] init];
Class objectClass2 = [object class];
3、 Class objectClass2 = object_getClass(object);
1 与 2都可以获取NSObject的类对象,且同一个类的类对象地址相同(仅有一份)。
B.对“类对象”执行object_getClass方法,就可以得到这个类的元类对象。
Class metaClass = object_getClass(objectClass2);

isa指针

关于isa指针指向关系.png

instance(实例)对象的isa指向class(类对象)。当调用对象方法时,通过instance的isa指针找到class,最后找到对象方法的实现进行调用。
class(类对象)的isa指向meta-class(元类)。当调用类方法时。通过class的isa指针找到meta-class,最后找到类方法的实现进行调用。

isa与superclass葵花宝典.jpg

这幅图我在初学iOS的时候就看到过,那时候头脑中还没有isa的概念☹️,此图几乎每一本iOS进阶书籍必备。通过MJ大神的网络视频,本人对instance、class、meta-class三者之间的isa与superclass关系有了更近一部的理解。

图中左上角一虚一实两箭头表明:该关系网中,所有虚线箭头指向都是isa指针指向的关系,实线箭头指向都是superclass指针指向的关系。
因此对于拥有isa变量的对象(instance、class、meta-class):
-instance对象的isa指向class
-class对象的isa指向meta-class
-meta-class对象的isa指向基类(Root class)的meta-class

对于拥有superclass变量的对象(class、meta-class):
-class对象的superclass指针指向父类的class对象。如果父类不存在,则superclass指针指向nil。
-meta-class对象的superclass指针指向父类的meta-class。基类的meta-class的superclass指向基类的class对象。(说人话就是:基类的元类对象的superclass指向基类的类对象!!!很多人会说瓦日吧😁)。

补充一点:从64bit系统开始,isa指针需要进行一次位运算才能算出其指向的对象的实际地址。(也就是说:之前讲的instance的isa值与instance的class对象的地址并不一定相等,instance的isa需要进行一次位运算才可以得到class的地址)


isa实际指向的地址与class、meta-class地址不一定相等.png

通过上面对“isa与superclass葵花宝典.jpg!
”这幅图中设计的逻辑关系的讲解,因此理解起来子类调用父类的方法就很简单了。对于子类调用父类的实例方法与类方法:
1、调用实例方法;
isa找到class,方法不存在,就通过class的superclass找到其父类的类对象,查看父类的class(类对象)里面是否有实例方法。一层层向上。
2、调用类方法。
isa找到meta-class,如果没有类方法,就通过meta-class的superclass指针找到父类的meta-class对象,查看父类的meta-class里面是否有对应的类方法。一层层向上。

奖励🎁:苹果objc源码开源下载地址,点进去会看到一堆objc4-208.tar.gz格式的文件,-208表示序号。苹果官方也在不断地更新这套源码,序号越大表示源码越新,下载查看的时候课留意一下。

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

推荐阅读更多精彩内容