上周同事提到了二进制兼容性,这篇文章主要是想记录下non fragile ivars如何做到动态调整实例变量,实现Objective-C Binary的兼容性。non fragile ivars布局如何调整,编译器和runtime中间起了什么样的角色。此外,还会讲解下实例的布局情况。
上周五电脑屏幕坏了送修,今天准备把上周的东西补上。
关于Non Fragile ivars在寻找资料的过程中发现了一篇讲解的很好的一篇文章,我这边就直接引用了,有兴趣的可以直接查看,我这边就不献丑了Objective-C类成员变量深度剖析,该篇文章从编译器和runtime两方面来展开,支持二进制的稳定性。
这边的话直接从实例对象的布局说起吧。有人会问, 为什么要讲这块内容,其实实例布局和上述的non fragile ivars息息相关,non fragile ivars其实本质是就是对实例变量进行重新布局,对实例中的每个成员变量的offset进行修改,再而调整其内存大小。
其实我们常说的实例对象的内存大小,指的是一块内存区域的大小,其中包含了isa指针和所有的成员变量,与其他的因素并没有关系。其中方法定义是在objc_class中管理的,所以不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。
下面举几个例子来验证上述的观点。
eg1:
NSObject *obj = [[NSObject alloc] init];
/// 其内存大小为8bytes
我们来分析下,可以从两处来分析,一是直接源码查看
/// Represents an instance of a class.
struct objc_object { // from objc
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
@interface NSObject <NSObject> { // from NSObject.h
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
二是clang解析成c++,也会看到一样的结构体,
struct NSObject_IMPL {
Class isa;
};
从一二我们可以发现,nsobject的实例对象所占的内存大小其实就是isa指针所占用的大小,指针所占用的大小只跟操作系统有关,32位是4字节,64位则是8字节,与上述控制台打印的结果不谋而合.
eg2:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface Student : NSObject{
@public
int _no;
int _age;
}
- (void)studInMethod;
@end
@implementation Student
- (void)studInMethod {
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu -> _no = 4;
stu -> _age = 5;
// 输出对象占用内存大小
NSLog(@"%zd",class_getInstanceSize([Student class]));
}
return 0;
}
聪明的你是否已猜到结果呢,输出结果是16.
同样的,解析成c++,
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
其中,struct NSObject_IMPL NSObject_IVARS
就是我们上面提到的isa指针,所以等同于
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
所以占用内存大小为16字节当然没任何问题。
细心的读者是否有看到这个类中我声明了一个实例方法,但是在计算内存大小中,并没有把该方面计算在内,这也证明了方法与内存布局没有关系。
eg3:
@interface Student : NSObject{
@public
int _no;
}
...省略若干代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu -> _no = 4;
// 输出对象占用内存大小
NSLog(@"%zd",class_getInstanceSize([Student class]));
}
return 0;
}
此类输出又是多少呢?相信很多人会说出12这个数字。没错,我之前也是也是认为是12,如果不看输出结果的话。但是输出结果其实是16
为什么会是16,明明是12,难道上面的分析都是错误的???
其实上述分析的结论都没错,真正用到的内存大小也的的确确是12,输出结果是16其实是内存对齐搞的鬼。
对于student对象来说,student对象的第一个地址要存放isa指针需要8个字节,第二个地址要存放_no成员变量需要4个字节,根据内存对齐的原则,8是4的整数倍,符合,不需要补齐。然后检查第二条原则,student对象共占据12个字节的内存,不是最大字节数8个字节的整数倍,所以需要补齐4个字节,因此student对象就占用16个字节空间。
Done!
网友评论