大家好,我是面试聊iOS的程序员。
这篇文章将和大家分享面试iOS时聊内存管理一般都可以聊些什么。
抖音搜索 面试聊iOS 观看视频版
相关文章链接
面试聊iOS:内存管理
面试聊iOS:RunTime(一)
面试聊iOS:RunTime(二)
面试聊iOS:Block
面试聊iOS:多线程
引用计数
OC类中实现了引用计数器,对象知道自己当前被引用的次数。
对象初始化时计数器为1,每次操作对象都会引起相应的计数器变化;
retain+1,release-1;
当引用计数为0时,给对象发送dealloc消息销毁对象。
黄金法则
凡是通过alloc、init、copy、mutableCopy、retain进行创建的对象,都要使用release或autorelease进行释放。
- 自己生成的对象,自己持有。
- 不是自己生成的对象,自己也能持有。
- 不再需要持有对象时释放。
- 非自己持有的对象无需释放。
引用计数存放
从64bit开始,对象的引用计数存放在优化过的isa指针中,也可能存放在SideTable中。当优化过的isa指针中,引用计数过大存放不下时,就会将引用计数存放到SideTable中。
SideTables其实是一个哈希表,Key为对象指针,Value为对象内容具体存放的SideTable。
SideTable包含自旋锁,引用计数表,弱引用表,由Runtime维护。
为什么是SideTables?
因为查找或修改引用计数时是要加锁的,使用SideTables方便多个对象同时做操作。
MRC
iOS5之前,需要开发者手动去管理内存。
需要引用对象时,发送retain消息,对象的引用计数+1;
不需要引用对象时,发送release消息,对象引用计数-1;
当引用计数为0时,自动调用对象的dealloc方法销毁对象,释放内存;
引用计数为0的对象,不能再使用release和其他方法。
ARC
ARC也是基于引用计数,只是编译器在编译时期自动在已有代码中插入合适的内存管理代码(包括retain、release、copy、autorelease、autoreleasepool)以及在运行时做了一些优化。
简单来说,就是代码中自动加入了retain、release,原先MRC中需要手动添加的用来管理引用计数的代码都由编译器帮我们完成了。
strong
强引用,指向一个对象时,对象引用计数+1;当对象没有一个强引用指向它时,它才会被释放。
如果在声明引用时不加修饰符,默认为strong。
weak
弱引用,不会引起对象的引用计数变化;当对象被释放时,所有指向它的弱引用指针会自动被置为nil,防止野指针。
给nil发送消息时,会直接renturn,不会调用方法,也不会crash。
weak原理
对象的SideTable中有一个weak表,以对象的内存地址为key,value则是所有指向该对象的弱引用指针的数组。
当对象销毁时,通过对象内存地址找到所有指向它的弱引用指针,置为nil并删除。
assign
assign指针纯粹指向对象,不会引起对象的引用计数发生变化。
当对象被释放时,指针依然指向对象原来的内存地址,不会自动被置为nil,容易造成野指针。
所以一般assign都用来修饰基本数据类型如int、float、struct等值类型。
值类型会被放入栈中,遵循先进后出的原则,由系统负责管理栈内存。
引用类型会被放入堆中,需要我们自己手动管理内存(MRC)或通过ARC管理。
深copy与浅copy
深拷贝:copy出来的对象与源对象地址不一致,开辟新的内存空间存放拷贝对象,对拷贝对象做修改不会影响源对象。
浅拷贝:copy出来的对象与源对象地址一致,对拷贝对象做修改会影响源对象。
对于不可变类型的对象,copy为浅拷贝,对象引用计数+1。
对于可变类型的对象,copy为深拷贝,拷贝对象也变为不可变类型。
对对象做mutableCopy操作,都为深拷贝,拷贝对象也会变为可变类型。
对于容器类型(NSArray、NSDictionary等),深拷贝也仅是拷贝容器本身,对容器里面的元素只做浅拷贝。
声明NSString类型的属性,用copy还是strong修饰更好?
考虑多态的原因,NSString类型的属性,最终可能指向的是NSMutableString,为了防止源字符串的修改引起变化,最好是采用copy来修饰。
如何自定义cpoy操作?
遵循copy协议<NSCopying, NSMutableCopying>,
重写copyWithZone、mutableCopyWithZone方法。
atomic 与nonatomic
对atomic修饰的属性的setter、getter方法添加了原子锁,保证set、get操作的完整性。
也就是下一次的set、get操作必须等到上一次的set、get操作完成之后才能执行。
因为atomic添加了原子锁,会增加开销,运行速度更慢,在不需要保证set、get操作的完整的情况下,所以一般都使用nonatomic。
atomic不能完全保证线程安全
只能保证set、get操作的完整性,当开启多个线程执行多个set、get操作时,无法保证执行的顺序。
另外如数组除了set、get操作外还有remove的操作。
内存泄漏
内存泄漏:是指申请的内存空间用完之后未释放,在ARC下根本原因是循环引用(在ViewController中没有正确的使用NStimer、delegate、block)引起的。
内存溢出:通俗的讲就是内存不够用了,程序申请内存空间时,没有足够的内存空间可供使用
循环引用
两个对象之间相互强引用,引用计数都依赖于对方,导致对象无法释放。
最容易产生循环引用的两种情况就是delegate和block,所以才引入了弱引用。
持有对象,但不增加引用计数,这样就避免了循环引用的产生。