简述一下OC的内存管理方式
包括alloc,retain,copy,release,autorelease,
delalloc这些方法理解
OC中每个对象都有一个与之对应的整数,叫“引用计数器”,
当一个对象在创建之后它的引用计数器值加1,当调用这个对象的
alloc、retain、new、copy方法之后引用计数器值自动在原
来的基础上加1,当调用这个对象的release方法之后它的引用计
数器值减1,如果一个对象的引用计数器值为0,则系统会自动
调用这个对象的dealloc方法来销毁这个对象。
在alloc的时候调用了allocWithZone分配了内存
alloc、retain、new、copy方法之后引用计数器值自
动在原来的基础上加1
用MRC写一个setter方法
- (void)setName:(NSString *)name {
if(_name != name) {
[name retain];
[_name relrase];
_name = name;
}
}
描述autorelease对象释放时机,
在没有手加Autorelease Pool的情况下,Autorelease对象
是在当前的runloop迭代结束时释放的,而它能够释放的原因是
系统在每个runloop迭代中都加入了自动释放池Push和Pop
简述autorelrasePool的工作机制
每一个线程创建的时候就会有一个autorelease pool的创建,
并且在线程退出的时候,清空整个autorelease pool。
(ps:如果在子线程中设置一个循环,autorelease对象确实无
法释放)所以子线程的autorelease对象,
要么在子线程中设置runloop清楚
RutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage *const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
AutoreleasePool并没有单独的结构,而是由若干个
AutoreleasePoolPage以双向链表的形式组合而成(
分别对应结构中的parent指针和child指针)
AutoreleasePool是按线程一一对应的
(结构中的thread指针指向当前线程)
AutoreleasePoolPage每个对象会开辟4096字节内存
(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,
剩下的空间全部用来储存autorelease对象的地址
上面的id *next指针作为游标指向栈顶最新add进来
的autorelease对象的下一个位置
一个AutoreleasePoolPage的空间被占满时,
会新建一个AutoreleasePoolPage对象,连接链表,
后来的autorelease对象在新的page加入
runtime 黑魔法之Thread Local Storage
Thread Local Storage(TLS)线程局部存储,
目的很简单,将一块内存作为某个线程专有的存储,
以key-value的形式进行读写,比如在非arm架构下,
使用pthread提供的方法实现:
在返回值身上调用objc_autoreleaseReturnValue方法时,
runtime将这个返回值object储存在TLS中,然后直接返回这
个object(不调用autorelease);同时,在外部接收这个返
回值的objc_retainAutoreleasedReturnValue里,发
现TLS中正好存了这个对象,那么直接返回这个object
(不调用retain)。
于是乎,调用方和被调方利用TLS做中转,
很有默契的免去了对返回值的内存管理。
于是问题又来了,假如被调方和主调方只有一边是ARC
环境编译的该咋办?(比如我们在ARC环境下用了非
ARC编译的第三方库,或者反之)
只能动用更高级的黑魔法。
黑魔法之__builtin_return_address
这个内建函数原型是char *__builtin_return_address
(int level),作用是得到函数的返回地址,参数表示层数,
如__builtin_return_address(0)表示当前函数体返回地址
,传1是调用这个函数的外层函数的返回值地址,以此类推。
如果一个函数返回前知道调用方是ARC还是非ARC,就有机会对
于不同情况做不同的处理
在一个类中retaion一个NSTimer类型的成员变量会有什么问题吗?请简述问题产生的根本原因
类有一个成员变量_timer,给_timer设置的target为这个类本、
身。这样类保留_timer,_timer又保留了这个类,就会出现循环
引用的问题,最后导致类无法正确释放。
解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这
个计时器时,就调用
[_timer invalidate];
_timer = nil;
assgin和weak区别
修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报
错-“Property with ‘weak’ attribute must be of
object type”。
assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,
MRC时代使用unsafe_unretained。当然,
unsafe_unretained也可能产生野指针,所以它名字
是"unsafe_”。
是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计
数器值为0),指针会自动被置nil,之后再向该对象发消息也不会
崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类
型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对
象发消息会崩溃。
总结
assign 适用于基本数据类型如int,float,struct等值类型,不
适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由
系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动
管理内存或通过ARC管理。
weak 适用于delegate和block等引用类型,不会导致野指针问
题,也不会循环引用,非常安全。
weak实现原理
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak
表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的
地址(这个地址的值是所指对象的地址)数组。
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak
指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数,
objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首
先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数
据设为nil,最后把这个entry从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,详细过程如下:
a. 从weak表中获取废弃对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil
c. 将weak表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录
为什么NSMutableArray必须用strong,而NSArray必须用copy?
NSArray:被strong修饰之后,由于只是强引用,所以副本对象数
组和源对象数组只是指向同一个内存区域,这样就会造成副本对象数
组会随着源对象数组的改变而改变,即便有时候你并不想让副本对象
跟着改变。测试打印结果显示,array的长度发生了变化,具有调用
mutableArray方法的能力。
NSMutableArray只能用strong修饰,不存在有copy修饰的情
况,写了就成NSArray了。
循环引用如何定位
FBRetainCycleDetector
其原理完全是基于 DFS 算法(深度优先搜索):把整个对象的之间
的引用情况当做图进行处理,查找其中的环,就找到了循环引用。
masnory的block会循环引用吗
masonry中设置布局的方法中的block对象并没有被View所引用,
而是直接在方法内部同步执行,执行完以后block将释放,其中捕捉
的外部变量的引用计数也将还原到之前。
对象a1,a2,a3没有dosomething方法,想让a1调的时候不崩溃
1.runtime动态添加方法
2.写category,使方法向前引用,在通过动态方法解析,不让丢出错误
3.用方法混淆
4.用kvo
数组addobject如何不强引用
1.使用NSPointerArray
2.使用代理中间类来代替