一.了解对象与指针

先看一张图:


这张图我们可知:

  • imgv是指针,指针指向的是对象;
  • [JPeople alloc]创建了一个对象;
  • p1写在等号前面,等于把P1指向了那个对象的内存地址,所以p1是指针;
  • 同理可知p2,p3也是指针,指向了P1指向了那个对象的同一块内存地址;
  • 所以我们都是通过通过这个指针找到内存中的对象(通过指针来找到对象而不是表示对象)

二.底层探索的三种方式

2.1 下断点方式

按着control + in 进入真机调试(模拟器是x86架构,真机是arm64架构):


in这里表示是上图所示的红框的按钮;

此时往下走找到libobjc.A.dylib动态库下的objc_alloc如图所示:

2.2 下符号断点

选择Symbolic Breakpoint(objc_alloc),

过去断点,我们得到了 alloc 实现位于 libObjc 这个动态库;

2.3 汇编方式

> Debug -> Debug Workflow -> Always show Disassembly

具体操作打开Debug菜单下的 Debug Workflow 下的 Always Show Disassembly如图:

然后跳转到objec_alloc 继续按住control + in 下一步,我们可以一步一步的找到libobjc.A.dylib库下的objc_alloc;

三.alloc初始化源码跟踪流程

1. alloc源码

1.alloc

+ (id)alloc {return _objc_rootAlloc(self);
}

2._objc_rootAlloc

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

3.callAlloc

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__if (slowpath(checkNil && !cls)) return nil;if (fastpath(!cls->ISA()->hasCustomAWZ())) {return _objc_rootAllocWithZone(cls, nil);}
#endif// No shortcuts available.if (allocWithZone) {return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);}return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

4._objc_rootAllocWithZone

注意:allocWithZone under OBJC2 ignores the zone parameter allocWithZone 在 OBJC2 下可以忽略zone的此参数,可能做了优化

_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{// allocWithZone under __OBJC2__ ignores the zone parameterreturn _class_createInstanceFromZone(cls, 0, nil,OBJECT_CONSTRUCT_CALL_BADALLOC);
}
<font color=red>alloc源码流程图</font>

5._class_createInstanceFromZone

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,int construct_flags = OBJECT_CONSTRUCT_NONE,bool cxxConstruct = true,size_t *outAllocatedSize = nil)
{ASSERT(cls->isRealized());// Read class's info bits all at once for performancebool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();bool hasCxxDtor = cls->hasCxxDtor();bool fast = cls->canAllocNonpointer();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);}if (slowpath(!obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {return _objc_callBadAllocHandler(cls);}return nil;}if (!zone && fast) {obj->initInstanceIsa(cls, hasCxxDtor);} else {// Use raw pointer isa on the assumption that they might be// doing something weird with the zone or RR.obj->initIsa(cls);}if (fastpath(!hasCxxCtor)) {return obj;}construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;return object_cxxConstructFromClass(obj, cls, construct_flags);
}

2. 跟着源码查看堆栈信息分析:


我们看一下每一个堆栈对应的实现函数:

  1. main

    People *p = [Peple alloc];

  2. objc_alloc
  3. callAlloc —> objc_msgSend
    走的第二个流程
  4. [NSObject alloc]的alloc
    alloc
  5. _objc_rootAlloc
    注意callAlloc第三个参数传true
  6. callAlloc
    注意:走的第一个流程
  7. _objc_rootAllocWithZone
    8. _class_createInstanceFromZone

    代码解读
  • 判断缓存中是否存在自定义的alloc/allocWithZone地方实现,显然第一次运行类中是没有该方法缓存的;
  • 类的初始化在read_images方法执行时,而 实例对象的初始化在alloc 的时候。
  • 第一次执行 ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));会进行慢速方法查找,找到 NSObject类的alloc方法,并将方法放入方法缓存。
  • 所以除了第一调用 alloc方法外,之后在进行对象初始化会直接走_objc_rootAllocWithZone方法
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__if (slowpath(checkNil && !cls)) return nil;if (fastpath(!cls->ISA()->hasCustomAWZ())) {return _objc_rootAllocWithZone(cls, nil);}
#endif// No shortcuts available.if (allocWithZone) {return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);}return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

我们发现无论我们运行多少次都是这样流程:

3. alloc源码流程图

4.补充[NSObject alloc]流程

以上我分析了对象p的alloc流程,我们知道People继承于NSObject,那对于NSObject对象的底层流程到底是什么流程呢?

objc_alloc -> callAlloc -> _objc_rootAllocWithZone ->_class_createInstanceFromZone

走这样的流程是因为缓存已经有了,所以不会发送alloc消息

四.重要知识点的分析:

以上alloc流程图中我们三个方法如下图:

1. cls->instanceSize

