美文网首页
OC instance 对象本质一

OC instance 对象本质一

作者: 曹来东 | 来源:发表于2018-08-11 12:43 被阅读39次

一个OC对象占用多少内存

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

解答:

  • 系统分配了16个字节给NSObject对象可通过malloc_size函数获得
  • 但是NSObject对象只使用了8个字节(64bit环境下,源码中未区分架构),可通过class_getInstanceSize函数获得

剖析:

该问题问的是obj指针所指向的那块内存空间占用多少内存

我们平时编写的OC代码,底层实现都是C/C++代码.
OC ---> C/C++ ---->汇编语言 ----->机器语言
所以OC面向对象都是基于C/C++的数据结构(结构体)实现的

OC代码通过终端转成C/C++代码来探究NSObject本质.

1.终端进入目标文件所在目录
2.输入指令

clang -rewrite-objc main.m -o main.cpp
//通过clang编译器把main.m文件转成C++文件.因为C++支持C语言,所以转成C++更合适.
//该指令生成的.cpp文件并没有指定是哪个平台的代码,
//同样的OC代码在不同平台转成的C++代码也不同.所以我们需要指定生成那个平台下的C++代码:
//模拟器(i386),32bit(armv7),64bit(arm64)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//指令解读:
//xcrun : xcode run
//iphoneos : 指定平台
//arch arm64 : 指定架构
//main-arm64.cpp : 指定生成目标文件为64位架构下的C++文件.
//指定架构后会发现生成的文件明显比未指定架构的文件要小

当把cpp文件放入项目中会发现编译报错,不让该文件参与编译即可解决.


image.png

3.在原目标文件路径下就可以查看main.cpp文件.

