1. Objective-C的本质
我们平时编写的OC代码,其实底层实现都是C/C++代码,类主要是基于C/C++的结构体的数据结构实现的,因为对象或者类有各种类型(NSArray *,NSDictionary *,CFfloat
等),因为可以存储不同种类的数据,能够满足的这样的结构就是结构体.
为了证明OC的结构,所以可以转换成C++的代码,窥探内部的结构(有时候C++的代码也不一定能完全表示源码的情况,需要调试到汇编代码或源码查看).
我们可以通过终端进入到要窥探所在文件的位置,使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
.(如果电脑上面安装了多个版本的Xcode,转换为C++代码的时候会提示各种框架找不到的错误,一般是因为多个版本的Xcode路径冲突导致的,我们需要在终端指定一个Xcode的路径,例:sudo xcode-select --switch/Applications/Xcode10.0.app/Contents/Developer/).
注释:解释各种参数的翻译
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文件。
2. NSObject底层实现原理
Class 定义为 :typedef struct objc_class *Class
;也就是说Class是个结构体指针.
代码中[NSObject alloc]
开辟空间给NSObject
。obj的指针指向了isa的地址.isa的地址就是结构体的地址,原因是结构体的地址就是结构体中第一个成员的地址,而结构体只有一个成员,即isa指针的地址.
一. 例:student底层的原理
答:因为Student
继承NSObject
,也就继承了NSObject
的数据结构,所以继承NSObject
的8个字节,也就是NSobject
中的isa的大小。
Person占据class_getInstanceSize=16 malloc_size=16
, Student占据class_getInstanceSize=16 malloc_size=16
,Person的变量实际用了12,但是由于内存对齐所以占用16.
二. 两种方法看内存大小
我们有这种方法在OC中表达一个类内存的大小.
<objc/runtime.h>文件提供class_getInstanceSize(Class _Nullable cls)方法,返回我们一个OC对象
的实例所占用的内存大小(可以说是结构体内存对齐之后的大小,8的倍数);
<malloc/malloc.h>文件提供 size_t malloc_size(const void *ptr)方法返回系统为这个对象分配的
内存大小(16的倍数)。
三. 内存对齐的原理(不全,后期添加)
我们先来看一些内存的例子,更加方便我们去理解内存分配和内存对齐原理:
- 看一个没有成员变量的类的实例(以NSObject为例)
NSObject *obj = [[NSObject alloc] init];
NSLog(@"NSObject实例大小--> %zd",class_getInstanceSize([obj class]));
NSLog(@"obj实际分配的内存%zd",malloc_size((const void *)obj));
// NSObject实例大小--> 8
// obj实际分配的内存16
- 一个普通的类的实例,并且实例有且仅有唯一的成员变量(如:Student只有一个name属性)
@interface Student: NSObject
@property (nonatomic, copy) NSString *name;
@end;
@implementation Student
@end;
Student *stu = [[Student alloc] init];
stu.name = @"Object-C";
NSLog(@"Student实例大小--> %zd",class_getInstanceSize([stu class]));
NSLog(@"stu实际分配的内存%zd",malloc_size((const void *)stu));
// Student实例大小--> 16
// stu实际分配的内存16
- 一个普通的类的实例,并且实例有自己的成员变量(如:Student类,为其添加属性age、name等)
@interface Student: NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end;
@implementation Student
@end;
Student *stu = [Student new];
stu.name = @"Object-C";
stu.age = 25;
NSLog(@"Student实例大小--> %zd",class_getInstanceSize([stu class]));
NSLog(@"stu实际分配的内存%zd",malloc_size((const void *)stu));
// Student实例大小-->24
// stu实际分配的内存32
由以上三次测试:一个OC对象所占用的内存取决于这个对象成员变量的多少.但是同时,系统为其分配内存时,默认会分配最少16个字节的大小.OC中对象的内存小于16就等于16(是Core Foundation的规定),下面是Core Foundation的源码.
size_t instanceSize(size_t extraBytes){
size_t size = alignedInstanceSize()+extraBytes;
//CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
内存对齐的原则:结构体的大小必须是最大成员的倍数.
更多的内存对齐的知识--内存对齐
补充:sizeof不是个函数是个运算符,传入的时候是类型不是具体的对象,sizeof是在编译的时候进行计算的.
3. OC对象的分类
objective-C的对象,简称为OC对象,分为三种:
- instance对象(实例对象).
- class对象(类对象).
- meta-class(元类对象).
一. 实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
object1、object2是NSObject的instance对象(实例对象),它们是不同的两个对象,分别占据着两块不同的内存。instance对象是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象.instance对象在内存中存储的信息包括:isa指针,其他成员变量。
实例对象存放的内容包含:
实例对象 |
---|
isa |
成员变量信息 |
二. 类对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1);//Runtime API
Class objectClass5 = object_getClass(object2);//Runtime API
objectClass1 ~ objectClass5都是NSObject的class对象(类对象).它们是同一个对象,每个类在内存中有且只有一个class对象.
类对象存放的内容包含:
类对象 |
---|
isa |
superclass |
属性信息 |
对象方法信息 |
协议信息 |
成员变量信息 |
............. |
class对象在内存中存储的信息主要包括:isa指针,superclass指针,类的属性信息(@property)、类的对象方法信息(instance method),类的协议信息(protocol)、类的成员变量信息(ivar).
三. 元类对象
获取一个类对象的元类对象的方法.
Class objectMetaClass = object_getClass([NSObject class]);//Runtime API
元类对象 |
---|
isa |
superclass |
类方法信息 |
............. |
objectMetaClass
是NSObject的meta-class
对象(元类对象).每个类在内存中有且只有一个meta-class对象.
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:isa指针,superclass指针,类的类方法信息(class method).
补充:
查看Class是否为meta-class:
BOOL result = class_isMetaClass([NSObject class]);
以下代码获取的objectClass是class对象,并不是meta-class对象
Class objectClass = [[NSObject class] class];
objcget-Class和object-getClass区别
objc_getClass | 传入字符串类名返回类对象. | 传入字符串类名返回类对象. | 传入字符串类名返回类对象. |
---|---|---|---|
object_getClass | 传入实例对象返回类对象. | 传入类对象返回元类对象. | 传入元类对象返回还是元类对象 |
四. isa和superClass
1. isa
①instance的isa指向class,当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用.
②class的isa指向meta-class,当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用.
2. superClass
当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用.
当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用.
3. 经典的isa和superclass图谱
- isa总结
- instance的isa都是指向class.
- class的isa都是指向meta-class.
- meta-class的isa指向基类的meta-class.
- superClass总结
- class的superClass指向父类的class.
- 如果没有父类,superClass指针为nil.
- meta-class的superClass指向父类的meta-class.
- 基类meta-class的superClass指向基类的class.
- instance的调用轨迹
- isa找到class,方法不存在,就通过superclass找父类.
- class调用类方法的轨迹
- isa找meta-class,方法不存在,就通过superclass找父类.
- 基类的meta-class方法不存在,就通过superclass找基类的class,如果没有找到就是nil.
4. isa地址运算
从64bit开始,isa需要进行一次位运算,才能计算出真实地址,superClass存储的地址值,直接就是父类的地址值,不用做位运算.
实例对象里只有成员变量没有方法,为什么实例对象的方法要存在类对象里,原因是只要存一份就够了,实例对象会创建多个.
想了解更多iOS学习知识请联系:QQ(814299221)