内存管明确析

源代码 2024-9-19 17:42:51 201 0 来自 中国
目次

1.内存地区剖析
2.什么是引用计数(retainCount)
3.什么是指针和所在
4.内存走漏、野指针、空指针、僵尸对象
5.内存管理原则
6.常用内存修饰词
7.alloc、init、new、dealloc 区别
8.强引用、弱引用、循环引用
9.weak详解
10.深浅拷贝明确
https://blog.csdn.net/z119901214/article/details/82417153?spm=1001.2014.3001.5502
1.内存地区剖析

常说的内存有五大区,分别为:堆区、栈区、全局区(静态区)、常量区、代码区,详细如下图。
增补分析:
1.内容分为假造内存+物理内存,正常初始化空间分配的都是假造内存,初始化实例才会占用物理内容。Instrument 的 Allocations 工具可以监控,All Heap & Anoymous VM 代表假造内存 Dirty Memory,resident Size 就是物理内存的巨细。
2.访问内存实在都是访问的逻辑所在,须要转换后才能访问物理内存, CPU根据逻辑所在通过界限寄存器判断是否越界,越界所在错误,反之加上基址寄存器转换成物理内存所在。
3.iOS 内存中的对象重要有两类:
一类是值范例;比如 int、float、struct 等根本数据范例,值范例会被放入栈中,依照先辈后出的原则。
一类是引用范例;也就是继续 NSObject 类的全部 OC 对象。引用范例会放在堆中,当给对象分配内存空间时,对随机在内存中开辟空间。


  • iOS 内存过多而被 Kill 掉?基于什么原则?
iOS 利用的是低内存处理机制 Jetsam,基于优先级队列的机制。 当内存过低的时间,就会在队列中举行广播,盼望各人只管开释内存,如果一段时间后,仍旧内存不敷,就会开始 Kill 历程,直到内存够用。


  • 其他相干增补分析:
1.RAM:运行内存,不能掉电存储。ROM:存储性内存,可以掉电存储,比方内存卡、Flash。由于RAM范例不具备掉电存储本领即一掉电数据消散,以是app步伐一样寻常存放于ROM中。RAM的访问速率要远高于ROM,代价也要高。
2.App步伐启动:App步伐启动,体系会把开启的谁人App步伐从Flash或ROM内里拷贝到内存(RAM),然后从内存内里实行代码。另一个原因是CPU不能直接从内存卡内里读取指令(须要Flash驱动等等)。
3.在iOS中,堆区的内存是应用步伐共享的,堆中的内存分配是体系负责的;体系利用一个链表来维护全部已经分配的内存空间(体系仅仅记载,并不管理详细的内容);变量利用竣事后,须要开释内存,OC中是根据引用计数为0,就分析没有任何变量利用该空间,那么体系将直吸取回;当一个app启动后,代码区,常量区,全局区巨细已固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时间刻变化的(堆的创建烧毁,栈的弹入弹出),以是当利用一个指针指向这两个区内里的内存时,肯定要注意内存是否已经被开释,否则会产生步伐崩溃(便是野指针报错)。
4.iOS是基于UNIX、Android是基于Linux的,在Linux和unix体系中,内存管理的方式根本类似;Android应用步伐的内存分配也是云云。除此以外,这些应用层的步伐利用的都是假造内存,它们都是创建在利用体系之上的,只有开辟底层驱动或板级支持包时才会接触到物理内存。举例:在嵌入式Linux中,实际的物理所在只有64M乃至更小,但是假造内存却可以高达4G。


  • 线程中栈与堆是公有的照旧私有的?
1.在多线程情况下,每个线程拥有一个栈和步伐计数器,栈和步伐计数器用来生存线程实行汗青和线程实行的状态,是线程私有资源。
2.堆资源是同一历程内多线程共享的。
3.png 2.什么是引用计数(retainCount)

引用计数是一个简朴而有用的管理 OC 对象生命周期的方式,不管是 OC 照旧 Swift 其内存管理方式(原理)都是基于引用计数的。引用计数也叫内存计数。
原理:
引用计数可以有用的管理对象的生命周期,当我们创建一个新对象的时间,他(该对象所在的内存块)的引用计数为1,当有一个新的指针指向这个对象时,我们将其引用计数加1,当某个指针不在指向这个指针时,我们将其应用计数减1,当对象的引用计数变为0时,分析这块内存不在被任何指针指向,这个时间体系就会将对象烧毁,接纳内存,从而达到管理内存。


  • 在 OC 中对象什么时间会被开释(或者对象占用的内存什么时间会被接纳利用)?
当对象没有被任何变量引用(也可以说是没有指针指向该对象)的时间就会被开释。
5.png

  • 引用计数举例
6.png

  • 注意一些特别的情况
NSString 引用计数标题比较复杂,详细参考链接:
https://blog.csdn.net/shisanshuno1/article/details/79810620
https://www.jianshu.com/p/990e5252e0fb
3.什么是指针和所在

指针:实在是一个内存所在,对于一个内存单元来说,单元的所在即为指针。指针是用于存放变量的所在,用于存放变量所在的变量称为“指针变量”。步伐的实行过程中可以通过指针来找到要利用的数据和可实行的函数代码。
所在:表现内存空间的一个位置点,他是用来赋给指针的,所在自己是没有巨细概念,指针指向变量的巨细,取决于所在反面存放的变量范例。变量的所在:盘算机的内存是以字节为单元的一片连续的存储空间,每一个字节都有一个编号,这个编号就是该内存的所在,而且这些编号是连续的。而一个变量实质上代表了“内存中的某个存储单元”,而这些存储单元的编号就是该变量的所在。
分析:
1.指针统共可以分为两种,函数指针和数据指针。
2.指针的利用肯定要特别注意,不能越界,不要利用未初始化过的指针。
一级、二级、多级指针:https://blog.csdn.net/OMGMac/article/details/122152963


  • 指针和所在的区别
