前言

相关文章:

iOS底层探索一(底层探索方法)

iOS底层探索三(内存对齐与calloc分析)

iOS底层探索四(isa初探-联合体,位域,内存优化)

iOS底层探索五(isa与类的关系)

iOS底层探索六(类的分析上)

iOS底层探索七(类的分析下)

iOS底层探索八(方法本质上)

iOS底层探索九(方法的本质下objc_msgSend慢速及方法转发初探)

iOS底层探索十(方法的本质下-消息转发流程)

开发中我们经常会用到XZPerson *p= [[XZPerson alloc]init],我们只知道,这样我们就新建了一个对象,可以直接使用这个对象,可以对这个对象进行赋值使用,但是我们没有关心过alloc方法底层到底是通过什么方式进行实现的;今天我准备对alloc方法进行一次尝试性底层挖掘;首先我们需要先准备一份可编译的objc4_752代码,可以直接进行下载探索;

首先,先上一张我探索完成的alloc流程图,供大家进行参考

然后我们直接使用能运行 objc-752 源码后,在 main.m 中写入如下代码就开始了 alloc 的探索。

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...XZPerson *object = [XZPerson alloc];NSLog(@"Hello, World! %@",object);}return 0;
}

打断点直接这里 alloc 点击进入的为 _objc_rootAlloc 其实我们断点后发现进入的是 objc_alloc

这里我们稍作解释:我们可以看到源码中有这么一个方法fixupMessageRef,这里应该是对进行符号绑定将alloc绑定为objc_alloc方法,具体是通过LLVM期进行符号绑定,我们这篇文章先不进行详细介绍;

fixupMessageRef(message_ref_t *msg)
{    msg->sel = sel_registerName((const char *)msg->sel);if (msg->imp == &objc_msgSend_fixup) { if (msg->sel == SEL_alloc) {msg->imp = (IMP)&objc_alloc;} else if (msg->sel == SEL_allocWithZone) {msg->imp = (IMP)&objc_allocWithZone;} else if (msg->sel == SEL_retain) {msg->imp = (IMP)&objc_retain;} else if (msg->sel == SEL_release) {msg->imp = (IMP)&objc_release;} else if (msg->sel == SEL_autorelease) {msg->imp = (IMP)&objc_autorelease;} else {msg->imp = &objc_msgSend_fixedup;}} else if (msg->imp == &objc_msgSendSuper2_fixup) { msg->imp = &objc_msgSendSuper2_fixedup;} else if (msg->imp == &objc_msgSend_stret_fixup) { msg->imp = &objc_msgSend_stret_fixedup;} else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { msg->imp = &objc_msgSendSuper2_stret_fixedup;}
#if defined(__i386__)  ||  defined(__x86_64__)else if (msg->imp == &objc_msgSend_fpret_fixup) { msg->imp = &objc_msgSend_fpret_fixedup;}
#endif
#if defined(__x86_64__)else if (msg->imp == &objc_msgSend_fp2ret_fixup) { msg->imp = &objc_msgSend_fp2ret_fixedup;}
#endif
}

1.首先我们可以看到进入的是 alloc 内部调用_objc_rootAlloc方法绩效调用callAlloc方法

