关于ios:iOS开发面试只需知道这些技术基本通关内存管理篇

40次阅读

共计 9197 个字符,预计需要花费 23 分钟才能阅读完成。

一、在  Obj- C 中,如何检测内存透露?你晓得哪些形式?

目前我晓得的形式有以下几种

· Memory Leaks

· Alloctions

· Analyse

· Debug Memory Graph

· MLeaksFinder

泄露的内存次要有以下两种:

· Laek  Memory这种是遗记   Release操作所泄露的内存。

· Abandon  Memory这种是循环援用,无奈开释掉的内存。

下面所说的五种形式,其实前四种都比拟麻烦,须要一直地调试运行,第五种是腾讯浏览团队出品,成果
好一些

二、在 MRC 下如何重写属性的 Setter 和 Getter_.md

setter

getter

重写dealloc

三、循环援用

循环援用的本质:多个对象相互之间有强援用,不能开释让零碎回收。
如何解决循环援用?

1、防止产生循环援用,通常是将 strong 援用改为 weak 援用。

比方在润饰属性时用 weak
block内调用对象办法时,应用其弱援用,这里能够应用两个宏

还能够应用 __block 来润饰变量
MRC下,__block不会减少其援用计数,防止了循环援用
ARC下,__block润饰对象会被强援用,无奈防止循环援用,须要手动解除。

2、在适合机会去手动断开循环援用。

通常咱们应用第一种。

(1)、代理 (delegate) 循环援用属于互相循环援用

delegate  iOS 中开发中比拟常遇到的循环援用,个别在申明  delegate的时候都要应用弱援用   weak, 或者assign, 当然怎么抉择应用 assign 还是 weakMRC的话只能用  assign,在 ARC 的状况下最好应用  weak,因为weak 润饰的变量在开释后主动指向  nil,避免野指针存在

(2)、NSTimer 循环援用属于互相循环应用

在控制器内,创立 NSTimer 作为其属性,因为定时器创立后也会强援用该控制器对象,那么该对象和定时
器就互相循环援用了。

如何解决呢?

这里咱们能够应用手动断开循环援用:
如果是不反复定时器,在回调办法里将定时器 invalidate并置为 nil 即可。
如果是反复定时器,在适合的地位将其 invalidate并置为 nil即可

(3)、block 循环援用

一个简略的例子:

因为 block 会对 block 中的对象进行持有操作, 就相当于持有了其中的对象,而如果此时 block 中的对象又
持有了该 block,则会造成循环援用。
解决方案就是应用 __weak 润饰 self 即可

并不是所有 block 都会造成循环援用。
只有被强援用了的 block 才会产生循环援用
而比方 `dispatch_async(dispatch_get_main_queue(),^{}),[UIViewanimateWithDuration:1
animations:^{}]` 这些零碎办法等
或者 block 并不是其属性而是长期变量, 即栈block

还有一种场景,在 block 执行开始时 self 对象还未被开释,而执行过程中,self被开释了,因为是用 weak 润饰的,那么 weakSelf 也被开释了,此时在 block 里拜访 weakSelf 时,就可能会产生谬误 (向nil 对象发消息并不会解体,但也没任何成果)。

对于这种场景,应该在 block 中对对象应用__strong 润饰,使得在 block 期间对对象持有,block 执行完结后,解除其持有。

四、说一下什么是悬垂指针?什么是野指针?

首先作为一个开发者,有一个学习的气氛跟一个交换圈子特地重要,这是一个我的 iOS 开发公众号:编程大鑫,不论你是小白还是大牛都欢送入驻,让咱们一起提高,独特倒退!(群内会收费提供一些群主珍藏的收费学习书籍材料以及整顿好的几百道面试题和答案文档!)

悬垂指针
指针指向的内存曾经被开释了,然而指针还存在,这就是一个悬垂指针或者说迷途指针

野指针
没有进行初始化的指针,其实都是野指针

五、说一下对 Strong,Weak,assign,copy,__unsafe_unretain,__autoreleasing 关键字的了解

Strong

Strong修饰符示意指向并持有该对象,其润饰对象的援用计数会加 1。该对象只有援用计数不为 0 就不会被销毁。当然能够通过将变量强制赋值 nil 来进行销毁。