1.指针和所在最大的区别就是指针是有范例的,所在是没有范例的。我们可以通过绝对所在的方式找到函数和数据,但是所在是没有范例的,不能对所在举行算术利用,在涉及诸如数组等利用时就不能通过所在的自增和自减来访问数组的各个变量。但是通过对指针的引用,就可以通过对指针举行一系列的加加减减利用很方便的访问数组的各个元素。
2.指针是由所在和范例两部门构成的;指向数据的指针不光记载该数据的在内存中的存放的所在,还记载该数据的范例,即在内存中占用几个字节,这是所在所不具有的。指向函数的指针不光记载函数的入口所在,也记载该函数的范例,即函数的返回值范例和该函数的参数范例。
3.指针意味着已经有一个指针变量存在,他的值是一个所在,指针变量自己也存放在一个长度为四个字节的所在当中,而所在概念自己并不代表有任何变量存在。指针的值如果没有限定,通常是可以变化的也可以指向别的一个所在。


  • 指针变量的界说和指针变量的基范例
1.界说指针变量的一样寻常形式如下:
范例名  *指针变量名1; *指针变量名2,...........;  比方:int *p,*q;注意:每个变量前的星号是一个分析符,用来分析该变量是指针变量。变量前的星号不可省略,若省略了星号分析符,就变成了将 p 和 q 界说为整形变量。2.指针变量的基范例:
在这里重要分析一下为什么指针必须区分基范例。一个指针变量中存放的是一个存储单元的所在。这里“一个存储单元”中的“一”所代表的字节数是差别的:对 short int 范例整数而言,它代表2个字节;对 int 范例而言,它代表4个字节,这就是基范例的差别寄义。在涉及指针的移动,也就是要对所在举行增减运算,这时指针移动的最小单元是一个储存单元,而不是1个字节。因此对于基范例差别的指针变量,其内容增1、减1所“凌驾”的字节数差别,因此基范例差别的指针变量不能混淆利用。


  • 常量指针和指针常量
https://juejin.cn/post/6972041530726940685
8.png 4.内存走漏、野指针、空指针、僵尸对象

内存走漏
我们知道 iOS 开辟有 “ARC 机制” 资助管理内存,但在实际开辟中如果处理欠好堆空间上的内存照旧会存在内存走漏的标题。如果内存走漏严肃,终极会导致步伐的崩溃。我们须要查抄我们的 App 有没有内存走漏,而且快速定位到内存走漏的代码,比较常用的内存走漏的排查方法有两种,都在 Xcode 中可以直接利用:第一种为静态分析方法(Analyze)、第二种为动态分析方法(Instrument 工具库里的 Leaks),一样寻常保举利用第二种。
相干链接:
https://juejin.cn/post/6844903681649803278
https://juejin.cn/post/7033233548249153550


  • 什么时间会发生内存走漏
https://juejin.cn/post/6844904070344343565
https://www.jianshu.com/p/6c4849dcef55


  • 举例:栈 - 走漏
int number = 4;int *a = malloc(8);a = &number;free(a);给指针 a 分配了8个字节的所在,a 又指向了 number 的所在,末了 a 开释了。这时间开释的是 number 的所在,而 number 是在栈区中,不能被手动开释,这时间就出现了栈的内存走漏。野指针
野指针:野指针出现的原因是指针没有赋值或者指针指向的对象已经被开释掉了,野指针指向一块随机的垃圾内存,向他们发送消息会报 EXC_BAD_ACCESS 错误导致步伐崩溃。野指针是不为 nil 但是指向已经被开释内存的指针,比如 __unsafe_unretain 或者 assign 的指针,对象开释后会出现野指针。
相干链接:
https://juejin.cn/post/6844903747538141191
https://juejin.cn/post/6930979515552235528


  • 什么情况下会产生野指针?
12.png 空指针
空指针:空指针差别于野指针,它是一个没有指向任何东西的指针,空指针是有用指针,值为 nil、NULL、Nil 或0等,给空指针发送消息不会报错,只是不相应消息而已。应该给野指针及时赋予零值变成有用的空指针,制止内存报错。
僵尸对象
僵尸对象:1个已经被开释的对象就叫做僵尸对象。一个 OC 对象引用计数为0被开释后就变成僵尸对象了,僵尸对象的内存已经被体系接纳,固然大概该对象还存在,数据依然在内存中,但僵尸对象已经是不稳固对象了,不可以再访问或者利用,它的内存是随时大概被别的对象申请而占用的。该对象已经被开释,但是内存空间还没有被复写,当前对象照旧可以访问,但是体系已标志为开释,随时有大概重新被分配举行复写,这个对象这时是不能访问的。
相干链接:https://juejin.cn/post/6844903801267159047
其他注意事项
1.制止循环引用,如果两个对象相互为对方的成员变量,那么这两个对象肯定不能同时为 retain,否则,两个对象的 dealloc 函数形成死锁,两个对象都无法开释。
2.不要滥用 autorelease,如果一个对象的生命周期很清楚,那最幸亏竣事利用后立刻调用 release,过多的等候 autorelease 对象会给内存造成不须要的负担。
3.编码过程中发起将内存相干的函数,如 init、dealloc、viewdidload、viewdidunload 等函数放在最前,这样比较显眼,忘记处理的概率也会降低。
4.AutoReleasePool 对象在开释的过程中,在 iOS 下,release 和 drain 没有区别,但为了划一性照旧保举利用 drain。
提问

  • 如果一个对象开释前被加到了 NotificationCenter 中,不在 NotificationCenter 中 remove 这个对象大概会出现什么标题?
