MJiOS底层笔记--OC对象本质

本文属笔记性质,主要针对本身理解不太透彻的地方进行记录。ios

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。数组


OC对象本质

基于C与C++结构体实现缓存


OC语言如何被编译器编译:

OC ==> C++ ==> 汇编 ==> 机器语言bash

而在C++中只有struct(结构体)才能容纳不一样类型的内容(好比不一样属性)。架构


将Objective-C代码转换为C\C++代码

  1. clang -rewrite-objc OC源文件 -o 输出的CPP文件

将源文件转写成通用的cpp文件框架

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

经过Xcode将源文件转写成arm64架构下的iphoneos文件,文件内容比第一种要少iphone

  1. 若是须要连接其余框架,使用-framework参数。好比-framework UIKit

NSObject的OC与C++定义

  • 在OC中的定义
@interface NSObject <NSObject> {
    Class isa;
}
复制代码
  • 转成C++以后的定义
struct NSObject_IMPL {
	Class isa;
};
复制代码

对于结构体来讲,和数组同样。其第一个成员的地址,即为结构体对象的地址。 因此一个OC对象的地址,实际上就是其isa指针的地址。函数

而这个isa是指向objc_class结构体的指针布局

// 指针
typedef struct objc_class *Class;
复制代码

而一个指针在64位系统中所占的内存为8字节学习

因此一个OC对象所占的内存至少为8字节


NSObject对象所占用内存的大小

上面的结论经过class_getInstanceSize函数也能够佐证:

#import <objc/runtime.h>

/*
得到NSObject实例对象的
`成员变量`
所占用的大小 >> 8
*/
NSLog(@"%zd", class_getInstanceSize([NSObject class]));


//runtime源码中
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

// Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } 复制代码

须要注意这个word_align返回的是内存对齐后的大小,以unalignedInstanceSize(为对齐的)大小做为参数。

而对于NSObject *obj指针,咱们有另外一个函数能够查看其实际被分配的内存大小

#import <malloc/malloc.h>
// 得到obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
复制代码

为何8字节的结构体会被分配16字节

继续看runtime

+ (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 // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; } // Replaced by ObjectAlloc + (id)allocWithZone:(struct _NSZone *)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } 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; } 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;

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


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;
}
复制代码

alloc函数最终会根据instanceSize返回的size,而后使用calloc(1, size);函数去分配内存。

instanceSize函数中,alignedInstanceSize方法为成员变量所占内存大小(上面已经贴过一次).extraBytes参数(据我所见)都为0。

CoreFoundation框架在instanceSize函数中硬性规定不足16字节的内存地址会被补成16位字节。

但实际上,NSObject对象只使用了8字节用来存储isa指针


Student对象的本质

@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end
复制代码

重写成C++以后

struct Student_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _no;
	int _age;
};

struct NSObject_IMPL {
	Class isa;
};


//其实就是
struct Student_IMPL {
	Class isa; //8字节
	int _no; //4字节
	int _age; //4字节
};
复制代码

因此一个OC对象的本质其实是一个包含了全部父类成员变量+自身成员变量的结构体


Student的内存布局及大小

能够经过Debug->Debug workflow->View momory查看指定地址的结构来查证

对于Student实例对象所占内存地址的大小,咱们一样能够经过malloc_size函数来肯定。

结果是16。8字节父类的isa指针、4字节_age的int、4字节_no的int。

固然若是有兴趣能够用memory write (stu地址+8偏移量) 8的方式,经过直接修改内存的方式对成员变量_no的值进行修改。


内存对齐原则下的OC对象内存分配

alignedInstanceSize()函数的内存对齐

alignedInstanceSize()函数会按照全部成员变量中内存最长的一个作内存对齐。好比

@interface Animal: NSObject
{
    int weight;
    int height;
    int age;
}
复制代码

实际上只须要8+4+4+4=20个字节长度便可,可是内存对其以后会返回8*3=24

malloc()/calloc()函数的内存对齐

在对象实际建立时,先以alignedInstanceSize()返回的大小做为参考。 而后calloc在实际分配内存时为了内存对齐,最终将会根据bucket进行分配。这个bucket是16的整数倍。

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

因此Animal的实例对象实际上会被分配32个字节长度的内存地址。


sizeOf 与 class_getInstanceSize

返回一个参数对象所占的内存大小

sizeOf

sizeOf是运算符,在程序编译阶段将会直接替换成参数类型所占内存具体的常数值。

因为在编译阶段替换,因此有如下这种特性:

MJPerson *p = [[MJPerson alloc] init];
NSLog(@"%zd", sizeof(p)); // 8
复制代码

p在编译时将会被认为成指针,返回8字节的指针内存长度。而不是MJPerson类型的内存长度。

class_getInstanceSize

class_getInstanceSize是一个方法,在程序运行阶段将会进行计算。

他能够在运行阶段计算某个类所需内存大小

class_getInstanceSize([p class]) //24
复制代码

objc_class

runtime.h

OC2.0之前的类结构体。在2.0以后只剩下头文件,而且已经标记成了OBJC2_UNAVAILABLE的弃用状态。

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
复制代码

objc_runtime_new.h

最新的runtime源码中,优化了类的结构,内部分工更加明确。

在一级结构体中,只保留了isasuperclasscache三个经常使用的成员

其他信息均转移到了class_data_bits_t这个二级结构体上

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // 方法缓存
    class_data_bits_t bits;    // 具体的类信息

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ...
}
复制代码

class_data_bits_t(类信息列表)内部,还保存着class_rw_t(可读写信息列表),这些信息是能够动态修改的

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;
}
复制代码

class_rw_t(可读写信息列表)内部,还保存着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;
    }
};
复制代码

参考资料

小码哥iOS底层原理班

iOS复习笔记:OC对象内存大小问题

相关文章
相关标签/搜索