此流程会计算出需要内存大小,跟踪源码如下:

  • 通过缓存进行快速计算

    快速途径

    最终来到了align16方法,系统内存16字节对齐算法

为什么系统内存16字节对齐

  1. cpu在存取数据时,并不是以字节为单位,而是以块为单位存取。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以通过减少存取次数来降低cpu的开销;
  2. 16字节对齐,是由于在一个对象中,第一个属性isa占8字节(继承自父类),当然一个对象肯定还有其他属性,当无属性时,会预留8字节,即16字节对齐,如果不预留,相当于这个对象的isa和其他对象的isa紧挨着,容易造成访问混乱;
  3. 16字节对齐后,可以加快CPU读取速度,同时使访问更安全,不会产生访问混乱的情况;
  4. 苹果对于内存做的一些容错考虑
  • 第一进来没有在缓存中查找
    alignedInstanceSize 方法走到 word_align方法,word_align方法中调用了unalignedInstanceSize
May be unaligned depending on class's ivars.
依赖于对象的属性
uint32_t unalignedInstanceSize() const {ASSERT(isRealized());return data()->ro()->instanceSize;}

此方法可以看到从内存中ro取出干净的内存,大小为8字节;为什么呢?
1.其继承于NSObject ,中有isa,占用8个字节;
疑问点:
1.我们是在对象内存大小,而此时对象没有任何属性、方法成员变量、协议等,那到底影响对象内存大小因素到底是什么呢?
2.系统的16字节对齐为什么呢?从哪分析呢?上面也有部分分析到了,真正底层在哪呢?

2.calloc

向系统申请开辟内存,返回地址指针。此流程会临时分配一个脏内存,调用calloc后分配的内存空间才是创建对象的内存地址。

3.obj->initInstanceIsa

关联到相应的类,即将开辟的内存空间指向所要关联的类!通过运行结果发现,在调用obj->initInstanceIsa之前,obj只有一个内存地址且是id类型,而调用之后明确了对象类型为LGPerson

4.影响对象内存大小因素分析

对象占用内存内存大小,由其成员变量确定

验证过程如下:

打印可知对象占用了8个字节,(内存中数据以16字节对齐)

添加几个属性

结论:成员变量越多,对象内存占用空间越大

x/5gx p:以5个16进制的排版打印对象,也可以说成:5个8字节的排版打印对象p

5. init和new

1.init(汇编和符号断点方式)

在LGPerson * person = [[LGPerson alloc] init];这行代码中添加断点,并设置:Debug -> Debug Workflow -> Always Show Disassembly。运行程序,结果如下图:

发现该过程调用objc_alloc_init ,加入符号断点查看objc_alloc_init汇编
汇编流程上图所示:[[LGPerson alloc] init];过程是调用了callAlloc方法创建对象之后,向该对象发送init消息。这里再添加init符号断点,继续运行程序


最后找到[NSObject init]函数,LGPerson没有实现init方法,寻到是其父类的,在源码中最终会调用到_objc_rootInit方法
结合源码,设置断点进行调试跟踪,流程和我们在汇编+符号断点的流程是一致的,如下图所示!最终返回的内容是callAlloc创建的对象自身

总结:init只是一个构造方法,没有参与对象初始化的创建;工厂模式设计

init的流程
  • [[LGPerson alloc] init];
  • objc_alloc_init;
  • 先进行对象内存开辟alloc流程;
  • objc发送init消息;
  • [NSObject init];
  • _objc_rootInit;
  • return self 返回当前对象;
2.new

探索方式和 -init() 一样, LGPerson * newlg = [LGPerson new];并不会直接调用+new(),而是执行了 objc_opt_new 方法,见下图:


源码上:

这里有类似callAlloc的判断流程,如果初次初始化,hasCustomCore()会返回true,见下图,这样会进入发送消息流程,即发送new消息。

因为LGPerson没有实现new类方法,所以在进行方法查找过程中会找到NSObject中,最终执行+[NSObject new];方法。见下图所示:

继续执行程序,会走到_objc_rootAllocWithZone中进行对象初始化

总结:new方法是个类方法,其是对[[cls alloc] init];流程的封装的过程