Weak

weak修饰符指向然而并不持有该对象,援用计数也不会加 1。在 Runtime 中对该属性进行了相干操作,无需解决,能够主动销毁。weak 用来润饰对象,多用于防止循环援用的中央。weak 不能够润饰根本数据类型。

assign

assign次要用于润饰根本数据类型,

例如 NSIntegerCGFloat,存储在栈中,内存不必程序员治理。assign 是能够润饰对象的,然而会呈现问题。

copy

copy关键字和 strong 相似,copy多用于润饰有可变类型的不可变对象 NSString,NSArray,NSDictionary 上。

__unsafe_unretain

__unsafe_unretain相似于weak,然而当对象被开释后,指针未然保留着之前的地址,被开释后的地址变为僵尸对象,拜访被开释的地址就会出问题,所以说他是不平安的。

__autoreleasing

将对象赋值给附有 __autoreleasing 润饰的变量等同于 ARC 有效时调用对象的 autorelease 办法, 本质就是扔进了主动开释池。

六、是否理解深拷贝和浅拷贝的概念,汇合类深拷贝如何实现

简而言之:

1、对不可变的非汇合对象,copy是指针拷贝,mutablecopy是内容拷贝

2、对于可变的非汇合对象,copymutablecopy都是内容拷贝

3、对不可变的数组、字典、汇合等汇合类对象,copy是指针拷贝,mutablecopy是内容拷贝

4、对于可变的数组、字典、汇合等汇合类对象,copymutablecopy都是内容拷贝

然而,对于汇合对象的内容复制仅仅是对对象自身,然而对象的外面的元素还是指针复制。要想复制整个汇合对象,就要用汇合深复制的办法,有两种:

(1)应用 initWithArray:copyItems: 办法,将第二个参数设置为 YES 即可

(2)将汇合对象进行归档(archive)而后解归档(unarchive):

七、应用主动援用计数应遵循的准则

1. 不能应用  retainreleaseretainCountautorelease

2. 不能够应用  NSAllocateObjectNSDeallocateObject

3. 必须恪守内存治理办法的命名规定。

4. 不须要显示的调用  Dealloc

5. 应用  @autoreleasePool来代替   NSAutoreleasePool

6. 不能够应用区域  NSZone

7. 对象性变量不能够作为  C 语言的构造体成员。

8. 显示转换  id 和   void*

八、能不能简述一下  Dealloc 的实现机制

Dealloc的实现机制是内容治理局部的重点,把这个知识点弄明确,对于全方位的了解内存治理的只是很有必要。

1.Dealloc调用流程

(1). 首先调用  _objc_rootDealloc()

(2). 接下来调用  rootDealloc()

(3). 这时候会判断是否能够被开释,判断的根据次要有 5 个,判断是否有以上五种状况

NONPointer_ISA

weakly_reference

has_assoc

has_cxx_dtor

has_sidetable_rc

如果有以上五中任意一种,将会调用  object_dispose()办法,做下一步的解决。

如果没有之前五种状况的任意一种,则能够执行开释操作,C函数的  free()。

执行结束。

2.object_dispose()调用流程。

(1). 间接调用  objc_destructInstance()。

(2). 之后调用  C函数的  free()。

3.objc_destructInstance()调用流程

(1). 先判断  hasCxxDtor,如果有 C++的相干内容,要调用    object_cxxDestruct(),销毁   C++相干的内容。

(2). 再判断  hasAssocitatedObjects,如果有的话,要调用 object_remove_associations(),销毁关联对象的一系列操作。

(3). 而后调用  clearDeallocating()。

(4). 执行结束。

4.clearDeallocating()调用流程。

(1). 先执行  sideTable_clearDellocating()。

(2). 再执行  weak_clear_no_lock, 在这一步骤中,会将指向该对象的弱援用指针置为 nil

(3). 接下来执行  table.refcnts.eraser(),从援用计数表中擦除该对象的援用计数。

(4). 至此为止,Dealloc的执行流程完结。

九、内存中的 5 大区别离是什么?

栈区(stack):由编译器主动调配开释,寄存函数的参数值,局部变量的值等。其操作形式相似于数据结构中的栈。

