iOS内存存储区分为栈、堆、全局和静态变量存储区、常量存储区、代码区。 代码区->常量存储区->全局和静态变量存储区->堆->栈在内存中的地址从低往高。 栈的生长方向为向低地址生长,堆的生长方向为向高地址生长。 栈和堆在程序运行时会动态增长,其他区在编译期确定
用来存放局部变量、函数参数等。底层实现数据结构为栈,后申请的变量先释放。
程序通过malloc,new创建出来的对象存放在堆中。
全局,静态变量存储在该区。该区在编译期就确定了。
常量存储在该区。该区在编译期就确定了。
二进制代码存储在该区。该区在编译器就确定了。
对象操作 | OC中对应的方法 | 对应的 retainCount 变化| |
---|---|---|
生成并持有对象 | alloc/new/copy/mutableCopy等 | +1 |
持有对象 | retain | +1 |
释放对象 | release | -1 |
废弃对象 | dealloc | 0 |
一、规则 1、四个法则
- 自己生成的对象,自己持有。
- 非自己生成的对象,自己也能持有。
- 不在需要自己持有对象的时候,释放。
- 非自己持有的对象无需释放。
2、任何以下列名称为前缀的方法,若其返回值为 object,则方法调用者持有该 object:
- alloc
- new
- copy
- mutableCopy
3、还有一个更为严格的规则:任何以 init 为前缀的方法必须遵守下列规则:
- 该方法必须是实例方法;
- 该方法必须返回类型为id或其所属class、superclass、subclass 的对象;
- 该方法返回的 object 不能是 autorelese,即方法调用者持有返回的 object。
4、『方法调用者持有该 object』也就意味着该 object 的内存问题需要调用方管理。 在此之外的任何方法返回的 object,其调用方都不持有,即返回的应该是 autorelease object。
ARC 是苹果引入的一种自动内存管理机制,会根据引用计数自动监视对象的生存周期,实现方式是在编译时期自动在已有代码中插入合适的内存管理代码以及在 Runtime 做一些优化。
有两种实现方式 1、在对象内部保持引用计数。 2、在全局表里保持引用计数,key为对象,value为对象的引用计数。 iOS采用的第二种方式,在调用retain方法的时候,在全局表里面查找对象并增加引用计数;调用release方法的时候,在全局表里面查找对象并减少引用计数,如果引用计数为0,释放掉对象,并且从表里面移除该项数据。
iOS在底层用了一个全局表来实现weak,表的key为引用对象,value为一个数组,代表了所有weak指针。对用weak指针引用对象时,如果不存在那就往weak指针数组里面插入该weak指针。 当weak指针销毁时候,会从weak指针数组里面移除该weak指针。 当引用对象销毁的时候,会自动地把weak指针数组置为nil。 weak跟assign、__unsafe_unretained区别在于引用对象销毁时,weak会自动置为nil,其他的不会,因此用weak不会造成野指针,因此不会崩溃。 assign与__unsafe_unretained差不多,最好不要用assign修饰对象。
如果对一个对象调用Autorelease,该对象会加入到自动释放池,当自动释放池释放的时候,会对该对象调用release。
_bridge直接转换,不进行任何内存转移操作
__bridge_retained 的作用是使得被赋值变量持有赋值 object。
ARC如下代码:
id obj = [[NSObjcet alloc] init];
void* p = (__bridge_retained void*)obj;
相当于MRC:
id obj = [[NSObjcet alloc] init];
void* p = (void*)obj;
[p retain];
__bridge_transfer,转移控制权,它的作用是使得赋值 object 在赋值后被 release。
ARC如下代码:
id obj = [[NSObjcet alloc] init];
void* p = (__bridge_transfer void*)obj;
相当于MRC:
id obj = [[NSObjcet alloc] init];
void* p = (void*)obj;
[p retain];
[obj release];
`
2013年9月,苹果推出了iPhone5s,与此同时,iPhone5s配备了首个采用64位架构的A7双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer 的概念。对于64位程序,引入Tagged Pointer后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升。 Tagged Pointer通过在其最后一个bit位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其isa变量。 具体可以参考深入理解Tagged Pointer
浅拷贝不会生成新对象,只是引用拷贝对象,指向的是同一块内存,两个对象任何一个发生变化都是互相影响。深拷贝新生成一个对象,把原来对象的内容复制过来了,两个对象毫不相关了。
- [NSArray copy] 浅copy
- [NSArray mutableCopy] 深copy
- [NSMutableArray copy] 深copy
- [NSMutableArray mutableCopy] 深copy
- 数组会对保存的对象内存引用计数+1。
- 数组保存的是对象的指针对象。
- 如果数组copy时,保存的对象也想同时copy,可以用initWithArray:copyItems:函数。
- 用copy修饰NSMutableArray,可能引发崩溃,因为执行copy后,数组变成了不可变数组。
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
self.copyedMutableArr = arr;
[self.copyedMutableArr removeObject:@1]; // 崩溃,错位信息为:Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x600000c1a190'。因为copyedMutableArr为不可变对象
参考资料:[copy修饰NSArray strong修饰NSMutableArray]https://www.jianshu.com/p/2008e585c1a0
实现NSCopying和NSMutableCopying协议来进行copy和mutableCopy
NSArray和NSDictionary是线程安全的。 NSMutableArray和NSMutableDictionary是线程不安全的。 线程安全总结
参考资料: 深入理解 iOS 内存管理
如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群931542608来获取一份详细的大厂面试资料为你的跳槽多添一份保障。