+ (id)alloc {return _objc_rootAlloc(self);
}id
_objc_rootAlloc(Class cls)
{return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

这里我们可以看到进入_objcrootAlloc方法里面有参数为Class的一个参数

Class 类底层其实是objc_class类结构体
typedef struct objc_class *Class;objc_class结构体中
struct objc_class : objc_object {// Class ISA;         //isa指针Class superclass;  //父类cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flagsclass_rw_t *data() { return bits.data();}//...下面还有很多方法就不在这里贴了,可以自行下代码进行查看

objc_class 又继承于 objc_object ,objc_object 存储了 isa 的一个 Class 结构体

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;

这里如果有使用过RunTime的同学就可以看到一些熟悉的东西 ivars, methodLists ,protocols,这个后期文章我们再进行赘述,此篇文章先进行稍微引入

2.当进入callAlloc方法中

这里我都有备注判断的原因,如有不对还请看到的各位,评论留言我们可以一起探讨,这里如果我们自己打断点的话,可以看到大部分会直接走到class_createInstance方法中;

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{/**fastpath(x)表示x很可能不为0,希望编译器进行优化;slowpath(x)表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令*/if (slowpath(checkNil && !cls)) return nil;#if __OBJC2__/**实际意义是hasCustomAllocWithZone——这里表示有没有alloc / allocWithZone的实现(只有不是继承NSObject/NSProxy的类才为true)*/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/**内部调用了bits.canAllocFast后分为2种情况if(FAST_ALLOC){}else{默认为false,}当我们查看FAST_ALLOC宏时,发现它的上方有个else 1 之后才是else中定义这个宏,也就是说这个宏一直是不存在的可以自行点击进入查看*/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];
}

3.class_createInstance方法中,跟踪class_createInstance(cls, 0)方法,其内部调用了_class_createInstanceFromZone方法,并在其中进行size计算,内存申请,以及isa初始化

class_createInstance(Class cls, size_t extraBytes)
{return _class_createInstanceFromZone(cls, extraBytes, nil);
}

_class_createInstanceFromZone这个方法是核心方法,进行了开辟内存和关联对象

_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 performancebool 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) {//这里是真正的创建类的方法但是这个calloc源码是在malloc中obj = (id)calloc(1, size);if (!obj) return nil;//将内存指向给objc进行关联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;
}

其中cls->instanceSize(extraBytes)此方法为计算应该开辟多大内存方法,计算出size,
其中64位系统下,对象大小采用8字节对齐,但是实际申请的内存最低为16字节,也就是说系统分配内存按照16字节对齐分配

 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;//最小返回16字节return size;}uint32_t alignedInstanceSize() {//字节对齐return word_align(unalignedInstanceSize());}#   define WORD_MASK 7UL  //这个宏标示数字7
static inline uint32_t word_align(uint32_t x) {//WORD_MASK  7//7+8 = 15//0000 0111 2进制表示7//0000 1111 2进制表示15//1111 1000 2进制表示~7//0000 1000 15 & ~7 值 16//这里主要就是做8进制对齐,这里相当于8倍数//x + 7//x=1  8//x=2  16return (x + WORD_MASK) & ~WORD_MASK;
}

字节对齐核心算法:

例如传值进来为7 已知 WORD_MASK为7

//WORD_MASK  7
    7+8 = 15
    0000 0111        2进制表示7
    0000 1111         2进制表示15
    1111 1000         2进制表示~7

---------------------------------
    /0000 1000    (15 & ~7 ) 值 16

这里主要就是做8进制对齐,这里相当于8倍数,当然这里的WORD_MASK如果是15那么这个就是16字节对齐

根据不同的条件,使用calloc或者malloc_zone_calloc进行内存申请,并且初始化isa指针,至此size大小的对象obj已经申请完成,并且返回;

4.这就OC中alloc底层的调用方式,我们都看了alloc的源码了,顺便看下init方法吧;

- (id)init {return _objc_rootInit(self);
}id
_objc_rootInit(id obj)
{// In practice, it will be hard to rely on this function.// Many classes do not properly chain -init calls.return obj;
}

我们可以看出来,在init方法中苹果开发人员没有做任何东西,只是将对象直接返回,说明这只是苹果开发人员为我们提供的一个工厂方法,让我们方便进行重写;

5.顺便把New方法也看下

+ (id)new {return [callAlloc(self, false/*checkNil*/) init];
}

仔细看过alloc源码的话,这里就很清晰了,callAlloc不就是调用了下alloc方法的底层么,顺便也调用了下init,这就是说,new方法底层也是调用alloc的,

总结

这是个人的实际追踪源码得出的一些结论,源码中还有一些分支代码可以自行查看initInstanceIsa下篇文章在进行分析,如果有错误的地方还请指正,大家一起讨论,开发水平一般,还望大家体谅,欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!

写给自己:

研究源码枯燥的,但是面对源码不用害怕,一步步把它拆分开来研究,多看看官方给的注释/Github大神的注释,慢慢来,总有一自己也会厉害的。

iOS底层探索二(OC 中 alloc 方法 初探)相关推荐

  1. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  2. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  3. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  4. OC中的方法、设置器与访问器、便利初始化函数及便利构造器的相关问题

    OC中的方法 方法:类的功能代码,在<.h>文件中声明,在<.m>文件中实现. 语法: -|+(返回值类型)方法名:(参数类型)参数名 { //方法体 } 示例: -(void ...

  5. c语言访问oc变量,OC中的方法调用流程

    OC是一门动态语言,其方法调用方式与C++还是有很大区别的. 具体的方法调用过程,可以参考下面一片枫叶的博客,写的还是很详细的. 对于OC的方法调用,有两个点是重点: 1.对于OC的一切方法调用,最终 ...

  6. OC底层探索(二十二)八大锁

    OC底层文章汇总 锁的种类 在OC中锁分为互斥锁和自旋锁两种. 互斥锁 用于保护临界区,确保同一时间,只有一条线程能够执行 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建 ...

  7. JS与OC中的方法相互调用

    文章主要是介绍oc如何调用js 以及js的代码调用oc的方法 先上代码后做解释 //oc的.m 的代码 //ps接下来有js的代码一并解析,最后会附上demo //  ViewController.m ...

  8. iOS底层原理总结 - OC对象的本质

    苹果官方文档 The Objective-C language defers as many decisions as it can from compile time and link time t ...

  9. oc基础-OC中对象方法的使用

    方法分为:无参,有参 #import <Foundation/Foundation.h>@interface Student :NSObject {int age;char *name; ...

最新文章

  1. LOJ 2721 「NOI2018」屠龙勇士——扩展中国剩余定理
  2. html代码复制到asp中不一样了_迷你代码编程在线成语词典
  3. leetcode算法题--对链表进行插入排序
  4. 漫画算法:无序数组排序后的最大相邻差值
  5. php响应式布局,响应式布局之弹性布局的介绍
  6. Ucosii消息邮箱使用
  7. redis调优 -- 内存碎片
  8. linux系统的安全机制有哪些内容,系统安全机制
  9. Flink on Yarn运行机制
  10. oracle清理表空间文件,如何自动删除表空间的文件?
  11. 《图解HTTP》学习笔记
  12. NOIP2017 Day1 T1 小凯的疑惑
  13. php返回成功信息msg_PHP进化史 — 从v5.6到v8.0(可收藏)
  14. 计算机组成原理唐朔飞重点,计算机组成原理唐朔飞高分笔记
  15. 今日头条的针锋相对让腾讯开始焦虑,天天快报能否占据一席之位?
  16. 脑与认知科学基础(期末复习)
  17. 2020年你还不会做绿幕特效?这4步基础技巧要点了解一下!
  18. 电子科技大学软件工程860考研专业课真题考频总结
  19. 由2003年的一篇讲座笔记
  20. 用acts_as_paranoid 做假删除

热门文章

  1. 从零基础学摄影nbsp;——7个轻巧实…
  2. 基于51单片机的酒精浓度检测量仪proteus仿真程序原理图设计数码管液晶LCD1602显示
  3. 云计算实训总结_云计算·实训报告书.doc
  4. java学习---新手笔记,多多包涵
  5. SpringBoot-08模板引擎 Thymeleaf 找源码官方文档使用 语法的学习
  6. android 三星手机拍照旋转90度,解决三星拍照上传照片被旋转90度,和三星相机崩溃...
  7. .net 中context、DbContext是什么?
  8. 【Unity】LineRenderer模拟橡皮筋弹跳效果
  9. ChatGPT频频发疯!马斯克警告:AI将毁灭人类
  10. 我建议,专家不要再建议了