堆区(heap):个别由程序员调配开释,若程序员不开释,程序完结时可能由   OS 回收。留神它与数据结构中的堆是两回事,调配形式倒是相似于链表。

全局区(动态区)(static):全局变量和动态变量的存储是放在一块的,初始化的全局变量和动态变量在一块区域,未初始化的全局变量和未初始化的动态变量在相邻的另一块区域。- 程序完结后由零碎开释。

文字常量区:常量字符串就是放在这里的。程序完结后由零碎开释。

程序代码区:寄存函数体的二进制代码。

十、内存治理默认的关键字是什么?

MRC

ARC

如果改为根本数据类型,那就是assign

十一、内存治理计划

taggedPointer:存储小对象如  NSNumber。深刻了解 Tagged Pointer

NONPOINTER_ISA(非指针型的 isa): 在 64 位架构下,isa 指针是占   64 比特位的,实际上只有  30 多位就
曾经够用了,为了进步利用率,残余的比特位存储了内存治理的相干数据内容。

散列表:简单的数据结构,包含了援用计数表和弱援用表,通过 SideTables() 构造来实现的,SideTables()构造下,有很多 SideTable 的数据结构。而 sideTable当中蕴含了自旋锁,援用计数表,弱援用表。
SideTables()实际上是一个哈希表,通过对象的地址来计算该对象的援用计数在哪个 sideTable中。

自旋锁:

自旋锁是“忙等”的锁。
实用于轻量拜访。
援用计数表和弱援用表理论是一个哈希表,来进步查找效率。

十二、内存布局

栈(stack): 办法调用,局部变量等,是间断的,高地址往低地址扩大

堆 (heap): 通过 alloc 等调配的对象,是离散的,低地址往高地址扩大,须要咱们手动管制

未初始化数据(bss): 未初始化的全局变量等

已初始化数据(data): 已初始化的全局变量等

代码段(text): 程序代码

64bit 和 32bit 下 long 和 char 所占字节是不同的

char:1 字节(ASCII2=256 个字符)

char*(即指针变量):4 个字节(32 位的寻址空间是 2, 即 32 个 bit,也就是 4 个字节。同理 64 位编译器为 8 个字节)

shortint:2 个字节范畴 -2~>2 即 -32768~>32767

int:4 个字节范畴 -2147483648~>2147483647

unsignedint:4 个字节

long:4 个字节范畴和 int 一样 64 位下 8 个字节,范畴 -9223372036854775808~9223372036854775807

longlong:8 个字节范畴 -9223372036854775808~9223372036854775807

unsignedlonglong:8 个字节最大值:1844674407370955161

float:4 个字节

double:8 个字节

static、const 和 sizeof 关键字

static关键字

答:Static的用处次要有两个,一是用于润饰存储类型使之成为动态存储类型,二是用于润饰链接属性使
之成为外部链接属性。

(1)、动态存储类型:

在函数内定义的动态局部变量,该变量存在内存的动态区,所以即便该函数运行完结,动态变量的值不会
被销毁,函数下次运行时能仍用到这个值。

在函数外定义的动态变量——动态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其余文
件通过 extern援用。

(2)、外部链接属性

动态函数只能在申明它的源文件中应用。

const关键字

1、申明常变量,使得指定的变量不能被批改。

2、润饰函数形参,使得形参在函数内不能被批改,示意输出参数。

3、润饰函数返回值,使得函数的返回值不能被批改。

sizeof关键字

sizeof是在编译阶段解决,且不能被编译为机器码。sizeof的后果等于对象或类型所占的内存字节数。
sizeof的返回值类型为size_t

变量:inta;sizeof(a)为 4;

指针:int*p;sizeof(p)为 4;

数组:intb[10];sizeof(b)为数组的大小,4*10;intc[0];sizeof(c)等于 0

构造体:struct(inta;charch;)s1;sizeof(s1)为 8 与构造体字节对齐无关。

对构造体求 sizeof 时,有两个准则:

留神:不能对构造体中的位域成员应用sizeof

sizeof(void)等于 1

sizeof(void*)等于 4

十三、讲一下  iOS 内存治理的了解

实际上是三种计划的联合

