浅拷贝和深拷贝
拷贝的最终目的是产生一个副本,修改副本不会对原对象产生影响。故到底是深拷贝还是浅拷贝要根据这个规则来判断。
- [任意对象 copy] 产生一个不可变对象,如果原对象是不可变对象,那么就是浅拷贝,因为不可变对象本身就不能修改,所以没必要创新分配内存。原对象是可变对象,就是深拷贝。
- [任意对象 mutableCopy] 产生一个可变对象,不论原对象是否是可变对象,都是深拷贝,因为要产生修改不影响原对象的副本,必须重新分配内存。
定义属性时使用copy修饰词,目的是在赋值之后产生一个不可变对象,那么在修改外部变量的时候不影响内部,例如UI控件的text都是copy修饰,防止外部修改影响内部实现。
CADisplayLink 和 NSTimer使用问题
会对target 强引用,如果target也强引用他们,就发生循环引用问题 解决方法是使用NSProxy,是和NSObject同一级别的基类,但是没有实现和声明多余的方法,只有alloc等。 作用是只要方法列表里找不到方法,跳过动态方法解析,直接做转发。且NXProxy中弱引用target即可。
内存布局
Tagged Pointer
从64位开始引入,目的是优化NSNumber, NSDate, NSString等小对象的存储,提高内存使用率
原来NSNumber占用一个对象的空间,指针还指向堆地址的值,最终可能只是为了存一个3。 就浪费很大空间
在使用Tagged pointer技术后,NSNumber 3的地址0x327, Tag+Data 的形式存储,从地址就能读到真实值,不需要额外空间。当内存地址长度超过64位后,才使用动态分配内存的方式存储数据。
如果地址最低有效位是1, 则是Tagged pointer
注意 : 字符串想要使用tagged pointer, 初始化要用[NSString stringWithFormat:]
,如果是@”xx”创建的是常规对象存在常量区。
引用计数管理
新建OC对象引用计数默认是1,当调用alloc,new,copy,mutableCopy时对象引用计数会+1
引用计数存储:
在64位优化后的isa共用体结构中,引用计数=extra_rc + sidetable_rc.refcnts[self].count,不是每个对象都会使用sidetable_rc,要看isa的标志位has_sidetable_rc.
如果isa不是共用体结构,引用计数=sidetable_rc.refcnts[self].count
refcnts是一个散列表,使用对象的地址为key,value为引用计数。 获取引用计数、retain、release操作都是获取extra_rc的值+sidetable的值+1或-1. 如果是release则判断是否为0,为0就dealloc。
weak 指针原理
__weak: 弱引用对象,当对象销毁时指针置为空
__unsafe_unretain: 弱引用对象,当对象销毁时,指针不变,使用会发生坏内存访问
weak能在对象销毁置为空的原理:
当自动调用dealloc方法时,判断是否有弱引用和sidetable_rc
,(弱引用和超过计数器时都使用到了sidetable),有弱引用就将sidetable中自己对应的value取出来,得到一个__weak对象的地址集合(不确定是不是数组,因为用到C++的遍历器), 并遍历将其置为nil
自动释放池
主要通过AutoreleasePoolPage来管理,结构如下
在@autoreleasePool代码开始时,调用AutoreleasePoolPage
的Push
方法,将BOUNDARY
添加到Page
中,标记为开始,后续调用autorelease
的对象也会添加到该Page
中,在括号结束时,调用Pop
方法时传入一个POOL_BOUNDARY
的内存地址,会从最后一个入栈的对象开始发送release
消息,直到遇到这个POOL_BOUNDARY
对象释放时机
MRC: 对象创建时调用了autorelease方法
系统在主线程的Runloop中注册了2个Observer,1个监听kCFRunLoopEntry时间,调用objc_autoreleasePoolPush()。另一个监听kCFRunLoopBeforeWaiting事件,调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()。
监听kCFRunLoopBeforeExit事件调用objc_autoreleasePoolPop()
例如:viewdidload和viewwillappear 在一个loop中,即一次执行所有的block和sources,直到执行完毕loop睡眠,在睡眠之前会释放对象。
ARC: 在括号结束时,LLVM编译器自动给对象release一次,而不是在runloop中执行。