runtime使用篇: class_getInstanceSize 和 苹果对内存的优化

size_t class_getInstanceSize(Class cls)

说明:该函数的作用是获取类的实例所占用内存的大小(单位是字节)

1. 以 NSObject 类为例,代码示例如下:
size_t size = class_getInstanceSize([NSObject class]);
NSLog(@"%zu", size);

在模拟器iPhone 4s和iPhone 5上运行打印结果是4,在iPhone 5s及之后的机型运行打印结果是8。

分析:Objective-C中,每个对象内部都有一个 isa 指针,指向该对象的类;一个类从另一角度来说也是一个对象,类也有一个 isa 指针,指向该类的元类。
因此上述代码打印出的结果,就是 NSObject 实例中 isa 指针所占用的字节数。

至于不同机型打印结果不同,是因为目标系统位数的不同:iPhone 5s开始使用了64位的处理器及系统;和使用Xcode的电脑系统位数无关(iOS开发)。一个指针所占用的字节数在32位系统上为4字节,在64位系统上为8字节。其他基本数据类型在32位和64位系统中所占用的字节数也有所差异:

32位系统下:
char a; // 1
short b; // 2
int c; // 4
long d; // 4
float e; // 4
long long f; // 8
double g; // 8
int *h; // 4
64位系统下:
char a; // 1
short b; // 2
int c; // 4
long d; // 8
float e; // 4
long long f; // 8
double g; // 8
int *h; // 8

可见32位和64位系统中只有 long指针 类型所占用的字节数不同。

2. 以其他类为例(以下均在64位的目标系统上进行测试)

(1). 自定义类 Person 类及代码示例如下

// Person.h
@interface Person : NSObject
/** name */
@property (nonatomic, copy) NSString *name;
/** age */
@property (nonatomic, assign) int age;
@end

// Person.m
@interface Person ()
{
    NSString *childhoodName;
}
@end

// ViewController.m
size_t size = class_getInstanceSize([Person class]);
NSLog(@"%zu", size);

打印结果为32,这是内部的 isa 指针和两个 NSString 指针、一个 int 类型一共占用的字节数。刚才测试的 int 类型在64位系统下所占字节数为4,为什么现在和3个指针所占用的字节数加起来是32呢?这是因为『字节对齐』的原因(译文1译文2原文)。

(2). 自定义类 LittlePerson 类及代码示例如下
当一个类继承其他类时,会继承此类的所有属性和实例变量

// LittlePerson.h
@interface LittlePerson : Person
/** 学校 */
@property (nonatomic, copy) NSString *school;
/** 玩具 */
@property (nonatomic, strong) NSArray *toys;
/** 书 */
@property (nonatomic, assign) short bookCount;
@end

// LittlePerson.m
@interface LittlePerson ()
@end

// ViewController.m
size_t size = class_getInstanceSize([LittlePerson class]);
NSLog(@"%zu", size);

打印结果为56。

要注意的是 class_getInstanceSize 函数和 sizeof() 并不等同

size_t size = class_getInstanceSize([UIViewController class]);
NSLog(@"InstanceSize: %zu", size);

UIViewController *vc = [[UIViewController alloc] init];
NSLog(@"sizeof: %ld", sizeof(vc));
NSLog(@"sizeof: %ld", sizeof([UIViewController class]));

打印结果如下:

runtime[57907:5849922] InstanceSize: 696
runtime[57907:5849922] sizeof: 8
runtime[58021:5856762] sizeof: 8
3. 从字节对齐看苹果对内存的优化

Person 类中增加几个属性和实例变量,其中标号为数字1、2、3的是上述代码中已有的,变量名为单个字母(a--h)的是新增加的。代码示例如下:

// Person.h
@interface Person : NSObject
/** name */
@property (nonatomic, copy) NSString *name; // 1
/** age */
@property (nonatomic, assign) int age; // 2
/** other */
@property (nonatomic, copy) NSString *a;
@property (nonatomic, assign) int b;
@property (nonatomic, assign) char *c;
@property (nonatomic, assign) short d;
@end

// Person.m
@interface Person ()
{
    NSString *childhoodName; // 3
    short e;
    char f[8];
    NSString *g;
    int h;
}
@end

// ViewController.m
size_t size = class_getInstanceSize([Person class]);
NSLog(@"InstanceSize: %zu", size);

我们先猜想一下打印结果。通过学习刚才链接中的字节对齐的知识,可以推测结果是96,我们先不公布打印结果,先按正常的字节对齐看看96是否正确。在 ViewController.m 中增加以下代码,一一对应 Person 实例中的属性和实例变量:

struct people {
    char *isa;
    char *name;
    int age;
    char *a;
    int b;
    char *c;
    short d;
    char *childhoodName;
    short e;
    char f[8];
    char *g;
    int h;
};

struct people p;
NSLog(@"sizeof:%ld", sizeof(p));

本次打印结果为96,对字节对齐的知识有所掌握😃
再回到刚才 Person 实例,打印结果如下:

runtime[58897:5911521] InstanceSize: 80

推测的并不正确。
如果手动对属性和成员变量『共同』重排进行优化,对应的 people 结构体如下:

struct people {
    char *isa;
    char *name;
    char *a;
    char *c;
    char *childhoodName;
    char *g;
    char f[8];
    int age;
    int b;
    int h;
    short d;
    short e;
};

struct people p;
NSLog(@"sizeof:%ld", sizeof(p));

此时打印结果为72,和 Person 实例所占用内存的大小仍不一样。
现在手动对属性和成员变量『分别』重排进行优化,对应的 people 结构体如下:

struct people {
    char *isa;
    char *name;
    char *a;
    char *c;
    int age;
    int b;
    short d;
    
    char *childhoodName;
    char *g;
    char f[8];
    int h;
    short e;
};

struct people p;
NSLog(@"sizeof:%ld", sizeof(p));

此时打印结果为80,和 Person 实例所占用内存的大小相同。可见:
苹果会自动对属性和成员变量进行分别重排从而优化内存
这一切不需要我们手动去优化,大大地节省了程序员的时间。
很早之前就听说苹果会变动一些代码的顺序进行优化,今天总算知道会出现在哪里了😃

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

推荐阅读更多精彩内容