1.TaggedPointer(针对相似于 NSNumber 的小对象类型)

2.NONPOINTER_ISA(64 位零碎下)

* 第一位的  0 或   1 代表是纯地址型   isa指针,还是    NONPOINTER_ISA指针。

* 第二位,代表是否有关联对象

* 第三位代表是否有  C++代码。

* 接下来 33 位代表指向的内存地址

* 接下来有弱援用的标记

* 接下来有是否  delloc的标记 …. 等等

3. 散列表(援用计数表、weak表)

*SideTables表在非嵌入式的    64 位零碎中,有   64 张  SideTable

* 每一张  SideTable次要是由三局部组成。自旋锁、援用计数表、弱援用表。

* 全局的援用计数之所以不存在同一张表中,是为了防止资源竞争,解决效率的问题。

* 援用计数表中引入了拆散锁的概念,将一张表分拆成多个局部,对他们别离加锁,能够实现并发操
作,晋升执行效率

十四、讲一下  @dynamic 关键字?

@dynamic意味着编译器不会帮忙咱们自动合成 settergetter办法。咱们须要手动实现、这里就波及到 Runtime 的动静增加办法的知识点。

十五、简要说一下  @autoreleasePool 的数据结构?

简略说是双向链表,每张链表头尾相接,有  parentchild指针
每创立一个池子,会在首部创立一个哨兵对象, 作为标记
最外层池子的顶端会有一个 next 指针。当链表容量满了,就会在链表的顶端,并指向下一张表。

十六、拜访  __weak 润饰的变量,是否曾经被注册在了   @autoreleasePool 中?为什么?

答案是必定的,__weak润饰的变量属于弱援用,如果没有被注册到 @autoreleasePool 中,创立之后也就
会随之销毁,为了缩短它的生命周期,必须注册到 @autoreleasePool 中,以延缓开释。

十七、retain、release 的实现机制?

1.Retain的实现机制。

2.Release的实现机制。

二者的实现机制相似,概括讲就是通过第一层 hash 算法,找到指针变量所对应的 sideTable。而后再通过一层hash 算法,找到存储援用计数的 size_t,而后对其进行增减操作。retainCount 不是固定的 1,SIZE_TABLE_RC_ONE是一个宏定义,实际上是一个值为 4 的偏移量。

十八、MRC(手动援用计数)和 ARC(主动援用计数)

1、MRCallocretainreleaseretainCount,autorelease,dealloc

2、ARC

*ARC LLVMRuntime合作的后果

*ARC禁止手动调用  retainreleaseretainCount,autorelease关键字

*ARC新增  weakstrong关键字

3、援用计数治理:

alloc: 通过一系列函数调用,最终调用了  calloc函数,这里并没有设置援用计数为   1

retain: 通过两次哈希查找,找到其对应援用计数值,而后将援用计数加  1(理论是加偏移量)

release:和 retain相同,通过两次哈希查找,找到其对应援用计数值,而后将援用计数减  1

4、弱援用治理:

* 增加 weak 变量:
通过哈希算法地位查找增加。如果查找对应地位中曾经有了以后对象所对应的弱援用数组,就把新的弱援用变量增加到数组当中;如果没有,就创立一个弱援用数组,并将该弱援用变量增加到该数组中。

* 当一个被 weak 润饰的对象被开释后,weak 对象怎么解决的?
革除 weak 变量,同时设置指向为 nil。当对象被 dealloc开释后,在  dealloc的外部实现中,会调用弱援用革除的相干函数,会依据以后对象指针查找弱援用表,找到以后对象所对应的弱援用数组,将数组中的所有弱援用指针都置为 nil

5、主动开释池:

在当次 runloop将要完结的时候调用 objc_autoreleasePoolPop,并 push进来一个新的   AutoreleasePool

AutoreleasePoolPage是以栈为结点通过双向链表的模式组合而成,是和线程一一对应的。
外部属性有 parentchild 对应前后两个结点,thread对应线程,next指针指向栈中下一个可填充的地位。

*AutoreleasePool实现原理?

编译器会将 @autoreleasepool{} 改写为:

