美文网首页
Non Fragile ivars

Non Fragile ivars

作者: skogt | 来源:发表于2019-07-08 21:03 被阅读0次

上周同事提到了二进制兼容性,这篇文章主要是想记录下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!

相关文章

网友评论

      本文标题:Non Fragile ivars

      本文链接:https://www.haomeiwen.com/subject/tbtzhctx.html