起首对于 NotificationCenter 的利用,我们都知道只要添加对象到消息中心举行关照注册,之后就肯定要对其 remove 举行关照注销。将对象添加到消息中心后,消息中心只是生存该对象的所在,消息中心到时间会根据所在发送关照给该对象,但并没有取得该对象的强引用,对象的引用计数不会加1。如果对象开释后却没有从消息中心 remove 掉举行关照注销,也就是关照中心还生存着谁人指针,而谁人指针指的对象大概已经被开释烧毁了,谁人指针就成为一个野指针,当关照发生时会向这个野指针发送消息导致步伐崩溃。


  • 提问:什么是安全开释?
开释掉不再利用的对象同时不会造成内存走漏或指针悬挂标题称其为安全开释。


  • 提问:野指针是什么,iOS 开辟中什么情况下会有野指针?
指针是不为 nil,但是指向已经被开释的内存的指针。 __unsafe_unretain 或者 assign 的指针,对象开释后会出现野指针。 一样寻常情况下 OC 利用了 weak 指针,在对象烧毁时指针会置 nil。
5.内存管理原则

一样寻常是 “谁创建,谁开释” 的原则;OC 提供了两种种内存管理方式:MRC 手动引用计数器和 ARC 主动引用计数,苹果保举 ARC 这个新技能来管理内存。
根本原则:
1.当你通过 new、alloc、copy 或 mutabelCopy 方法创建一个对象时,它的引用计数为1,当不再利用该对象时,应该向对象发送 release 或者 autorelease 消息开释对象。
2.当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为 autorelease,则不须要实行任何开释对象的利用;
3.如果你计划取得对象全部权,就须要保存对象并在利用完成之后开释,且必须包管 retain 和 release 的次数对等。


  • 注意:ARC 与 主动开释池区别?
区别就是 ARC 会主动为对象加上  retain,release,而主动开释池就是把 release 变成 autorelease。
1.在 MRC 中,调用 [obj autorelease] 来延长内存的开释是一件简朴天然的事,在 ARC 下我们乃至可以完全不知道 Autorelease 就能管理好内存,而在这背后 objc 和编译器都帮我们做了很多事故。
2.主动开释池是 OC 的一种内存主动接纳机制,可以将一些暂时变量通过主动开释池往返收同一开释,主动开释池自己烧毁的时间,池子内里全部的对象都会做一次 release 利用。它可以延长参加 autoreleasepool 中变量 release 的机遇,创建的变量会在超出其作用域的时间 release,autorelease 本质上就是延长调用 release。
3.在没有手动添加 autoreleasepool 的情况下,autoreleasepool 对象是在当前的 runloop 迭代竣事时开释的,在收到内存告诫时也会举行内存开释,而它能够开释的原因是体系在每个 runloop 迭代中都参加了主动开释池 Push 和 Pop (每一个线程都有一个默认 autoreleasepool)。
https://www.jianshu.com/p/e69e303ba1b4
有三种情况是须要我们手动添加 autoreleasepool 的:
1.你编写的步伐不是基于 UI 框架的,比如说下令行工具;
2.你编写的循环中创建了大量的暂时对象;
3.你创建了一个辅助线程。


  • 增补:对象是什么时间被 release 的?
引用计数为0时被 release。autorelease 实际上只是把对 release 的调用延长了,对于每一个 Autorelease,体系只是把该 Object 放入了当前的 Autorelease pool 中,当该 pool 被开释时该 pool 中的全部 Object 会被调用 Release。对于每一个 Runloop, 体系会隐式创建一个 Autorelease pool,这样全部的 release pool 会构成一个象 CallStack 一样的一个栈式结构,在每一个 Runloop 竣事时,当前栈顶的 Autorelease pool 会被烧毁,这样这个 pool 里的每个 Object(就是 autorelease 的对象)会被 release。那什么是一个 Runloop 呢? 一个 UI 事故,Timer call、 delegate call 都会是一个新的 Runloop。
疑问待办理?:就是比如创建应用运行的时间在main入口的时间就体系创建好了主动开释池,在iOS开辟中如今就主动都是设置了ARC了。那么在开辟中用对象等时间怎么利用这2者的或者什么时间用的哪个?
6.常用内存修饰词

在 iOS 开辟中,我们最常用到的就是那些修饰属性的关键字。
内存管理有关的关键字:strong、retain、weak、assign、copy
线程安全有关的的关键字:nonatomic、atomic
访问权限有关的的关键字:readonly、readwrite(只读,可读写)
修饰变量的关键字:const、static、extern
相干链接:https://juejin.cn/post/6844903773131767816
内存管理有关的关键字

  • stronng:
用于修饰一些 OC 对象范例的数据,比如:NSNumber、NSString、NSArray、NSDate、NSDictionary、模子类等,它被一个强指针引用着,是一个强引用,指向对象内存所在,内存计数+1。在 ARC 情况劣等同于 retain,这一点区别于 weak。它是一我们通常所说的指针拷贝(浅拷贝),内存所在保持稳固,只是天生了一个新的指针,新指针和引用对象的指针指向同一个内存所在,没有天生新的对象,只是多了一个指向该对象的指针。
注意:由于利用的是一个内存所在,当该内存所在存储的内容发生变动的时间,会导致属性也跟着变动。


  • retain:
表现持有特性,setter 方法将传入参数先保存再赋值,传入参数的 retaincount 会+1。利用 retain 会对内存的引用计数举行利用以包管持有的对象不会被接纳,retain:setter 方法对参数举行 release 旧值,再 retain 新值。
注意:retain 和 strong 区别:作用是一样的,只是写法上的区别。在非 ARC 机制时是用 retain 关键字修饰;在 ARC 机制后一样寻常都用 strong 关键字来取代 retain 了。


  • release
