底层一:OC对象的本质

612 阅读5分钟

OC对象本质是C++的结构体:因为对象涉及到不同类型,只有结构体能存储不同的结构体

- (void)test {
    
}


其实是系统会传递两个参数过来

// self 方法调用者
// _cmd 方法名,等价于当前方法的selector,既@selector(test)
- (void)testId:(id)self _cmd:(SEL)_cmd {
    
}

//转化成CPP
void test(MJPerson* self, SEL _cmd) {
    
}

OC对象的本质

将OC代码转换成为C\C++代码

这里最好新建项目创建两个类进行转换,因为要遵从一些上下文,否则会转换失败

切换到文件目录,使用命令行 clang -rewrite-objc xxxx.m -o xxx.cpp 直接切换到iOS下64位系统可以使用的CPP代码 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxxx.m -o xxx.cpp

查看OC源码地址

OC源码 数字最大的是最新的 objc 4

Core Foundation 硬性规定对象占用十六字节

面试题:

一个NSObject对象占用多少内存?

系统分配了16个字节NSObject对象(通过malloc_size函数获得)

NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

64位CPU中,NSObject 对象占用大小为16字节,其中8字节为指针大小,8字节为实例对象结构体所占大小。

32位CPU中,NSObject 对象占用大小为8字节,其中4字节为指针大小,4字节为实例对象结构体所占大小。

参考:深入理解tagPointer

OC 转换为C++后,查看对象的实现,一般是类型_IMPL形式,Student转换后就是 Student_IMPL

查看class_getInstanceSize的实现(.mm 文件),在Objc 4

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

// NSObject Implementation

struct NSObject_IMPL {
    Class isa;
};

//typedef struct objc_class *Class; // 64位占8个字节

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSObject *obj = [[NSObject alloc] init];
        
        NSLog(@"%zd",class_getInstanceSize([NSObject class])); // 8
        
        NSLog(@"%zd", malloc_size((__bridge const void *)obj)); // 16
        
    }
    return 0;
}

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() const {
        return word_align(unalignedInstanceSize());
    }


alloc 实际是调用 allocWithZone

--------------------- 类: NSObject.mm ---------------------

// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}


--------------------- 类: Objc-runtime-new.mm ---------------------

_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}


--------------------- 类 objc-runtime-new.mm  ---------------------

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ............
    

    size_t size;

    size = cls->instanceSize(extraBytes);

    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }

    ............
}



--------------------- 类:objc-runtime-new.h  ---------------------
inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;   ``
        return size;
    }

if (size < 16) size = 16; 主要是这句,是Core Foundation框架 硬性规定的

查看过程:

 1. 将OC转换成C++代码,可以看到的NSObject的本质是一个结构体,其中包含了一个isa指针,占用了8个字节
// NSObject Implementation
struct NSObject_IMPL {
    Class isa; // 8个字节
};
 2. 查看源码
 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());
    }

查看alloc分配内存的过程

// 1.在源码中搜索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;
}

// 2. 查看class_createInstance函数
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}


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;
}
// 3. 查看cls->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;
    }

可以看出Core Foundation框架要求对象至少占用16个字节

截屏2021-03-11 下午3.20.26.png

test_xcodeproj.png

计算机中的正数用源码表示,负数用补码表示,而负数的补码是其反码+1 所以出现了-128 简单的说为了避免-0,负数的算法结果是每位都加了1

iOS是小端模式,在内存中从高位向低位读取数据

Interview01-OC对象的本质_xcodeproj.png

内存对齐: 结构体的大小必须是最大成员大小的倍数

iOS内部操作系统:内存分配是16的倍数

instance对象(实例对象)、class对象(类对象)、meta_class对象(元类对象)

instance对象(实例对象)

  • instance对象在内存中存储的信息包括 :isa指针(也是成员变量)、其他成员变量

  • isa的地址,就是instance对象的地址,因为isa总是在首部

class对象(类对象)

每个类在内存中有且只有一个class对象

runtime 源码 在objc4 里面

isa和superClass

instance对象、class对象、meta_class对象

instance对象

每个instance对象创建出来(通过allocinit创建),方法都只有一份,因为方法是相同的,放到类的方法列表中 (不放在对象的结构体中)

一个类的类对象(class对象)是唯一的,在内存中只会开辟一份存储空间

isa指针向顺序

屏幕快照 2018-07-15 18.33.02.png

class对象的superClass指针

屏幕快照 2018-07-15 19.18.56.png

isa和superClass总结

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

屏幕快照 2018-07-16 22.53.37.png

  • 注意点:基类的元类的superClass指针,指向基类的类对象

isa指针细节

ISA_MASK 去源码里面获取

屏幕快照 2018-07-18 23.19.09.png

检测方式
实例对象

定义一个类MJPerson,创建一个instance对象(实例对象)和一个Class对象(类对象),断点打印对应的isa地址。

打印指针 2018-07-18 23.23.01.png

未命名.png

类对象

因为类对象的isa指针无法通过程序直接打印,可以创建一个结构体模仿类对象的实现,然后进行打印

struct mj_objc_class {
    Class isa;
    Class superclass;
};

// 使用
struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);
从而得出
personClass->isa & ISA_MASK:0x00000001000014c8 = meta_class 

窥探struct objc_class的结构 (类对象和元类对象对应的结构体Class)

01-OC语法.png

super 和 superClass的区别

  • self就是给谁发消息,不一定是当前类,谁接受消息,self就代表谁
#import <Foundation/Foundation.h>

@interface NSObject (Test)

+ (void)test;

@end

//#import "NSObject+Test.h"

@implementation NSObject (Test)

//+ (void)test
//{
//    NSLog(@"+[NSObject test] - %p", self);
//}
//
- (void)test
{
    // self就是给谁发消息,不一定是当前类,谁接受消息,self就代表谁
    NSLog(@"-[NSObject test] - %p", self);
}

@end



@interface MJPerson : NSObject

+ (void)test;

@end

@implementation MJPerson

//+ (void)test
//{
//    NSLog(@"+[MJPerson test] - %p", self);
//}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"[MJPerson class] - %p", [MJPerson class]);
        NSLog(@"[NSObject class] - %p", [NSObject class]);
        // OC对象的调用方法,就是发送消息,发送过程中不会注意到是类方法还是对象方法
        [MJPerson test];
        // objc_msgSend([MJPerson class], @selector(test))
        // isa -> superclass -> suerpclass -> superclass -> .... superclass == nil
        
        [NSObject test];
        // objc_msgSend([NSObject class], @selector(test))
        
        /* 打印结果
         [MJPerson class] - 0x1000011e0
         [NSObject class] - 0x7fff8d775140
         // self就是给谁发消息,不一定是当前类,谁接受消息,self就代表谁,所以这里的打印虽然发生在NSObject的分类中,但是其实消息是发送给MJPerson的,所以打印出的self是MJPerson的类对象
         /*重点是看具体的地址*/
         [NSObject test] - 0x1000011e0
         [NSObject test] - 0x7fff8d775140
         */
    
    }
    return 0;
}