iOS-Objective-C对象的本质

作者: 梦蕊dream | 来源:发表于2018-08-08 17:52 被阅读46次

    前言:本文简述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
    

    相关文章

      网友评论

        本文标题:iOS-Objective-C对象的本质

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