与 retain 配对利用的方法是 release,由于 retain 是将内存的引用计数加一即对对象举行一次持有,release 是将内存的引用计数减一即竣事对对象的持有。


  • copy
同样用于修饰 OC 对象范例的数据,同时在 MRC 时期用来修饰 block,由于 block 须要从栈区 copy 到堆区,在如今的 ARC 期间,体系主动给我们做了这个利用,以是如今利用 strong 或者 copy 来修饰 block 都是可以的。
注意:
1.copy 和 strong 类似点在于都是属于强引用,都会是属性的计数加1。
2.copy 和 strong 差别点在于,它所修饰的属性当引用一个属性值时,是内存拷贝(深拷贝),就是在引用时会天生一个新的内存所在和指针所在来,和引用对象完全没有类似点,因此它不会由于引用属性的变动而改变。
3.copy 在修饰 Mutable 可变范例会在内存里拷贝一份对象,两个指针指向差别的内存所在,copy 出来的新对象是不可变范例的。而修饰 NSString、NSArray 等平凡范例充当 strong 利用,内存计数+1。详细的深浅拷贝情况剖析看拷贝专讲。
举例:用 @property 声明的 NSString(或NSArray,NSDictionary)常常利用 copy 关键字,为什么?如果改用 strong 关键字,大概造成什么标题?
https://blog.csdn.net/weixin_33725515/article/details/88027657


  • retain、strong、copy 区别
https://blog.csdn.net/qiushisoftware/article/details/102647011
https://www.cnblogs.com/fengmin/p/5390073.html


  • weak:
同样常常用于修饰 OC 对象范例的数据,修饰的对象在开释后指针所在会主动被置为 nil,这是一种弱引用。更为详细的剖析看 weak 专讲。
注意:在 ARC 情况下,为制止循环引用,每每会把 delegate 属性用 weak 修饰;在 MRC 下利用 assign 修饰。当一个对象不再有 strong 范例的指针指向它的时间,它就会被开释,纵然另有 weak 型指针指向它,那么这些 weak 型指针也将被扫除。


  • assign:
常常用于非指针变量,用于底子数据范例 (比方 NSInteger)和 C 数据范例(int、float、double、char 等),别的另有 id 范例。用于对根本数据范例举行复制利用,不更改引用计数。也可以用来修饰对象,但是被 assign 修饰的对象在开释后,指针的所在照旧存在的,也就是说指针并没有被置为 nil,成为野指针。
注意:
1.之以是可以修饰根本数据范例,由于根本数据范例一样寻常分配在栈上,栈的内存会由体系主动处理,不会造成野指针。
2.在 MRC 下常见的 id delegate 每每是用 assign 方式的属性而不是 retain 方式的属性,为了防止 delegation 两头产生不须要的循环引用。比方:对象 A 通过 retain 获取了对象 B 的全部权,这个对象 B 的 delegate 又是 A, 如果这个 delegate 是 retain 方式,两个都是强引用,相互持有,那根本上就没偶然机开释这两个对象了。


  • weak 和 assign 的区别:
1.修饰的对象:weak 修饰 OC 对象范例的数据,assign 用来修饰黑白指针变量。
2.引用计数:weak 和 assign 都不会增长引用计数。
3.开释:weak 修饰的对象开释后指针所在主动设置为 nil,assign 修饰的对象开释后指针所在依然存在,成为野指针。
4.修饰 delegate 在 MRC 利用 assign,在 ARC 利用 weak。


  • unsafe_unretained
unsafe_unretained 范例指针指向一块内存时,内存的引用计数也不会增长,这一点与 weak 划一。但是与 weak 范例差别的是,当其所指向的内存被烧毁时(对象被烧毁),unsafe_unretained 范例的指针并不会被赋值为 nil,也就是变成了一个野指针。对野指针指向的内存举行读写,步伐就会 crash。
注意:当声明一个局部变量时,利用方式有点区别,要在关键词前面加双下划线__。
相干链接:https://www.jianshu.com/p/bd6aa1e62717
线程安全有关的的关键字

  • nonatomic
nonatomic 非原子利用:不加锁,线程实行快,但是多个线程访问同一个属性时结果无法预料。也就是多线程并发访问性能高,但是访问不安全。


  • atomic
atomic 原子利用:加锁,包管 getter 和 setter 存取方法的线程安全(仅对 setter 和 getter 方法加锁)。 由于线程加锁的原因,在别的线程来读写这个属性之前会先实行完当前的利用。
比方:线程 A 调用了某一属性的 setter 方法,在方法还未完成的情况下,线程 B 调用了该属性的 getter 方法,那么只有在实行完 A 线程的 setter 方法以后才实行 B 线程的 getter 利用。当几个线程同时调用同一属性的 setter 和 getter方法时会得到一个合法的值,但是 get 的值不可控(由于线程实行的次序不确定)。
注意:atomic 只针对属性的 getter/setter 方法举行加锁,以是安全只是针对 getter/setter 方法来说,并不是整个线程安全,由于一个属性并不光有
getter/setter 方法。举例:如果一个线程正在 getter 或者 setter 时,有别的一个线程同时对该属性举行 release 利用,如果 release 先完成会造成 crash。
访问权限有关的的关键字

  • readonly
这个属性变量就是表明变量只有可读方法,也就是说你只能利用它的 get 方法。由于他只天生 getter 方法而不天生 setter方法(getter 方法没有 get 前缀)。


  • readwrite
这个属性是变量的默认属性,就是如果你 readwrite and readonly 都没有利用,那么你的变量就是 readwrite 属性,通过参加 readwrite 属性你的变量就会有 get 和 set 方法。它是可读可写特性,会天生不带额外参数的 getter 和 setter 方法(setter 方法只有一个参数)。
修饰变量的关键字
相干链接:
https://www.jianshu.com/p/2fd58ed2cf55
条记17第11点:https://www.jianshu.com/p/b619c4286d26


  • const
