美文网首页
Objective-C对象的本质

Objective-C对象的本质

作者: kalpa_shock | 来源:发表于2018-07-31 17:50 被阅读0次

    1. 要研究Objective-C对象的本质,针对NSObject进行一探究竟

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

    终端执行这段代码将.m文件转换成成c++文件

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-cpp.cpp
    
    屏幕快照 2018-07-31 下午1.39.34.png
    //在cpp文件中找到main函数的实现:如下
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        }
        return 0;
    }
    
    
    //这是Class的定义, 可以看到这是一个指向结构体的指针
    typedef struct objc_class *Class;
    //这是转成C++语言的结构体
    struct NSObject_IMPL {
        Class isa;
    };
    //这是Xcode里面点击NSObject 直接看到的声明
    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    
    从而得出 屏幕快照 2018-07-31 下午2.53.55.png
    可以看到NSObject类的本质是结构体
    拥有一个指向typedef struct objc_class 的结构体指针
    

    2: NSObject所占内存大小

    我们可以看到NSObject内部就只有一个成员变量isa指针
    一个指针在系统中所占用内存大小为:
    指针变量在内存中所占空间的大小与变量类型并没有关系,只与操作系统位数有关,64位系统占8字节,32位系统占4字节。
    苹果官方资料宣布iOS7.x的SDK支持了64位的应用,而且内置的应用都已经是64位。
    所以当前得出指针大小为8位

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            NSObject *obj = [[NSObject alloc] init];
            
            //sizeof 是一个运算符
            NSLog(@"sizeof - %zd",sizeof(obj));
            //获取类的实例对象的成员变量所占用的大小
            NSLog(@"class_getInstanceSize - %zd",class_getInstanceSize([NSObject class]));
            //获取指针指向的内存大小
            NSLog(@"malloc_size - %zd",malloc_size((__bridge const void *)(obj)));
            
        }
        return 0;
    }
    
    //运行之后打印信息如下:
    2018-07-31 15:25:48.385385+0800 oc[54118:2818629] sizeof - 8
    2018-07-31 15:25:48.385668+0800 oc[54118:2818629] class_getInstanceSize - 8
    2018-07-31 15:25:48.385703+0800 oc[54118:2818629] malloc_size - 16
    Program ended with exit code: 0
    

    可以看出指针指向的内存大小为16个字节
    分析一下:
    因为Objective-C的对象本质是结构体, 但是结构体拥有自己的内存对齐

    结构体内存对齐:
    因为为了CPU能够快速访问,提高访问效率,变量的起始地址应该具有某些特性,这就是所谓的“对齐”。比如4字节的int型变量,那它的起始地址就应该在4字节的边界上,即起始地址可以被4整除。

    内存对齐的规则很简单:

    1. 起始地址为该变量类型所占内存的整数倍,若不足则不足部分用数据填充至所占内存的整数倍。
    2. 该结构体所占总内存为结构体成员变量中最大数据类型的整数倍。

    根据结构体内存对齐我们可以计算出8个字节是符合内存对齐的
    **???那么为什么是16个字节呢? **
    在看源码中,CoreFoundation中硬性规定分配内存为16

    屏幕快照 2018-07-31 下午3.57.28.png

    那么可以得出一个NSObject对象占用的内存为16个字节, 其中有8个字节保存着isa指针, 剩余8个为多出来的内存空间

    3. 看下一个问题

    @interface Cat : NSObject
    {
        int _age;
        int _weight;
        int _height;
    }
    @end
    
    @implementation Cat
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            Cat *cat = [[Cat alloc] init];
            
            //sizeof 是一个运算符
            NSLog(@"sizeof - %zd",sizeof(cat));
            //获取类的实例对象的成员变量所占用的大小
            NSLog(@"class_getInstanceSize - %zd",class_getInstanceSize([Cat class]));
            //获取指针指向的内存大小
            NSLog(@"malloc_size - %zd",malloc_size((__bridge const void *)(cat)));
            
        }
        return 0;
    }
    

    我们按照上面说的研究下这个cat所占用内存大小
    cat这个对象所拥有成员变量有,我们用clang一下看一下

    struct NSObject_IMPL {
        Class isa;
    };
    
    struct Cat_IMPL {
        struct NSObject_IMPL NSObject_IVARS;//父类结构体
        int _age;
        int _weight;
        int _height;
    };
    

    这样看下来cat实例对象所拥有成员变量有

    • Class isa; 占用8个字节
    • int _age;占用4个字节
    • int _weight;占用4个字节
    • int _height;占用4个字节
      这样计算下来有20个字节, 按照结构体内存对齐,那么我们计算下来是24个字节.那我们来看一下log
    2018-07-31 16:41:24.479647+0800 oc[54772:2907708] sizeof - 8
    2018-07-31 16:41:24.479959+0800 oc[54772:2907708] class_getInstanceSize - 24
    2018-07-31 16:41:24.479989+0800 oc[54772:2907708] malloc_size - 32
    Program ended with exit code: 0
    

    可以看到是32个字节, 那我们看一下objc的源码,探究下为什么会是分配了32个字节内存

    屏幕快照 2018-07-31 下午3.47.05.png
    //其中这两句代码
    extraBytes这个值传过来的时候为0
    size_t size = cls->instanceSize(extraBytes);
    
    obj = (id)calloc(1, size);
    
    //解释 cls->instanceSize(extraBytes)
    
    size_t instanceSize(size_t extraBytes) {
    
            size_t size = alignedInstanceSize() + extraBytes;
    //alignedInstanceSize() 这个函数
            // CF requires all objects be at least 16 bytes.
            if (size < 16) size = 16;
            return size;
        }
    
    //解释alignedInstanceSize()
    class_getInstanceSize(Class)这个函数也调用者个函数
    
    size_t class_getInstanceSize(Class cls)
    {
        if (!cls) return 0;
        return cls->alignedInstanceSize();
    }
    

    我们刚才打印出来class_getInstanceSize([Cat class])是24,也就是说是obj = (id)calloc(1, size);这句代码使内存从24变成了32,

    void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
    

    calloc() 这个函数声明在stdlib库里面,那我们找到这个库的源码看一下

    void *
    calloc(size_t num_items, size_t size)
    {
        void *retval;
            //发现调动了malloc_zone_calloc这个函数
        retval = malloc_zone_calloc(default_zone, num_items, size);
        if (retval == NULL) {
            errno = ENOMEM;
        }
        return retval;
    }
    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;
    }
    
    

    这一段代码过于复杂了,看这个宏定义

    #define NANO_MAX_SIZE           256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
    

    我们在像iOS操作系统申请内存的时候,操作系统也有自己的一套内存对齐, 操作系统有最优的分配内存方式, 是按照16的倍数去进行分配内存.

    4: 还有一种情况,直接看代码

    
    @interface Animal : NSObject
    {
        int _name;
    }
    @end
    
    @implementation Animal
    
    @end
    
    @interface Cat : Animal
    {
        int _weight;
    }
    @end
    
    @implementation Cat
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            Cat *cat = [[Cat alloc] init];
            
            //sizeof 是一个运算符
            NSLog(@"sizeof - %zd",sizeof(cat));
            //获取类的实例对象的成员变量所占用的大小
            NSLog(@"class_getInstanceSize - %zd",class_getInstanceSize([Cat class]));
            //获取指针指向的内存大小
            NSLog(@"malloc_size - %zd",malloc_size((__bridge const void *)(cat)));
            
        }
        return 0;
    }
    
    

    我们来分析下cat这个对象系统分配了多少内存,
    这里分析的是结构体所占用内存

    struct NSObject_IMPL {
        Class isa; //8个字节
    };
    struct Animal_IMPL {
        struct NSObject_IMPL NSObject_IVARS; //8个字节
        int _name; //4个字节
    }; // 结构体内存对齐  这个结构体为 16个字节
    struct Cat_IMPL {
        struct Animal_IMPL Animal_IVARS;//16个字节
        int _weight; //4个字节
    };// 结构体内存对齐  这个结构体为 32个字节
    

    我们看一下结果

    2018-07-31 17:29:49.638592+0800 oc[55144:2974294] sizeof - 8
    2018-07-31 17:29:49.638833+0800 oc[55144:2974294] class_getInstanceSize - 16
    2018-07-31 17:29:49.638856+0800 oc[55144:2974294] malloc_size - 16
    Program ended with exit code: 0
    

    我们看到我们又错了, 系统分配了16个字节, 并不是我们分析的32个字节,我们看一下系统分配的空间都都放置了哪些数据
    系统分配内存时候,其中成员变量的内存块是连续的, 那我们从isa分配了8个是0x000000b1-0x000000b8, _name是从0x000000b9-0x000000bc, _weight是0x000000bd-0x000000c0

    -

    可以看到系统分配内存会看该类的成员占用内存最优分配内存

    相关文章

      网友评论

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

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