【iOS面试#1】alloc的简单分析

189 阅读2分钟

1. alloc流程分析

打开符号断点_objc_rootAlloccallAlloc_objc_rootAllocWithZone

App启动 2.png

1). alloc

走这个返回分支的时候

return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));

objc_msgSend会走下面这个方法 iShot2022-01-17 14.35.49.png

[BYPerson alloc]走下面的

// Calls [cls alloc].
id objc_alloc(Class cls)

iShot2022-01-17 14.36.24.png

2). init / new

iShot2022-01-17 14.36.41.png

3). callAlloc

普通类先走objc_msgSend,再走_objc_rootAllocwithZone,NSObject直接走_objc_rootAllocwithZone

iShot2022-01-17 14.37.05.png

判定cls->ISA()->customAWZ iShot2022-01-17 14.40.34.png

iShot2022-01-17 14.40.51.png NSObject存在DEFAULT_AWZ所以customAWZfalse,普通类不存在DEFAULT_AWZ所以customAWZtrue,故走下面的objc_msgSend回到 ==> 1). alloc,所以这也解释了为什么NSObject只走一遍callAlloc而普通类要走两遍。

4). _objc_rootAllocWithZone / _class_createInstanceFromZone

iShot2022-01-17 14.37.21.png

计算空间,申请内存,关联isa

image.png

5). instanceSize 计算空间

instanceSize(extraBytes) 计算需要开辟的内存大小,传入的extraBytes 为 0 ,对象最小空间为16,所以需要16字节对齐,isa指针占用1个字节,8bit iShot2022-01-17 14.38.13.png

iShot2022-01-17 14.38.31.png

6). initInstanceIsa / initIsa

iShot2022-01-17 14.39.16.png

iShot2022-01-17 14.40.07.png

2. isa

旧版ISA()实现 iShot2022-01-18 11.27.15.png

新版ISA()需要认证 STIIITCH_2022_01_18_11_32_36.PNG

ISA_MASK等一些定义

#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

image.png

isa_t简化结构

// 联合体,每次只能使用其中某一个成员
union isa_t {
    Class cls;
    uintptr_t bits;

    // arm64 架构
    struct {
        uintptr_t nonpointer        : 1;  // 0:普通指针,1:优化过,使用位域存储更多信息
        uintptr_t has_assoc         : 1;  // 对象是否含有或曾经含有关联引用
        uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析构函数或OC的dealloc
        uintptr_t shiftcls          : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
        uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
        uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
        uintptr_t deallocating      : 1;  // 对象是否正在释放
        uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数
        uintptr_t extra_rc          : 19;  // 引用计数能够用 19 个二进制位存储时,直接存储在这里
    };

};

// x86_64 架构
struct {
    uintptr_t nonpointer        : 1;  // 0:普通指针,1:优化过,使用位域存储更多信息
    uintptr_t has_assoc         : 1;  // 对象是否含有或曾经含有关联引用
    uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析构函数或OC的dealloc
    uintptr_t shiftcls          : 44; // 存放着 Class、Meta-Class 对象的内存地址信息
    uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
    uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    uintptr_t deallocating      : 1;  // 对象是否正在释放
    uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数
    uintptr_t extra_rc          : 8;  // 引用计数能够用 8 个二进制位存储时,直接存储在这里
};
  • nonpointer
    0,代表普通的指针,存储着ClassMeta-Class对象的内存地址
    1,代表优化过,使用位域存储更多的信息,bits & ISA_MASK可获得真实的isa指针地址

  • has_assoc
    是否有设置过关联对象,如果没有,释放时会更快

  • has_cxx_dtor
    是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

  • shiftcls
    存储着ClassMeta-Class对象的内存地址信息,bits & ISA_MASK可获得真实的isa指针地址

  • magic
    用于在调试时分辨对象是否未完成初始化

  • weakly_referenced
    是否有被弱引用指向过,如果没有,释放时会更快

  • deallocating
    对象是否正在释放

  • extra_rc
    里面存储的值是引用计数器减1

  • has_sidetable_rc
    引用计数器是否过大无法存储在isa中
    如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

extra_rchas_sidetable_rc 详细解析参见 ==> 内存管理

isa与superclass走向 1642497014813.jpg

3. 相关问题

1). NSObject和自定义类为啥不走alloc而是走objc_alloc

llvm会将一些方法hook,当调用的时候,会替换成其他方法,所以当我们调用alloc的时候,其实真实调用的是objc_alloc

image.png 类似的方法还有以下这些:

// This is the table of ObjC "accelerated dispatch" functions.  They are a set
// of objc methods that are "seldom overridden" and so the compiler replaces the
// objc_msgSend with a call to one of the dispatch functions.  That will check
// whether the method has been overridden, and directly call the Foundation 
// implementation if not.  
// This table is supposed to be complete.  If ones get added in the future, we
// will have to add them to the table.
const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = {
    "objc_alloc",
    "objc_autorelease",
    "objc_release",
    "objc_retain",
    "objc_alloc_init",
    "objc_allocWithZone",
    "objc_opt_class",
    "objc_opt_isKindOfClass",
    "objc_opt_new",
    "objc_opt_respondsToSelector",
    "objc_opt_self",
};

2). NSObject为啥只走一遍callAlloc而普通类要走两遍

见 ==> 3). callAlloc

4. 一些操作

1). 字节对齐

  • 位移
size_t align16(size_t x) {
    return (x + size_t(15)) >> 4 << 4;
}
  • 位与和取反
size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

2). 内存占用

  • sizeof 计算类型占用空间,指针*p -> 8,double -> 8,int -> 4,char -> 1

  • class_getInstanceSize 实例对象占用内存的大小,实例对象的成员变量实际所需大小

  • malloc_size 为对象申请内存的大小,会进行16 字节对齐,不小于class_getInstanceSize

具体见 ==> iOS - 获取内存大小的三种方式