常量修饰符,表现不可变。可以用来修饰右边的根本变量和指针变量,放在谁的前面修饰谁(根本数据变量 p,指针变量 *p)。
常用写法比方:
1.const 范例 * 变量名 a
可以改变指针的指向,不能改变指针指向的内容。 const 放前面束缚参数,表现 *a 只读,只能修改所在 a,不能通过 a 修改访问的内存空间。
2.范例 * const 变量名
可以改变指针指向的内容,不能改变指针的指向。 const 放反面束缚参数,表现 a 只读,不能修改 a 的所在,只能修改 a 访问的值,不能修改参数的所在。


  • 常量(const)和宏界说(define)的区别:
利用宏和常量所占用的内存差别不大,宏界说的是常量,常量都放在常量区,只会天生一份内存。
1.编译时间:宏是预编译(编译之前处理),const 是编译阶段。导致利用宏界说过多的话,随着工程越来越大,编译速率会越来越慢。
2.宏不做查抄,不会报编译错误,只是更换,const 会编译查抄,会报编译错误。
3.宏能界说一些函数和方法, const 不能。


  • static
界说所修饰的对象只能在当前文件访问,不能通过 extern 来引用。默认情况下的全局变量作用域是整个步伐(可以通过 extern 来引用),被 static 修饰后仅限于当前文件来引用,其他文件不能通过 extern 来引用。
修饰局部变量:
1.偶然盼望函数中的局部变量的值在函数调用竣事后不消散而继续保存原值,即其占用的存储单元不开释,在下一次再调用的时间该变量已经有值。这时就应该指定该局部变量为静态变量,用关键字 static 举行声明。
2.延长局部变量的生命周期(没有改变变量的作用域,只在当前作用域有用),步伐竣事才会烧毁。
3.局部变量只会天生一份内存,只会初始化一次。把它分配在静态存储区,该变量在整个步伐实行期间不开释,其所分配的空间始终存在。
注意:当在对象 A 里这么写 static int i = 10; 当 A 烧毁掉之后,这个 i 还存在。当我再次 alloc init 一个 A 的对象之后,在新对象里依然可以拿到 i = 90,除非杀死步伐再次进入才能得到 i = 0。
修饰全局变量:
1.只能在本文件中访问,修改全局变量的作用域,生命周期不会改。
2.制止重复界说全局变量(单例模式)。


  • extern
只是用来获取全局变量(包罗全局静态变量)的值,不能用于界说变量。先在当前文件查找有没有全局变量,没有找到才会去其他文件查找(优先级)。


  • const、static、extern 组合利用
@property、@synthesize、@dynamic
相干链接:
https://www.jianshu.com/p/490424151e31
https://www.ngui.cc/el/1176329.html?action=onClick


  • @Property
1.属性(property)是 Objective-C 的一项特性,用于封装对象中的数据。这一特性可以令编译器主动编写与属性相干的存取方法,而且生存为各种实例变量。
2.属性的本质是实例变量与存取方法的结合,@property = ivar + getter + setter
3.@property 会使编译器主动编写访问这些属性所需的方法,此过程在编译期完成,称为主动合成 (autosynthesis)。与此相干的另有两个关键词:@dynamic 和 @synthesize。


  • @dynamic
告诉编译器不要主动创建实现属性所用的实例变量,也不要为其创建存取方法。纵然编译器发现没有界说存取方法也不会报错,运行期会导致崩溃。


  • @synthesize
在类的实现文件里可以通过 @synthesize 指定实例变量的名称。


  • 注意
在 Xcode 较早版本之前,@property 共同 @synthesize 利用,@property 负责声明属性,@synthesize 负责让编译器天生 带下划线的实例变量而且主动天生 setter、getter 方法。Xcode 新版本之后 @property 得到加强,直接一并更换了 @synthesize 的工作。
15.png 其他
7.alloc、init、new、dealloc 区别

alloc
alloc:开辟一块内存给对象,让它不开释而且把所在返回给指针。
通过检察 objc 源码发现 alloc 方法重要做了三件事故:
1.盘算申请内存空间巨细
2.开辟内存空间
3.把类的信息关联到 isa 上
18.png init
init:对这块内存举行初始化。通过检察 objc 源码发现 init 方法什么事故都没做,而是直接 return (id)self; 。没什么复杂的,苹果这样写原因:init 只是一个构造方法,为我们自界说类的时间重写提供便利。自界说类每每会有自己的构造方法即重写 init,如 - (instancetype)initWithName: (NSString *)name;
new
通过检察源码 new 调用的是 [callAlloc(self, false) init], 相称于调用了 alloc init 两个方法。区别就在于 new 不会调用自界说的构造方法,以是一样寻常开辟中不建义利用 new。
dealloc
dealloc:对象的烧毁,对象要开释必须实行根类 NSObject 中 -(void)dealloc()
方法,由于该方法终极会去实行 C++ 的 objc_object::rootDealloc() 方法。对象的烧毁终极就是在 rootDealloc() 中烧毁。
注意:
1.MRC 要调用 [super dealloc];  ARC 不能调用 [super dealloc];
2.MRC 要调用:在 MRC 情况下体系不会主动开释对象。由于对象的开释必须调用 c++ 的 rootDealloc() 方法。因此一旦我们重写了对象的 dealloc 方法,如果不调用 [super dealloc]; 那么就不会调用到根类 NSObject 中 -(void)dealloc() 方法。对象也就开释不掉,只是单单实行了该类 -(void)dealloc() 方法并没有开释对象。以是 MRC 一旦重写了 -(void)dealloc() 方法肯定要调用 [super dealloc];,这样才能开释该实例对象。
3.ARC 不能调用:ARC 会主动帮我们在代码的得当位置插入 [obj release]; 我们什么也不做,ARC 就调用了一次 C++ 的 objc_object::rootDealloc() 方法。此时如果我们再次调用了 [super dealloc]; 方法,那么终极会调用根类 NSObject 的 release() 方法,就会实行到 C++ 的 objc_object::rootDealloc() 方法,由于 ARC 已经开释该对象了,这时再次 free(this) 开释该对象会发生错误。以是 ARC 情况下不能调用 [super dealloc];,否则会导致该实例对象重复开释。
相干链接:
https://blog.csdn.net/pk_sir/article/details/107384255
8.强引用、弱引用、循环引用

