iOS 实例对象的本质

当你初始化一个对象的时候,其内部实现又是怎么样的呢?
先说结论:
1、创建一个对象的时候,本质是生成一个结构体,包含
[1]、isa指针(isa指针指向类对象)
[2]、成员变量
2、对象的内存分配大小,是结构体分配内存(对象实际需要的内存)之后再进行一次内存对齐,为16的倍数
3、对象的方法,在类的方法列表里面

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

创建一个空工程,写入如下代码

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
    }
    return 0;
}

点击进入NSObject,可以看到如下代码

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

删除无用代码后:

@interface NSObject <NSObject> {
    Class isa;
}

我们cd到main.m文件目录下,将OC代码转为C++代码:

clang -rewrite-objc main.m -o main.cpp

其中:main.m为文件名称,main.cpp是指最终生成文件的名称(如果不写-o main.cpp,则默认生成同名的cpp文件)
由于直接生成cpp文件包含各种架构,代码量较大,我们可以生成指定架构类型,这里我使用的是arm64架构类型【模拟器(i386),32bit(armv7),64bit(arm64)】。再调用:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

打开生成的cpp文件之后,可以看到NSObject对象本质实际上是包含isa指针的结构体

struct NSObject_IMPL {
    Class isa;
};

Class实际上是结构体指针,结构为:

typedef struct objc_class *Class;

由源码可以看到,结构体NSObject_IMPL的地址,实际上就是isa的地址,我们创建的objc对象,指针指向的其实是isa的地址。调用class_getInstanceSize和malloc_size可以窥探内存情况

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

// Class:  typedef struct objc_class *Class;
struct NSObject_IMPL {
    Class isa;  //  8个字节
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];

        //  获取NSObject实例对象的成员变量所占用的大小  8
        NSLog(@"%zd" , class_getInstanceSize([NSObject class]));
        //  获取objc指针所指向内存的大小(内存分配大小)   16
        NSLog(@"%zd", malloc_size((__bridge  const void *)objc));
    }
    return 0;
}

当设计个类,继承自NSObject,包含一个属性

#import <Foundation/Foundation.h>

@interface OneClass : NSObject
{
    int _one;
}
@end

@implementation OneClass
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        OneClass *oneClass = [[OneClass alloc] init];
    }
    return 0;
}

编译后,如下所示:

struct NSObject_IMPL {
    Class isa;
};
struct OneClass_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _one;
};

简化后相当于oneClass对象最终转化为结构体OneClass_IMPL:

struct OneClass_IMPL {
    Class isa;
    int _one;
};

下载objc源码,当我们执行[NSObject alloc]的时候,内部实现为:

//  NSObject.mm文件内
+ (id)alloc {
    return _objc_rootAlloc(self);
}
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

解读源码:
1、当我们执行alloc,实际执行_objc_rootAlloc(self)
2、_objc_rootAlloc,实际调用callAlloc(cls, false/checkNil/, true/allocWithZone/),并且传递的第三个参数为true,即参数:allocWithZone=true,进入callAlloc代码,可以看到执行allocWithZone:方法
3、+ (id)allocWithZone:(struct _NSZone *)zone调用了_objc_rootAllocWithZone
4、全局搜索,找到_objc_rootAllocWithZone在objc-runtime-new.mm中

//  objc-runtime-new.mm
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

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

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

//  *********分配内存********
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }

    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

size = cls->instanceSize(extraBytes);
该函数获取内存分配大小

// Class's ivar size rounded up to a pointer-size boundary.
//  返回类的成员变量大小
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

//  创建实例对象最少创建的大小
inline size_t instanceSize(size_t extraBytes) const {
    //  快查找,(从缓存查找)
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    // 要求所有对象至少为16字节。
    if (size < 16) size = 16;
    return size;
}

计算好的size,通过执行malloc_zone_calloc或calloc,进行内存对齐后返给obj(libmalloc-317.140.5版本中calloc即调用malloc_zone_calloc)

id obj;
if (zone) {
   obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
   obj = (id)calloc(1, size);
}

malloc_zone_calloc和calloc可以查看libmalloc库源码,找到malloc.c文件,该函数即对前面计算好的size进行内存对齐

void *
calloc(size_t num_items, size_t size)
{
    return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
        malloc_zone_options_t mzo)
{
    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

    void *ptr;
    if (malloc_check_start) {
        internal_check();
    }

    ptr = zone->calloc(zone, num_items, size);

    if (os_unlikely(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);
    }

    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
    if (os_unlikely(ptr == NULL)) {
        malloc_set_errno_fast(mzo, ENOMEM);
    }
    return ptr;
}

该代码过于难懂,各种搜索之后,得出结论,内存对齐是16的倍数(malloc为通用创建内存方式,带-malloc的文件都为各种情况下的内存创建方式)

#define NANO_MAX_SIZE           256 /* Buckets sized {16, 32, 48, ..., 256} */
#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16
#define NANO_QUANTA_MASK        (NANO_REGIME_QUANTA_SIZE - 1)
#define NANO_SIZE_CLASSES       (NANO_MAX_SIZE/NANO_REGIME_QUANTA_SIZE)*/

苹果官方文档
https://opensource.apple.com/tarballs/
objc源码
https://opensource.apple.com/tarballs/objc4/
libmalloc库源码
https://opensource.apple.com/tarballs/libmalloc/

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

推荐阅读更多精彩内容