美文网首页
OC对象本质(一)

OC对象本质(一)

作者: dandelionYD | 来源:发表于2018-12-21 14:13 被阅读0次

面向对象

(本文所运行的Demo是在集成了objc4源码基础上的,详见:gitHub

本文所写的项目详见:OCBasicDemo

面试题:

1. 一个NSObject对象占用多少内存?
析:
系统分配了16个字节给NSobject对象(通过malloc函数来获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,通过class_getInstanceSize函数获得)

OC对象的本质(一)

其实我们平时所写的OC代码,底层的实现都是C/C++来完成的

即:OC-->C\C++-->汇编语言-->机器语言  
所以Obiect-C的面向对象都是基于C\C++的数据结构来实现的(也就是**结构体**)

1.一个NSObject对象占用多少内存?
(1)系统分配了16个字节给NSobject对象(通过malloc函数来获得)
(2)但NSObject对象内部只使用了8个字节的空间(64bit环境下,通过class_getInstanceSize函数获得)
验证(至于为什么是8和16我们在下面的时候在来论证):

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        //获得NSObject实例对象的成员变量所占用的大小 >> 8
        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
        
        //获得obj指针所指向内存的大小 >> 16
        //malloc_size(const void *ptr):Returns size of given ptr
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));
    }
    return 0;
}

分析:
1.我们创建一个obj的实例对象,通过class_getInstanceSize获得的字节数是8(实际所需的最小的内存大小)
2.通过malloc_size我们获取的字节数是16(系统给分配的内存大小)


2.一个NSObject实例对象,转为C/C++
补充:将一个OC文件转为C/C++代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的.cpp文件名  
如果需要链接到其他框架,使用-framework参数,比如-framework UIKit

使用:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o myMain.cpp
得到的myMain.cpp

分析:在cpp文件里面发现了这样一个结构体
struct NSObject_IMPL {
    Class isa;
};

对比原来的.m文件
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
  @autoreleasepool {
      NSObject *obj = [[NSObject alloc] init];
}
return 0;
我们在改变下.m文件的内容:
执行:xcrun -sdk iphoneos clang  -arch arm64 -rewrite-objc main.m -o myMain2.cpp

#import <Foundation/Foundation.h>
@interface Person:NSObject
@end

@implementation Person
@end

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

我们在myMain2.cpp中发现:有这么一个结构体
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};
我们按住command+鼠标左键 点击NSObject后发现  
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@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;
}
加上上述的:我们可以知道
NSObject的第一个小底层:
struct NSObject_IMPL {
    Class isa;
};
其中:Class 为:typedef struct objc_class *Class;
具体的里面的isa是啥,我们在后面讨论

我们再来分析下8个字节(实例至少需要的内存的大小)
16个字节的问题(系统实际上分配的内存的大小)
我们通过打断点:

memoryCount_OC2.png

然后实时查看内存:Debug -> Debug Workfllow -> View Memory (Shift + Command + M)
发现如下:

viewMemory_OC1.png

我们可以猜测:
前8个字节是有值的,后8个字节为0值(8个字节是存放实例真正的大小)(系统实际上给该实例分配了16个字节的大小)

接下来我们通过打断点来论证下为什么会是16个字节(集成了objc4源码github
第1步:

memoryCount_OC3.png

第2步:(通过单步走发现会走到下面的方法)

+ (id)alloc {
    return _objc_rootAlloc(self);
}

单步走进入:
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

单步走进入:
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0); //打个端点走到了这里
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
//此处代码省略。。。

我们发现走到了id obj = class_createInstance(cls, 0);该方法

单步走进入:
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}


单步走进入:
static __attribute__((always_inline)) 
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;
//此处代码省略。。。

我们发现走到了size_t size = cls->instanceSize(extraBytes);该方法

单步走进入:
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;
}
这里我们发现:
size_t size:内存对齐值+实例对象的extraBytes
这边系统对于小于16的进行了返回16个字节

单步走进入:
 uint32_t alignedInstanceSize() {
       return word_align(unalignedInstanceSize());
}

进入:
uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;//打个断点 
}
我们发现:
data()->ro->instanceSize 

data():函数为:
 class_rw_t *data() { 
        return bits.data();
 }
 
 返回的是class_rw_t(类的可读可写列表)
 它是一个结构体:(点击进入command+鼠标左键)
 如下
 struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
//此处代码省略。。。

里面我们发现了class_ro_t一个结构体(类只读列表)
结构体如下:
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//重点 
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

里面有一个系统存储的:instanceSize属性值(实例大小)

综上:我们发现NSObject实例对象所需8个字节,但是苹果系统为它分配了16个字节(可能是为了方便系统去更好的计算,分配内存)

友情链接:

相关文章

  • OC 与 Swift

    OC对象的本质(上):OC对象的底层实现原理OC对象的本质(中):OC对象的种类OC对象的本质(下):详解isa&...

  • OC对象的本质(中)—— OC对象的种类

    OC对象的本质(上):OC对象的底层实现原理OC对象的本质(中):OC对象的种类OC对象的本质(下):详解isa&...

  • OC对象的本质(下)—— 详解isa&supercl

    OC对象的本质(上):OC对象的底层实现原理OC对象的本质(中):OC对象的种类OC对象的本质(下):详解isa&...

  • OC对象的本质(上)

    iOS | OC对象本质 | Objective-C 什么是OC语言,OC对象、类的本质是什么,OC对象的内存布局...

  • Runtime:OC对象、类、元类的本质

    零、Runtime是什么一、OC对象的本质二、OC类的本质三、OC元类的本质四、Runtime关于对象、类、元类的...

  • iOS底层isa结构分析

    在介绍正文之前,首先需要理解一个概念:OC对象的本质是什么? OC对象本质 在探索oc对象本质前,先了解一个编译器...

  • block 笔记

    block本质是OC对象(封装了函数调用以及调用环境的OC对象) 本质

  • OC对象原理探究(下)

    介绍正文前,我们思考一个问题,什么是对象?或者说OC对象的本质是什么? 对象本质以及拓展 在探索oc对象本质前,先...

  • iOS底层原理总结-- KVO/KVC的本质

    iOS底层原理总结--OC对象的本质(一) - 掘金 iOS底层原理总结--OC对象的本质(二) - 掘金 iOS...

  • 总结

    主题一《OC对象的本质》=========== 1、OC的本质 Objective-C ----> C\C++ -...

网友评论

      本文标题:OC对象本质(一)

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