默认情况下,一个指针都会利用__strong 属性,表明这是一个强引用。当全部强引用都去除时,对象才能被开释。但偶然间我们大概要克制这种活动:一些集合类不应该增长其元素的引用,大概对导致对象无法开释,大概会出现循环引用,导致无法开释,这种情况下我们要利用弱引用 __weak。weak 是弱引用,计数器不会加1,并在引用对象被开释的时间主动设置为 nil。ARC 提供四种修饰符分别是:__strong、__weak、__autoreleasing、__unsafe_unretained。
相干链接:http://t.zoukankan.com/geek6-p-3947985.html


  • 增补:weak 属性修饰词与 __weak 什么关系,一个东西?
weak 属性修饰词:修饰属性对象,属于弱引用,不利用的时间直接开释内存,不会造成野指针。__weak:用来办理循环引用的闭环,达到烧毁对象的结果。底层应该都差不多,只是利用场景的差别差别。
强引用(__strong)
强引用:持有所指向对象的全部权,无修饰符情况下步伐默认的变量修饰符就是  __strong。如需欺压开释可置 nil。在 ARC 中修饰符是 __strong,比如 __strong NSObject *obj;
注意:__strong 与变量:在 ARC 模式下,id 范例和 OC 对象的全部权修饰符默认是 __strong。当一个变量通过 __strong 修饰符来修饰,当该变量超出其所在作用域后,该变量就会被废弃,同时值给该变量的对象也会被开释。__strong 修饰后,对象的引用计数会增长,在作用域外不会烧毁。
弱引用(__weak)
弱引用:不持有所指向对象的全部权,引用指向的对象内存被接纳之后,引用自己会置 nil 制止野指针。在 ARC 中修饰符是 __weak,比如 __weak NSObject *obj;  比如制止循环引用的弱引用声明:__weak __typeof(self) weakSelf = self;
注意:__weak 修饰后,对象引用计数不会增长,在作用域外会主动置为 nil。


  • 强引用与弱引用区别
1.两者区别简朴讲的话就是:强引用持有对象,而弱引用不持有对象。
2.什么时间用 strong 和 weak,平时一样寻常都是用 strong,也就是默认不添加,在会照成循环引用时才利用 weak。
循环引用
1.当两个差别的对象各有一个强引用指向对方,那么循环引用就产生了,每个对象的引用计数都会+1,无法得到内存的开释。只有在堆中的相互引用无法接纳,有大概造成循环引用,简朴来说就是调用了 new,alloc 等关键字才有大概出现循环引用。
2.循环引用的实质是多个对象相互之间有强引用,不能开释导致无法接纳,办理方法一样寻常是将 strong 引用改为 weak 引用。


  • 署理(delegate)循环引用标题
delegate 是 iOS 开辟中比较常碰到的循环引用,一样寻常在声明 delegate 的时间都要利用弱引用 weak 或者 assign,固然怎么选择利用 assign 照旧 weak,MRC 的话只能用 assign,在 ARC 的情况下最好利用 weak,由于 weak 修饰的变量在开释后主动指向 nil,防止野指针存在。
delegate 属性的声明一样寻常是 weak:@property (nonatomic, weak) id <TestDelegate> delegate;


  • block 循环引用标题