查看.cpp文件技巧

  • 文件检索NSObject的定义 NSObject_IMPL {因为我们知道OC对象本质是基于C和C++的结构体实现的,所以NSObject定义一定是个结构体.这样就可以快速的在大量代码的文件中查找的目标代码.
    OCNSObjcet的定义在C/C++中如下
//NSObject_IMPL : NSObject implementation(定义实现)
struct NSObject_IMPL {
    Class isa;
};

再次查看OC中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 {
    Class isa;
}

总结:NSObject的底层实现:

image.png
查看Class isa:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
//Class是一个`struct objc_class *`类型的结构体指针

结论:

  • OC中的一个 NSObject对象底层实现是一个结构体,该结构体有一个成员变量 Class isa,所以一个NSObject对象占用多少内存就转化成Class isa这个成员变量占用多少内存.
  • Class isa是一个结构体指针,在32位系统上占用4个字节;在64位系统占用8个字节.
  • NSObject结构体的地址值就是该结构体第一个成员变量的地址值,即isa的地址值.

导入<objc/runtime.h> <malloc/malloc.h>检查NSObject对象占用内存大小

//获取NSObject的instance对象成员变量的大小
//class_getInstanceSize(<#Class  _Nullable __unsafe_unretained cls#>)
NSLog(@"%zd",class_getInstanceSize([NSObject class]));//8字节
//获取obj指针所指向内存的大小
//malloc_size(<#const void *ptr#>)
NSLog(@"%zu",malloc_size((__bridge const void *)(obj)));//16字节
//malloc_size接收的参数是一个const void *类型的C语言指针
//所以需要将obj这个OC指针做个桥接转成C语言的指针

我们要注意malloc_size函数传入的是一个指向NSObjectinstance对象的指针,所以malloc_size函数返回的是该指针所指向的对象所分配的内存大小
class_getInstanceSize返回的又是什么?我们可以通过Apple开源的代码(https://opensource.apple.com)来查看该函数的实现,进而了解该函数.我们需要的开源代码地址https://opensource.apple.com/tarballs.进入后检索objc4.进入该目录下会发现有很多开源的代码,我们筛选最新的代码查看,最新的代码一般来说需要根据文件名字 和 该文件所占大小 综合比较选出我们需要的源码.我们此次下载的是objc4-723.tar.gz.下载后检索class_getInstanceSize.实现在.m文件.我们会发现该函数的实现如下:

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

我们发现调用的是clsalignedInstanceSize()方法aligned意为:对准的,均衡的,排整齐

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

结论:

当一个NSObject对象被创建时系统分配的是16个字节,但是该对象只占用了8个字节,因为NSObject对象的底层是一个结构体,该结构体内只有一个结构体指针 Class isa占用8个字节.

通过查看NSObject对象的alloc函数底层实现来验证初始化分配16个字节.

NSObject对象的alloc函数底层调用的是allocWithZone方法.在源码中检索allocWithZone,查看该函数的底层实现:

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

简单来看调用NSObjectalloc函数底层会调用上面方法,返回一个id类型.该函数主要代码在obj = class_createInstance(cls, 0);查看该函数实现:

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

查看_class_createInstanceFromZone

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

主要看下面的内存分配代码:

image.png
查看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 = alignedInstanceSize() + extraBytes;//8字节.alignedInstanceSize()获取成员变量大小
从上面的代码可以看出,一个NSObject对象内存如果小于16字节,就分配16个字节.所以一个NSObject对象至少分配16个字节.

通过Xcode推断该问题答案

image.png
image.png
image.png
上面16个字节的内存空间即代表NSObject的内存占用情况.

字节: Byte
通常1 B(一个字节)有8位.一个位就代表一个二进制的0或1,所以一个字节有八位二进制位,即2的八次幂,1个字节的范围是 0 ~ 255.

一个16进制位代表四个二进制位,两个16进制位代表八个二进制位,即两个16进制位就是一个字节八位二进制数.Xcode显示的是16进制.上图中每两位数字代表一个字节.通过阅读源码我们了解到:一个OC对象一般分配16个字节.
由上图可以看出:
0x102801c80地址值开始到之后的16个字节代表的是NSObject所占用的内存空间,改16个字节中,前八位字节已经被占用,剩余八位都为零.前八位即为isa指针的地址.

简单的lldb指令

打印

image.png
p    print 指令
po 表示把打印目标视为一个OC对象来打印

读取内存

memory read简写 x

image.png
//memory read/数量+格式+字节数   内存地址
//x/数量+格式+字节数   内存地址
//格式:
//x是16进制,f是浮点数,d是十进制
//字节大小:
//b: byte 1字节;h: half word 2字节
//w :word 4字节; g : giant word 8字节
x/3xw 0x10010

十进制 方式 读取内存 (memory read : x) 四串 每串4个字节

image.png
16进制 方式 读取内存 (memory read : x) 四串 每串4个字节
image.png

通过lldb指令修改内存中的数据

将下图指定内存地址的值修改为7 :


image.png

其实位置地址为0x102801c80,目标位置为下数7个字节,所以内存地址为0x102801c87

memory write 0x102801c87 7
image.png
如图可看到 1D后面的数据由00变成了07

相关文章

  • OC instance 对象本质一

    一个OC对象占用多少内存 解答: 系统分配了16个字节给NSObject对象可通过malloc_size函数获得 ...

  • OC instance 对象本质二

    检索Student_IMPL {查看Student结构体实现如下: struct NSObject_IMPL NS...

  • OC instance 对象本质三

    LDPerson底层实现: 所以上图中LDPerson的instance对象占用16字节的内存. 思考下图中LDP...

  • Object-C对象本质

    Object-C对象本质 OC中对象类型 instance 实例对象isa指针其他成员变量 class 类对象is...

  • OC对象的分类

    OC中的对象,简称OC对象,主要可以分为3种: instance对象(实例对象) instance对象就是通过类a...

  • oc对象的种类

    oc对象:instance对象,class对象,meta-class 对象 instance对象 isa 其他成员...

  • iOS-浅谈OC中对象的类型

    目录 OC对象的类型Instance对象(实例对象)----实例(instance)对象内存结构Class对象(类...

  • Runtime基础类型介绍

    Class:类Instance:实例 OC中的对象的实例本质上是 OC中的类本质上是 isa_t的结构是 这是一个...

  • OC对象的分类

    OC对象的分类 - instance对象(实例对象) instance对象就是通过类alloc出来的对象,每次调用...

  • iOS OC对象的本质

    先来一张OC对象的关系图 oc对象关系图 1、oc对象包括instance对象、class对象、metaclass...

网友评论

      本文标题:OC instance 对象本质一

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