iOS 底层原理 OC对象的本质

2 阅读4分钟

NSObject的内存

clang -rewrite-objc main.m -o main.cpp可以查看C++的代码,目前新版本的Xcode不支持-rewrite-objc。所有资料都是之前本地的笔记目前上传到网上备份用。

NSObject的内存实现
struct NSObject_IMPL{
    Class isa;
}
Class的定义
typedef struct objc_class *Class

查看内存大小

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

int main(int argc, char * argv[]) {
    NSObject *obj = [[NSObject alloc] init];
    
    long size = class_getInstanceSize([NSObject class]);
    NSLog(@"%ld",size); // 8
    long mememoySize = malloc_size((__bridge const void *)(obj));
    NSLog(@"%ld",mememoySize ); // 16
    return 0;
}
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;
}

结构体含有一个指针isa。实际只需要8字节内存。但是实际objc源码限定了,如果对象小于16字节,直接分配16字节对象。作用是每次取内存都直接16字节对齐提升效率。

自定义对象的内存

就是继承NSObject,第一个还是isa指针,后面是普通成员变量。

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


@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end

24->32对齐

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Student : NSObject
{
    @public
    int _no;
    int _age;
    int _score;
}
@end

@implementation Student

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        stu->_no = 4;
        stu->_age = 5;
        
        NSLog(@"%zd", class_getInstanceSize([Student class]));// 24,实际占20,然后内存对齐后是24(指针为8,结构体内存为最大成员变量的整数倍)
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));// 32
        
        
    }
    return 0;
}

开启内存的过程

static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) {
    // 1. 计算实例所需的内存大小(内部会调用 instanceSize -> align16 对齐)
    size_t size = cls->instanceSize(extraBytes); 

    // 2. 在堆区开辟内存(calloc 会自动清零)
    id obj = (id)calloc(1, size); 
    
    // 3. 初始化 isa 指针,将实例与类绑定
    obj->initInstanceIsa(cls, hasCxxDtor); 
    
    return obj;
}
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.
    // 保底机制:即使对象只有 isa 指针(8字节),也强制分配 16 字节
    if (size < 16) size = 16; 
    return size;
}
size_t fastInstanceSize(size_t extra) const {
    ASSERT(hasFastInstanceSize(extra));
    if (__builtin_constant_p(extra) && extra == 0) {
        return _flags & FAST_CACHE_ALLOC_MASK16;
    } else {
        size_t size = _flags & FAST_CACHE_ALLOC_MASK;
        // 在这里调用了 align16 进行 16 字节对齐
        return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); 
    }
}

16字节对齐。理解为加15对16取余。

将传入的字节数加上 15,然后与 15 取反后的值(即二进制的 ...11110000)进行按位与(&)运算。这相当于抹去低 4 位,实现向上取 16 的倍数。

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

对象的分类

- (Class)class {
    return object_getClass(self);
}

objc_getClass的本质就是获取对象的isa。isa & ISA_MASK才是真实的类对象的地址。

//
//  main.m
//  demo01
//
//  Created by wang on 2026/9/6.
//

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        // obj就是实例对象
        NSObject *obj = [[NSObject alloc] init];
        
        // 类对象
        Class objClass1 = [NSObject class];
        Class objClass2 = [objClass1 class]; // 对类对象取class还是自己
        Class objClass3 = objc_getClass("NSObject");
        NSLog(@"%p\n%p\n%p",objClass1,objClass2,objClass3);
        
        // 元类对象meta-class
        Class objmetaClass1 = objc_getMetaClass("NSObject");
        Class objmetaClass2 = object_getClass([NSObject class]);
        Class objmetaClass3 = object_getClass(objmetaClass1);//对元类对象取元类对象,是自己
        NSLog(@"%p\n%p\n%p\n%@",objmetaClass1,objmetaClass2,objmetaClass3,class_isMetaClass(objmetaClass2)?@"true":@"false");

        
    }
    return EXIT_SUCCESS;
}

所有对象的元类对象都指向父类的元类对象。而父类的元对象又指向NSObjecct的元对象。因此,所有对象的原来的isa都指向NSObject的元对象。

// 获取当前类的元类
Class metacls = cls->ISA(); 

// 核心逻辑:设置元类的 isa 指针
if (metacls->isMetaClass()) {
    // 如果已经是元类,它的 isa 指向谁?
    // 指向基类(Root class)的元类
    metacls->initClassIsa(cls->getSuperclass()->ISA()); 
}

而NSObject由于没有父类,则指向自己。

// 获取当前类的父类
Class supercls = cls->getSuperclass(); 

// 核心边界判断
if (supercls != nil) {
    // 普通子类:元类的 isa 指向【父类的元类】
    metacls->initClassIsa(supercls->ISA()); 
} else {
    // 根类 (NSObject):没有父类了,元类的 isa 指向【自己】,形成终极闭环
    metacls->initClassIsa(metacls); 
}

superclass

执行对象方法的时候,会优先找当前的类对象的方法列表查找,没有找到就调用父类的类对象方法列表,NSObject的superclass为空,如果NSObject还没有找到就会报错。

类方法也一样,直接找父类的类对象。区别是NSObject的superclass是NSObject的类对象。

所以,有一个有趣的事情发生了,给NSObject添加一个扩展,可以被所有的类调用类对象

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

@interface NSObject(Hack)
- (void)unknowMethod;
@end

@implementation NSObject(Hack)
- (void)unknowMethod{
    
    NSLog(@"NSObject unknowMethod");
    NSString *className = NSStringFromClass([self class]);
    
    // 核心判断:判断当前的 self 是不是一个类对象
    if (object_isClass(self)) {
        NSLog(@"👉 我是被当作【类方法 (+)】调用的!当前类名: %@, self: %@", className, self);
    } else {
        NSLog(@"👉 我是被当作【实例方法 (-)】调用的!当前类名: %@, self: %@", className, self);
    }
}
@end

@interface Person : NSObject

@end


@implementation Person

@end

int main(int argc, const char * argv[]) {
    NSObject *obj = [[NSObject alloc] init];
    [obj unknowMethod];
    [Person unknowMethod]; // 编译通过,可以正常使用
    //    [Person unknowMethod1];编译报错
    return EXIT_SUCCESS;
}


// 输出
NSObject unknowMethod
👉 我是被当作【实例方法 (-)】调用的!当前类名: NSObject, self: <NSObject: 0x1007b1b90>
NSObject unknowMethod
👉 我是被当作【类方法 (+)】调用的!当前类名: Person, self: Person
Program ended with exit code: 0