new的流程
  • 1.[LGPerson new];
  • 2.objc_opt_new;
  • 3.先fastpath(cls && !cls->ISA()->hasCustomCore()有没有缓存第一次没有缓存;
  • 4.没有缓存[NSObject new]发送new的消息;
  • 5.调用[callAlloc(self, false/checkNil/) init]方法
  • 6.有缓存时候直接[callAlloc(cls, false/checkNil/) init];
  • 7.objc发送init消息;
  • 8.[NSObject init];
  • 9._objc_rootInit;
  • 10.return self 返回当前对象;

虽然+new()是对[[cls alloc] init];的封装,但是依然建议使用[[cls alloc] init];进行对象的初始化!因为init作为构造器,可以自定义提供自己所需要的初始化方法;

更多解释
x/nuf <addr>n 表示要显示的内存单元的个数
------------------------
u 表示一个地址单元的长度
b 表示单字节
h 表示双字节
w 表示4字节
g 表示8字节
------------------------
f 表示显示方式,可取以下值:
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式显示变量
i 按指令地址格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量

iOS 底层原理-alloc流程相关推荐

  1. OC底层原理-alloc流程

    alloc流程分析 可以通过opensource下载objc4来查看alloc的源码,这里用objc4-818版本来分析 1.alloc + (id)alloc {return _objc_rootA ...

  2. iOS底层原理之内存管理

    文章目录 定时器 CADisplayLink.NSTimer GCD定时器 内存管理 iOS程序的内存布局 Tagged Pointer OC对象的内存管理 拷贝 引用计数的存储 dealloc 自动 ...

  3. iOS底层原理 - 常驻线程

    iOS底层原理 - 常驻线程 在 AFN 2.0 时代,会经常看到 AFN 创建一个常驻线程的方式: 0️⃣ AFN 2.0 时代的常驻线程 + (NSThread *)networkRequestT ...

  4. iOS底层原理之架构设计

    文章目录 何为架构? MVC - Apple版 MVC – 变种 MVP MVVM 设计模式 面试题 何为架构? 架构(Architecture):软件开发中的设计方案,类与类之间的关系.模块与模块之 ...

  5. 视频教程-iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-iOS

    iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化 小码哥教育CEO,曾开发了2个iOS的流行开源框架(MJRefresh.MJExtension),目前在国内的使用率非常高. 李 ...

  6. iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-李明杰-专题视频课程...

    iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-236人已学习 课程介绍         得遇名师,突飞猛进!iOS培训王者MJ(李明杰)老师精心研发,iOS进阶课程,实用技术 ...

  7. iOS底层原理探究 第一探. 事件传递和响应者链

    一. 声明:  本文意在探讨, 也参考了几位大神的文章, 在最后我会把链接发出来, 如果有理解错误的地方, 请大神们指正哈! 二. 前言:  最近自己做项目的时候, 用到了UITabbarContro ...

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

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

  9. iOS底层原理班实战视频教程 -李明杰-专题视频课程

    IOS底层开发班实战视频培训课程:APP逆向实战.加壳脱壳.数据安全.编译原理.iOS底层开发实现.iOS底层开发机制 iOS进阶课程,实用技术不断的更新和升级,更快帮助职场人士在开发领域脱颖而出.远 ...

最新文章

  1. layou split 属性
  2. linux 4t磁盘格式化,centos格式化大于2T的硬盘
  3. Magento教程 11:Inline Translation前台改文
  4. 安卓 spinner下拉框 做模糊查询_SEO数据查询工具
  5. 在ASP.NET Core 2.2 Web应用程序项目中自定义Bootstrap
  6. 学习编写测试桩之declspec (dllexport)篇
  7. Codeblocks中的empty project和console application
  8. 火狐firebug和firepath插件安装方法
  9. AI机器人AI源码营销机器人电销机器人智能电话机器人拨号机器人语音机器人空号识别FreeSWITCH呼叫中心中间ipbxIPBX科大识别阿里识别语音识别语音翻译
  10. 新闻与传播c刊_3本新闻传播类期刊入围新版C刊扩展目录
  11. 拉普拉斯矩阵 拉普拉斯算子 图论
  12. cpu和gpu各自的作用
  13. springboot(原先是mybatis)整合mybatis-plus,注入报错Error creating bean with name 'sqlSessionFactory'
  14. 区间估计Bootstraping/Jackknife
  15. IvParameterSpec 干什么用的
  16. vue项目引入vue-i18n,实现中英文切换
  17. shell脚本——业务上线前扫描网段内所有ip地址
  18. 迟了太久,就不必到了
  19. 金链盟大赛新亮点|第一创业证券用区块链创新其报价业务
  20. 手机邮箱不下载可以使用么,手机邮箱怎么登录呢?

热门文章

  1. Anki 插入编程代码
  2. [附源码]Java计算机毕业设计SSM财务管理系统
  3. 有没有云计算机,有没有免费的云电脑可以让玩家试用?
  4. EF Core DbContext 线程安全
  5. 算法设计与分析: 6-24 最长距离问题
  6. linux 下免安装版软件,米聊linux版软件下载
  7. 我建议,专家不要再建议了
  8. opencv边界识别
  9. SMART S7-200PLC串行自由口通讯(耐压测试仪)
  10. 使用jsoup获取微信公众号文章发布时间