OC转换成C/C++
我们都知道平时编写的Objective-C
代码,底层实现其实都是C\C++
代码;
Objective-C
被编译过程是:Objective-C--> C/C++ --> 汇编 --> 机器语言
;
所以Objective-C
的面向对象都是基于C\C++
的数据结构实现的;
如果要想看C++
文件长什么样子?我们可以使用命令将OC代码转换成C/C++
代码。
命令行如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
一个OC对象在内存中是如何布局的?
拿NSObject
来说,创建一个对象NSObject *obj = [[NSObject alloc]init];
按住cmd直接查看
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
仅仅看到一个Class
类型的isa
,转换成C++
文件再查看一下:
struct NSObject_IMPL {
Class isa;
};
是的,OC
对象转化成C++
代码后就是通过结构体
的形式存储在内存中的。一个对象可能多个不同类型的属性,所以以结构体的形式存储。
对于结构体来说,和数组一样。其第一个成员的地址,即为结构体对象的地址。 所以一个OC
对象的地址,实际上就是其isa
指针的地址。
如果这个类中有多个成员,会是什么样子呢?
@interface Cat : NSObject
{
NSString *_name;
int _age;
}
@end
@implementation Cat
@end
转换后:
struct Cat_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
int _age;
};
其中NSObject_IMPL是:
struct NSObject_IMPL {
Class isa;
};
汇总后:
struct Cat_IMPL {
Class isa;
NSString *_name;
int _age;
};
所以执行下面代码的时候:
Cat *cat = [[Cat alloc]init];
cat->_age = 10;
cat->_name = @"Tom";
可以看出cat
这个实例对象是个指针,指向了一个struct Cat_IMPL
结构所对应的内存空间;这个内存空间存储着实例对象各个成员变量的值;
这个内存空间的地址,就是该内存空间第一个元素的内存地址Class isa
;而isa
指针指向Cat
这个类的类对象。
我们验证一下,将cat
指针通过桥接方式(C语法)指向struct Cat_IMPL
:
Cat *cat = [[Cat alloc]init];
cat->_age = 10;
cat->_name = @"Tom";
struct Cat_IMPL *catImpl = (__bridge struct Cat_IMPL *)cat;
NSLog(@"name is %@, age is %d", catImpl->_name, catImpl->_age);
打印结果:name is Tom, age is 10
所以说:Cat
这个类,转换成C++就是struct Cat_IMPL
这个结构体,而cat
指针就指向一个这样结构的结构体。
实例对象占用多少空间?
cat
这个对象所占用的内存空间也显而易见了:一个isa指针8个字节,一个name指针8个字节,一个int型4个字节,所以这个对象只需要20个字节来存储;真是这样吗?
结构体也有自己的对齐方式,结构体大小都是最大成员大小的的整数倍 但是考虑到结构体对齐,此处会有 8*3 = 24个字节;
但但是,OC中有自己的对齐方式,iOS系统分配内存时,都是16 * N,便于CPU快速访问,所以cat
对象实际分配了32个字节;
查看占用空间的方式:
创建一个实例对象,至少需要多少内存
class_getInstanceSize([NSObject class])
创建一个实例对象,实际分配多少内存
malloc_size((__bridge const void *)obj)
OC中的对象
主要可以分为3种
instance
对象(实例对象)class
对象(类对象)meta-class
对象(元类对象)
实例对象
就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
创建两个实例对象
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
obj1 obj2
是不同的两个对象,分别占据着两块不同的内存,一个类在内存中的实例对象可以有多个。
实例对象在内存存储的信息包括:isa指针 、 其他成员变量的值
类对象
每个类只有一个类对象,也只有一个元类对象。
类对象,内存中只要一份
Class objectClass1 = [obj1 class];
Class objectClass2 = object_getClass(obj1);
Class objectClass3 = [NSObject class];
NSLog(@"objectClass1 = %p",objectClass1);
NSLog(@"objectClass2 = %p",objectClass2);
NSLog(@"objectClass3 = %p",objectClass3);
打印结果
objectClass1 = 0x7fff88a67e08
objectClass2 = 0x7fff88a67e08
objectClass3 = 0x7fff88a67e08
class方法返回的一直是类对象
class
方法源码:
-(Class){
return self->isa;
}
+ (class){
return self;
}
验证一下
Class objectClass4 = [[NSObject class]class];
Class objectClass5 = [[[NSObject class]class]class];
NSLog(@"objectClass4 = %p",objectClass4);
NSLog(@"objectClass5 = %p",objectClass5);
打印结果
objectClass4 = 0x7fff88a67e08
objectClass5 = 0x7fff88a67e08
类对象主要包括:
isa指针
、superclass指针
、属性信息
、对象方法信息
、协议信息
、成员变量信息
元类对象
元类对象和类对象内存结构是一样的,但用途不一样。同样包含:
isa指针
、superclass指针
、属性信息
、对象方法信息
、协议信息
、成员变量信息
Class objectMetaClass = object_getClass([NSObject class]);
objectMetaClass是NSObject的meta-class对象(元类对象)
每个类在内存中有且只有一个meta-class对象
补充一下:
object_getClass(id obj)
传入的obj
可能是instance对象
、class对象
、meta-class对象
返回值:
-
如果是instance对象,返回class对象
-
如果是class对象,返回meta-class对象
-
如果是meta-class对象,返回NSObject(基类)的meta-class对象
实例对象、类对象、元类对象三者的关系
说道三者的关系就不得不提网上流传着那张图
总体来说:
- 实例对象(
instance
)的isa
指向类对象(class
) - 类对象(
class
)的isa
指向元类对象(meta-class
) - 元类对象(
meta-clsaa
)的isa
指向基类的元类(meta-class
) - 类对象的
superclass
指向父类的类对象(class
),如果没有父类,superclass
指针为nil
- 元类(
meta-class
)的superclass
指向父类的元类(meta-class
),基类元类(meta-class
)的superclass
指向基类的class
instance
调用方法轨迹:isa
找类对象,方法不存在,就通过superclasss
找父类,调用父类的实例方法,依次找下去
补充:
- 对象方法、属性、成员变量、协议信息,存放在
class
对象中 - 类方法存放在
meta-class
中 - 而成员变量的具体值才存放着
instance
对象中
关于基类元类的superclass指针为什么在找不到方法的时候会指向基类的类对象?
这是因为OC
在调用方法的时候实际上是转换为C/C++
去底层实现的;
但是C/C++
的底层实现并没有区分类方法还是对象方法 也就是没有区分+ -
号
比如[NSObject test]
实际上是转换为了
objc_msgSend([NSObject class], @selector(test))
没有区分+ -
号 所以在基类元类对象没有找到对应的类方法后,会去基类的类对象中查看是否有同名的对象方法,有的话就调用,再没有的话就进入动态方法解析、消息转发或者直接报错。
以上若有错误,欢迎指正。转载请注明出处。