*objc_autoreleasePoolPush
把以后 next 地位置为 nil,即哨兵对象, 而后next 指针指向下一个可入栈地位,AutoreleasePool的多层嵌套,即每次objc_autoreleasePoolPush,实际上是一直地向栈中插入哨兵对象。

*objc_autoreleasePoolPop:

依据传入的哨兵对象找到对应地位。给上次 push 操作之后增加的对象顺次发送 release 音讯。
回退 next 指针到正确的地位。

十九、BAD_ACCESS 在什么状况下呈现?

拜访了曾经被销毁的内存空间,就会报出这个谬误。
根本原因是有悬垂指针没有被开释。

二十、autoReleasePool 什么时候开释?

App启动后,苹果在主线程 RunLoop 里注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。

第一个 Observer 监督的事件是  Entry(行将进入 Loop),其回调内会调用_objc_autoreleasePoolPush() 创立主动开释池。其 order 是 -2147483647,优先级最高,保障创立开释池产生在其余所有回调之前。

第二个 Observer 监督了两个事件:BeforeWaiting(筹备进入休眠)时调用 _objc_autoreleasePoolPop() 和_objc_autoreleasePoolPush()开释旧的池并创立新池;Exit(行将退出 Loop) 时调 _objc_autoreleasePoolPop() 来开释主动开释池。这个 Observerorder是 2147483647,优先级最低,保障其开释池子产生在其余所有回调之后。

二十一、ARC 主动内存治理的准则

* 本人生成的对象,本人持有

* 非本人生成的对象,本人能够持有

* 本人持有的对象不再须要时,须要对其进行开释

* 非本人持有的对象无奈开释

二十二、ARC 在运行时做了哪些工作?

* 次要是指  weak关键字。weak润饰的变量可能在援用计数为    0 时被主动设置成nil,显然是有运行时逻辑在工作的。

* 为了保障向后兼容性,ARC在运行时检测到类函数中的   autorelease后紧跟其后    retain,此时不间接调用对象的  autorelease办法,而是改为调用   objc_autoreleaseReturnValueobjc_autoreleaseReturnValue会检视以后办法返回之后行将要执行的那段代码,若那段代码要在返回对象上执行  retain 操作,则设置全局数据结构中的一个标记位,而不执行   autorelease操作,与之类似,如果办法返回了一个主动开释的对象,而调用办法的代码要保留此对象,那么此时不间接执行  retain,而是改为执行    objc_retainAoutoreleasedReturnValue函数。此函数要检测方才提到的标记位,若曾经置位,则不执行  retain 操作,设置并检测标记位,要比调用 autorelease retain更快。

二十三、ARC 在编译时做了哪些工作

依据代码执行的上下文语境,在适当的地位插入retainrelease

二十四、ARC 的   retainCount 怎么存储的?

存在 64 张哈希表中,依据哈希算法去查找所在的地位,无需遍历,非常快捷

散列表(援用计数表、weak表)

SideTables表在非嵌入式的 64 位零碎中,有 64 张 SideTable

- 每一张 SideTable 次要是由三局部组成。自旋锁、援用计数表、弱援用表。

- 全局的援用计数之所以不存在同一张表中,是为了防止资源竞争,解决效率的问题。

- 援用计数表中引入了拆散锁的概念,将一张表分拆成多个局部,对他们别离加锁,能够实现并发操作,
晋升执行效率

援用计数表(哈希表)

通过指针的地址,查找到援用计数的地址,大大晋升查找效率

通过  DisguisedPtr(objc_object)函数存储,同时也通过这个函数查找,这样就防止了循环遍历。

二十五、__weak 属性润饰的变量,如何实现在变量没有强援用后主动置为    nil?

用的弱援用  - weak表。也是一张哈希表。

被  weak润饰的指针变量所指向的地址是   key,所有指向这块内存地址的指针会被增加在一个数组里,
这个数组是  Value。当内存地址销毁,数组里的所有对象被置为 nil

二十六、__weak 和   _Unsafe_Unretain 的区别?

weak润饰的指针变量,在指向的内存地址销毁后,会在   Runtime的机制下,主动置为    nil_Unsafe_Unretain不会置为  nil,容易呈现悬垂指针,产生解体。然而    _Unsafe_Unretain__weak 效率高。

正文完
 0