美文网首页
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