1.由于 block 会对 block 中的对象举行持有利用,就相称于持有了此中的对象,如果此时 block 中的对象又持有了该 block 则会造成循环引用,办理方案就是利用 weak 举行弱引用。比如 block 在 copy 时都会对 block 内部用到的对象举行强引用造成循环引用。
2.并不是全部的 block 都会造成循环引用,只有被强引用了的 block 才会产生循环引用,详细看情况。比如 dispatch_async(dispatch-get_main_queue(),^{}),[UIView animateWithDuration:1 animations:^{}] 这些体系方法等,或者 block 并不是其属性而是暂时变量即栈 block,这些都不会产生循环引用。
相干链接:https://juejin.cn/post/6998919742010425357
1.block 循环引用举例:
self.myBlock = ^{     [self doSomething];//这个 self 就是强引用};由于 block 会对 block 中的对象举行持有利用,就相称于持有了此中的对象,如果此时 block 中的对象又持有了该 block,则会造成循环引用。办理方案就是利用 __weak 修饰 self 即可:__weak typeof(self) weakSelf = self;self.myBlock = ^{     [weakSelf doSomething];};2.利用 block 不会造成循环引用的分析:
21.png 3.__block 的作用
__block 的根本作用就是把 block 的外部变量的所在从栈区放到堆区。__block 只要观察到某一个成员变量被 block 所持有,就会把该变量的内存所在从栈区放到堆区。因此在堆区的该成员变量就会变成有用户自己分配和开释,不会被体系管理造成丢失。
相互链接:https://www.jianshu.com/p/3d3878649b4d


  • Timer 定时器循环引用标题
在控制器内,创建NSTimer作为其属性,由于定时器创建后也会强引用该控制器对象,那么该对象和定时器就相互循环引用了。办理办法为:可以利用手动断开循环引用,如果是不重复定时器,在回调方法里将定时器 invalidate 并置为 nil 即可;如果是重复定时器,在符合的位置将其 invalidate 并置为 nil 即可。
相干链接:
https://juejin.cn/post/6992501463058481188
https://juejin.cn/post/6844903968250789896
__unsafe_unretained
__unsafe_unretained 修饰后引用计数不会增长,在作用域外不会置空,会造成野指针闪退。
9.weak详解

概念
weak 是弱引用,用 weak 来修饰、形貌所引用对象的计数器并不会增长,而且 weak 会在引用对象被开释的时间主动置为 nil,这也就制止了野指针访问坏内存而引起奔溃的情况,别的 weak 也可以办理循环引用
拓展:为什么修饰署理利用 weak 而不是用 assign?
assign 可用来修饰根本数据范例,也可修饰 OC 的对象,但如果用 assign 修饰对象范例指向的是一个强指针,当指向的这个指针开释之后,它仍指向这块内存,必须要手动给置为 nil,否则会产生野指针。如果还通过此指针利用那块内存,会导致EXC_BAD_ACCESS 错误,由于调用了已经被开释的内存空间。而 weak 只能用来修饰 OC 对象,而且相比 assign 比较安全,如果指向的对象消散了,那么它会主动置为 nil,不会导致野指针。
相干链接:
https://www.jianshu.com/p/fec68e84cee9
https://juejin.cn/post/6993634762896195615
weak实现原理的概括
当 weak 引用指向的对象被开释时,又是怎样行止理 weak 指针的呢?
当开释对象时,其根本流程如下:
1.调用 objc_release
2.由于对象的引用计数为0,以是实行 dealloc
3.在 dealloc 中,调用了 _objc_rootDealloc 函数
4.在 _objc_rootDealloc 中,调用了 object_dispose 函数
5.调用 objc_destructInstance
6.末了调用 objc_clear_deallocating
调用的 objc_clear_deallocating 函数中,调用了 clearDeallocating,它终极是利用了迭代器来取 weak 表的 value,然后调用 weak_clear_no_lock,然后查找对应的 value,将该 weak 指针置空。
IBOutlet 连出来的视图属性为什么可以被设置成 weak?
在 storyboard 中添加一个控件引用关系是这样的(以 UIbutton 为例): UIviewController -> UIview -> UIbutton。此时 UIviewController 强引用着 UIview,UIview 强引用着 UIbutton,IBoutlet 连线到控制器的 .m 或者 .h 中作为视图的属性时用 weak 修饰就可以了, (觉得用 strong 修饰也可以但是没有须要)。添加到子控件也是强引用: UIbutton 就是添加到了 UIviewController 的 view 上。
总结
1.为了管理全部的引用计数和 weak 指针,苹果创建了一个全局的 SideTables,它是一个全局的 hash 表(即 weak 的原理在于底层维护了一张 weak_table_t 结构的 hash 表),用于存储指向某个对象的全部 weak 指针,key 是所指向对象的所在,value 是 weak 指针的所在数组,内里存放的都是 SideTable 结构体。
2.weak 关键字的作用是弱引用,计数器不会加1,并在引用对象被开释的时间主动设置为 nil。
3.对象引用计数相干的利用是原子性的,如果多个线程同事利用一个对象的引用计数会造成数据庞杂,同时在内存中的对象数据量大,不能读整个 Hash 加锁,以是苹果接纳了分离锁。
4.对象开释时,调用 clearDeallocating 函数根据对象所在获取全部 weak 指针所在的数组,然后遍历这个数组把此中的数据设为 nil,末了把这个 entry 从 weak 表中删除,末了整理对象的记载。
10.深浅拷贝明确

1.简朴明确就是:浅拷贝是拷贝了指向对象的指针, 深拷贝不光拷贝了对象的指针还在体系中再分配一块内存,存放拷贝对象的内容。
2.浅拷贝:拷贝对象自己,返回一个对象,指向类似的内存所在。 深拷贝:拷贝对象自己,返回一个对象,指向差别的内存所在。
3.浅拷贝与深拷贝的本质区别:在于是否在堆内存中开辟新的内存空间。浅拷贝并不是拷贝对象自己,而是对指向对象的指针举行拷贝,但照旧指向同一块堆内存中指针指向的对象。深拷贝直接拷贝对象到内存中的一块地区,然后把新对象的指针指向这块内存。
24.png 25.png

  • 关于 Copy 的利用
1.在 oc 中 copy 是利用一个源对象产生一个副本对象,本质就是修改源对象的属性和活动,不会影响副本对象,同样修改副本对象的属性和活动,不会影响源对象。
2.怎样利用 copy:一个对象可以调用 copy 或 mutableCopy 方法来创建一个副本对象;copy 创建的是不可变副本(NSString、NSArray、NSDictionary…);mutableCopy 创建的是可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary…)。
3.copy利用的条件:必须按 NSCopying 协议实现 copyWithZone: 方法
@protocol NSCopying- (id)copyWithZoneNSZone *)zone;@end4.mutableCopy的利用条件:必须按 NSMutableCopying 协议实现 mutableCopyWithZone: 方法
@protocol NSMutableCopying- (id)mutableCopyWithZoneNSZone *)zone;@end


  • 举例分析深浅拷贝
先来形象表明一波:
表明1:
浅拷贝即如果拷贝一个对象,而不拷贝它的子对象;
深拷贝:拷贝一个对象时连同子对象一起拷贝
表明2:
浅拷贝:向retain一样,创建一个指针指向了同一块内存空间(即同一个对象,不产生新对像),使该内存空间引用计数加1
深拷贝:向copy一样,复制一个对象,产生了一个新的对象;新对象的引用计数为1,旧对象的引用计数稳固
增补 - 深拷贝用归档息争归档可以实现:
例子:人---关联一个车---车再关联一个引擎
应用场景:用初始化alloc init 创建对象,速率慢,内存...;以是思量用复制对象方式来创建
1.浅复制(浅拷贝,指针拷贝,shallow copy)

  • 源对象和副本对象是同一个对象
  • 源对象(副本对象)引用计数器+1,相称于做一次retain利用
  • 本质:没有产生新的对象
