目标的实质

Objective-C 代码的底层都是经过 C/C++ 完成,所以 Objective-C 面向目标是基于 C/C++ 数据结构完成。 下图为OC语言转换成机器语言的几个进程

iOS八股文(一)对象的本质探索(上)

能够经过clong编译器完结第一个进程。 把OC代码重写成C++代码。

clang -rewrite-objc <oc fileName> -o <c++ fileName>

也能够指定平台、指定指定架构模式,代码量少点,便于研究.

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc < OC FileName> -o <Cpp FileName>

iOS八股文(一)对象的本质探索(上)

OC代码

@interface OSTestObject1 : OSTestObject
{
  @public int _count2;
}
@end
@interface OSTestObject : NSObject
- (void)print;
@end
int object_c_source_m() {
  OSTestObject1 *obj1 = [[OSTestObject1 alloc] init];
  return 0;
}

C++代码(删减许多,便利阅览)

struct NSObject_IMPL {
    Class isa;
};
struct OSTestObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};
struct OSTestObject1_IMPL {
    struct OSTestObject_IMPL OSTestObject_IVARS;
    int _count2;
};
int object_c_source_m() {
  OSTestObject1 *obj1 = ((OSTestObject1 *(*)(id, SEL))(void *)objc_msgSend)((id)((OSTestObject1 *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("OSTestObject1"), sel_registerName("alloc")), sel_registerName("init"));
  return 0;
}

结论:

OC目标的实质其实是结构体,目标里边存储了目标的成员变量信息。

目标创建的流程

我们知道一个目标的init其实底子是掉用了c语言里边的calloc函数。但其实在这个操作之前还需求知道该目标所需求的内存巨细。所以在objc4源码中能够看到(在objc4-818.2/runtime/objc-runtime-new.mm中的_class_createInstanceFromZone办法的完成)

iOS八股文(一)对象的本质探索(上)
这儿便是目标创建的关键代码。我们能够大致总结为3个部分。

1.计算目标内存巨细 (cls->instanceSize)
2.向体系请求内存空间 (calloc)
3.设置isa指针,相关到类目标,初始化目标信息 (obj->initInstanceIsa)

目标的巨细

在OC中有3个关于目标巨细的API。

API 所属库 意义
sizeof foundation 成员变量所占空间
class_getInstanceSize runtime 是一切成员变量所占空间
malloc_size malloc 是目标分配的空间

其间 sizeofclass_getInstanceSize的区别是,sizeof为符号,在编译的进程就确定了值,而class_getInstanceSize为函数,在运转的进程中才知道成果。

上代码:

struct objcet_test {
  Class isa;
};
- (void)test1 {
    NSLog(@"结构体所占内存为%zd",sizeof(struct objcet_test));
    NSObject *objc1 = [[NSObject alloc] init];
  NSLog(@"nsobject 的成员变量所占空间为%zd",class_getInstanceSize([NSObject class]));
  NSLog(@"objc1所占内存空间为%zd",malloc_size((__bridge const void *)objc1));
}

运转成果:

2022-04-18 14:41:00.898227+0800 ObjectStudy[9601:189987] 结构体所占内存为8 2022-04-18 14:41:00.746690+0800 ObjectStudy[9394:185408] nsobject 的成员变量所占空间为8

2022-04-18 14:41:00.746737+0800 ObjectStudy[9394:185408] objc1所占内存空间为16

内存对齐

造成上面情况的底子原因是内存对齐。能够大致理解为这样存储虽然数据空间会增大,但拜访数据的功率会更高,是一种牺牲空间交换时刻的操作。

在OC中是经过了2次内存对齐,一种是结构体的内存对齐,一种是目标的内存对齐。

其间class_getInstanceSize获取到的是结构体内存对齐后的成果。 而malloc_size获取到的是目标内存对齐后的成果。

结构体内存对齐规矩

  1. 数据成员对齐规矩:结构(struct)的第一个数据成员放在offset为0的当地,以后每个数据成员存储的起始位置要从该成员巨细或许成员的子成员巨细的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。

  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素巨细的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)。

  3. 收尾工作:结构体的总巨细,也便是sizeof的成果有必要是其内部最大成员的整数倍,缺乏的要补齐。

目标内存对齐

oc目标内存对齐能够粗暴的理解为所需的内存有必要是16的倍数。这是苹果体系分配内存处理的。为了进一步优化内存读取功率,内存在运用的时候是有bucket这样一个概念的。苹果会把内存分为多个bucket,其间bucket最小的单位是16个字节。这点能够在libmalloc源码中旁边面证实。

iOS八股文(一)对象的本质探索(上)
最终能够顺便在源码中参考一下苹果类似内存对齐的源码,运用位移运算。

#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;
    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
        k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
        slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
    *pKey = k - 1; // Zero-based!
    return slot_bytes;
}