iOS-Objective-C对象的本质

前言:本文简述Objective-C基础知识,如有错误请留言指正。

Q:Objective-C的本质

A:Objective-C->C\C++->汇编语言->机器语言

  • Objective-C的面向对象都是基于C\C++的数据结构实现的
  • Objective-C的对象、类主要是基于C\C++的结构体数据结构实现的
  • Objective-C的对象内存至少是16字节
  • 内存对齐:结构体的大小必须是最大成员变量大小的倍数

将Objective-C代码转换为C\C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
如果需要链接其他框架,使用-framework参数。比如-framework UIKit

Q:一个OC对象在内存中如何布局的

例:NSObject

NSObject *obj = [[NSObject alloc] init];

底层实现

@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指针
//64位下 占8个字节;32位下占4个字节
typedef struct objc_class *Class;

结构体只有isa成员变量,isa地址就是结构体的地址。


NSObject底层实现

Q:一个NSObject对象占用多少内存?

A:系统分配了16个字节给NSObject对象(通过malloc_size函数获得)

但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

#import <objc/runtime.h>
#import <malloc/malloc.h>

NSObject *obj = [[NSObject alloc] init];
//获取NSObject实例对象的成员变量所占用的大小 : 8
NSLog(@"%zd",class_getInstanceSize([NSObject class]));

//获取*obj指针所指向内存的大小:16
NSLog(@"%zd",malloc_size((__bridge const void *)obj));

Q:例如一个student继承自NSObject,内部包含两个int变量,那么student占用多少内存

A:占用内存大小 16

  • 子类的成员变量:父类成员变量+子类成员变量
  • isa指针地址就是该结构体的内存地址
student的本质

1.NSObject是占用16字节,isa指针占用8字节,余下为空
2.Student继承自NSObject,isa指针占用8个字节,int变量占用4个字节,所以共占用 isa(8) + _no(4) + _age(4) = 16
3.可使用malloc_sizeclass_getInstanceSize验证,结果皆16个字节

Student底层实现

Q:一个Person对象(包含一个_age(int)实例变量),一个Student对象继承自Person对象(包含一个_no(int)),各占用多少内存空间

A:都占用内存大小为16

  • Person对象占用:16; isa(8) + _age(4) < 16
  • Student对象占用:16;isa(8) + _age(4) + _no(4) = 16
    Person、Student本质

Q1:一个Student对象继承自NSObject对象(包含_no(int)、_age(int)),占用多少内存空间

A1:分配空间为16

@interface Student : NSObject
{
    int _age;
    int _no;
}
@end

@implementation Student
@end

Student *stu = [[Student alloc] init]; 
NSLog(@"stu:%zd",class_getInstanceSize([Student class]));
NSLog(@"stu:%zd",malloc_size((__bridge const void *)stu));

输出结果:stu:16 stu:16
不做多余解释

Q2:一个Student对象继承自NSObject对象(包含_no(int)、_age(int)、_height(int)),占用多少内存空间

A2:分配内存32

@interface Student : NSObject
{
    int _age;
    int _no;
    int _height;
}
@end
Student *stu = [[Student alloc] init];
NSLog(@"stu:%zd",class_getInstanceSize([Student class]));
NSLog(@"stu:%zd",malloc_size((__bridge const void *)stu));

输出结果:stu:24 stu:32

解答:
所需内存解释如下:

转化C/C++
struct Student_IMPL{
    struct NSObject_IMPL NSOBJECT_IVARS;//8byte
    int _age;//4byte
    int _no;//4byte
    int _height;//4byte
}
//总共需要8+4+4+4=20;内存对齐,isa指针8字节,需要内存为24字节,结构体需要24个字节

分配内存解释如下:
在Runtime的allocWithZone层层追踪下,最终分配内存地址指向void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

runtime 追踪源码

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}
#define --------------------------------------------------
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
#define --------------------------------------------------
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
#define --------------------------------------------------
void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

#define ------------------------分配内存--------------------------
uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    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;
    }

继续追踪calloc,找到库libmalloc
下载地址:https://opensource.apple.com/tarballs/

void *
calloc(size_t num_items, size_t size)
{
    void *retval;
    retval = malloc_zone_calloc(default_zone, num_items, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}

#define -------------------------------------------------
void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
    void *ptr;
    size_t alloc_size;
    if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
        internal_check();
    }
    if (os_mul_overflow(num_items, size, &alloc_size) || alloc_size > MALLOC_ABSOLUTE_MAX_SIZE){
        errno = ENOMEM;
        return NULL;
    }

    ptr = zone->calloc(zone, num_items, size);
    
    if (malloc_logger) {
        malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
                (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
    }
    return ptr;
}

综上:源码追踪难读懂,总结为:
结构体计算内存大小,存在内存对齐,按照isa(8byte)进行对齐
操作系统分配内存时候也存在内存对齐,iOS 在堆空间分配内存都是16的倍数
class_getInstanceSize([Student class]):至少需要24字节
malloc_size((__bridge const void *)stu):实际分配32字节

Q3:Person继承自NSObject(内含3个int成员变量);Student继承自Person(内含8个int成员变量),问各实例变量分配内存多少

A3:Person分配32字节;Student分配64字节

@interface Person : NSObject
{
    int _age;
    int _name;
    int _bro;
}
@end

@implementation Person
@end

@interface Student : Person
{
    int _dog;
    int _cat;
    int _book;
    
    int _phone;
    int _bicycle;
    
    int _hat;
    int _clothes;
    int _pants;
}

@end

@implementation Student
@end

Person *p = [[Person alloc] init];
NSLog(@"p:%zd",class_getInstanceSize([Person class]));
NSLog(@"p:%zd",malloc_size((__bridge const void *)p));

Student *stu = [[Student alloc] init];
NSLog(@"stu:%zd",class_getInstanceSize([Student class]));
NSLog(@"stu:%zd",malloc_size((__bridge const void *)stu));

输出结果:
p:24
p:32
stu:56
stu:64

Q3内存图解

其他

实时查看内存数据

Debug -> Debug Workfllow -> View Memory (Shift + Command + M)


View Memory
常用LLDB命令
  • 打印:print、p
  • 打印对象:po
  • 格式:x是16进制,f是浮点,d是10进制
  • 字节大小:
    b:byte 1字节,h:half word 2字节
    w:word 4字节,g:giant word 8字节
  • 修改内存中的值:memory write 内存地址 数值
memory  write  0x0000010  10
  • 读取内存:memory read/数量格式字节数 内存地址
x/数量格式字节数  内存地址
x/3xw  0x10010
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341