NSString *srcStr = @"1gcode";NSString *copyStr = [srcStr copy];NSLog(@"srcStr = %p, copyStr = %p", srcStr, copyStr);2.深复制(深拷贝,内容拷贝,deep copy)

  • 源对象和副本对象是差别的两个对象
  • 源对象引用计数器稳固,副本对象计数器为1(由于是新产生的)
  • 本质:产生了新的对象
NSString *srcStr = @"1gcode";NSMutableString *copyStr = [srcStr mutableCopy];NSLog(@"src = %p, copy = %p", srcStr, copyStr);NSLog(@"src = %@, copy = %@", srcStr, copyStr);[copyStr appendString" cool"];NSLog(@"src = %@, copy = %@", srcStr, copyStr);NSMutableString *srcStr = [NSMutableString stringWithFormat"1gcode"];NSString *copyStr = [srcStr copy];[srcStr appendString".com"];NSLog(@"src = %p, copy = %p", srcStr, copyStr);NSLog(@"src = %@, copy = %@", srcStr, copyStr);NSMutableString *srcStr = [NSMutableString stringWithFormat"1gcode"];NSMutableString *copyStr = [srcStr mutableCopy];[srcStr appendString".com"];[copyStr appendString" good"];NSLog(@"src = %p, copy = %p", srcStr, copyStr);NSLog(@"src = %@, copy = %@", srcStr, copyStr);


  • copy与内存管理
1.如果是浅拷贝,不会天生新的对象,但是体系就会对原来的对象举行retain,以是我们要对原来的对象举行一次
2.如果是深拷贝,会天生新的对象,体系不会对原来的对象举行retain,但是由于天生了新的对象,以是我们要对新的对象举行release
对于浅拷贝:原对象引用计数器+1,必须开释原对象
NSString *srcStr = [[NSString alloc] initWithFormat"www.1gcode.com"];NSLog(@"srcStr = %lu", [srcStr retainCount]);NSString *copyStr = [srcStr copy];NSLog(@"copyStr = %lu", [copyStr retainCount]);[copyStr release]; // 必须做一次release对于深拷贝(即深复制):新对象引用计数器+1,必须开释新对象
NSString *srcStr = [[NSString alloc] initWithFormat"www.1gcode.com"];NSLog(@"srcStr = %lu", [srcStr retainCount]);NSString *copyStr = [srcStr mutableCopy];NSLog(@"copyStr = %lu", [copyStr retainCount]);[copyStr release]; // 必须做一次release


  • 小结
1.浅拷贝是指针的拷贝,深拷贝是所在的拷贝
2.就是有可变的就是深拷贝;比如[NSString mutableCopy]这种就是深拷贝,[NSMutableString copy]也是深拷贝
3.可变就是要保存原有的对象,利用copy出来的对象,这两个对象的指针指向两个所在;这样做目标是包管原来的对象不被修改(如果你原来的对象都改了,你原来的对象原来要干的东西就干不了了)
4.浅拷贝不会拷贝所在只会拷贝指针,如果你要修改对象的内容,那你从前的谁人对象也被修改了
5.浅复制比如你和你的影子,你完蛋,你的影子也完蛋;深复制比如你和你的克隆人,你完蛋,你的克隆人还活着


  • 怎样判断浅拷贝、深拷贝?
深浅拷贝取决于拷贝后的对象的是不是和被拷贝对象的所在类似;
1.如果差别,则产生了新的对象,则实行的是深拷贝。
2.如果类似,则只是指针拷贝,相称于 retain 一次原对象,实行的是浅拷贝。
27.png

  • 如果 NSMutableArray 用 copy 修饰可以不可以?
当 copy 修饰可变范例集合(比方:NSMutableArray)时,赋值后,会导致可变范例属性变为不可变范例,然后在调用可变范例方法时,会产生非常错误。产生非常的原因是 copy 属性在运行时赋值时调用了 -copyWithZone: 赋值,将可变范例转换为不可变范例。
增补分析:
比如这个写法:@property (nonatomic, copy) NSMutableArray *array;  如果利用这个 array 会闪退,由于 copy 出来的是一个不可变的数组。比如调用的时间是添加元素,那么错误会是数组找不到添加元素的方法,以是会闪退。
31.png

  • block 变量界说时为什么用 copy?block 是放在那里的?
默认情况下,block 是存档在栈中,大概被随时接纳,通过 copy 利用可以使其在堆中保存一份, 相称于不停强引用着,因此如果 block 中用到 self 时须要将其弱化,通过 __weak 或者 __unsafe_unretained。


  • copy 与 @property
1.@property利用copy可以防止外界修改内部的数据
2.可以利用copy生存block, 这样可以制止在block中利用的外界对象的时间, 外界的对象已经开释出现的野指针错误
3.注意: copy block大概会引发循环引用,如果对象中的block又用到了对象自己, 那么为了制止内存走漏, 应该将对象修饰为__block


  • 增补:block 内存相干


  • __block 什么时间用?
    在 block 内里修改局部变量的值都要用 __block 修饰
  • 在 block 内里,对数组实行添加利用,这个数组须要声明成 __block 吗?
    不须要声明成 __block,由于 testArr 数组的指针并没有变。往数组内里添加对象,指针是没变的,只是指针指向的内存内里的内容变了。
  • 在block 内里对 NSInteger 举行修改,这个 NSInteger 是否须要声明成 __blcok?
    NSInteger 的值发生改变,则要求添加 __block 修饰。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2025-4-19 19:21, Processed in 0.143387 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表