关于内存
App启动后会把程序拷贝到内存里,如下图所示,内存是一块自下而上,地址由低到高分布的区域
大致说下五大区:
- 栈
连续的内存区域。使用栈保护函数现场,包括函数里的局部变量的分配和释放,通过压栈和弹栈的方式保护函数现场,先进后出,由编译器自动分配释放,程序员不要管 - 堆
不连续的内存区域,由程序员自己分配释放([[xx alloc ] init]
),又叫优先队列,本质上是二叉树
为什么要二叉树:现实中,有很多一对多的情况要处理,尤其是面向对象的框架
- 全局区/静态区
程序结束由系统释放 - 常量
常量字符串存于此(由const
修饰)
常量与宏的区别:宏定义只在预处理器进行文本替换,不做任何的类型检查,大量使用还会增大二进制文件大小;常量则共享一块内存空间,就算项目中N处用到,也不会分配N块内存空间,可以被修改,在编译阶段会执行类型检查
- 代码区
存放二进制代码
MRC
一、引用计数(保留计数)
1.1 原理
iOS的内存管理主要是依赖引用计数,so,我们扯扯这个的工作原理:
- (void)fun_1{
id obj = [[NSObject alloc] init];
[self printCount:obj];//1
[obj retain];
[self printCount:obj];//2
[obj retain];
[self printCount:obj];//3
[obj release];
[self printCount:obj];//2
[obj release];
[self printCount:obj];//1
[obj release];
[self printCount:obj];//crash, 因为对象已经被释放,无法打印retaincount
}
- (void)printCount:(id)obj{
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
注意,开启MRC模式,可以在Build phase添加-fno-objc-arc
1.2 为什么要用retaincount
在面向对象的框架中,经常会出现对象直接传递和共享数据,那此时的数据何时释放?
假如对象 A 生成了一个对象 M,需要调用对象 B 的某一个方法,将对象 M 作为参数传递过去。在没有引用计数的情况下,一般内存管理的原则是 “谁申请谁释放”,那么对象 A 就需要在对象 B 不再需要对象 M 的时候,将对象 M 销毁。但对象 B 可能只是临时用一下对象 M,也可能觉得对象 M 很重要,将它设置成自己的一个成员变量,那这种情况下,什么时候销毁对象 M 就成了一个难题。
1.3 四大原则
自己生成的对象,自己持有。
非自己生成的对象,自己也能持有。
不在需要自己持有的对象的时候,释放。
非自己持有的对象无法释放。
ARC(自动的引用计数)
ARC是苹果引入的一中自动管理内存的机制,背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码,让程序员放飞自我
一、修饰变量
__strong
__weak
__unsafe_unretained
__autoreleasing
1.1 __strong
__strong
是默认的变量修饰符。只要还有一个强引用指向对象,该对象就一直存在。会使retaincount+1
NSObject * a = [[NSObject alloc] init];
[self printCount:a];//2
注意,这里retaincount是2不是1,跟MRC是不一样的,具体原因是因为这里等价于NSObject * __strong a = [[NSObject alloc] init];
,而__strong会使retaincount+1
# 强引用对象指向强引用对象
__strong NSObject *obj1=[NSObject new];
__strong NSObject *obj2 = obj1;
obj1=nil;
NSLog(@"%@,%@",obj1,obj2);
//(null),<NSObject: 0x60000001f6c0>
1.2 __weak
__weak
表示其存亡不决定所指对象的存亡,如果没有强引用指向了,就被置为nil
# 强引用对象指向弱引用对象
__strong NSObject *obj1=[NSObject new];
__weak NSObject *obj2 = obj1;
obj1=nil;
NSLog(@"%@,%@",obj1,obj2);
//(null),(null)
1.3 __unsafe_unretained
表示其存亡不决定所指对象的存亡,如果没有强引用指向了,不会被置为nil。如果它引用的对象被回收掉了,该指针就变成了野指针
1.4 __autoreleasing
用于标示使用引用传值的参数(id *),在函数返回时会被自动释放掉。
二、修饰属性
assign
weak
strong
retain
copy
unsafe_unretained
2.1 retain, strong其实一个意思
2.2 copy, strong区别
- copy会重新开辟一块内存,并将源对象的内容传给新对象,是深拷贝
- strong只是将源对象的指针传给新对象,是浅拷贝,如果源对象的内容变化,新对象也跟着变化
#import <Foundation/Foundation.h>
@interface XYPerson : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,strong) NSString * StrongName;
@end
测试如下
NSMutableString *name = [NSMutableString stringWithFormat:@"will is so"];
self.name = name;
self.StrongName = name;
[name appendString:@" handsome"];
NSLog(@"%@ \n %@", self.name, self.StrongName);
打印如下:
很明显,StrongName内容发生了变化
拓展
经常有这样面试题
用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
答:是因为NSString、NSArray、NSDictionary有对应的可变类型NSMutableString、NSMutableArray、NSMutableDictionary,如果属性被这些可变类型赋值了,那么会导致属性无意变动,为避免这些,使用copy;如果使用strong关键字,会导致属性无意变动
2.3 strong, weak区别
- strong:强引用,其存亡直接决定所指对象的存亡,在赋值时对对象进行retain操作,使引用计数+1
- weak:弱引用,其存亡不决定所指对象的存亡,若所指对象被其他强引用指向,强引用置为nil,则其所指对象也置为nil
2.4 引用循环
- delegate都是用
weak
修饰,为啥
两个对象各有一个强引用指向对方,会造成引用循环
当[tableView.delegate method]
就会使得delegate引用计数+1,导致无法释放,所以用@property (nonatomic, weak) id<Delegate>delegate
- block的引用循环,也可以用
__weak
破
#define RCLog(obj){if (obj){printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));}else{printf("null \n");}}
RCLog(self);//retain count = 7
[self block:^{
self.strongPoint = [NSDate date];
}];
RCLog(self);//retain count = 8
打印引用计数,发现+1了,这里self持有block,而block又持有self,导致了引用循环,我们用__weak来解决:
RCLog(self);//retain count = 7
__weak typeof(self) weakself=self;
[self block:^{
weakself.strongPoint = [NSDate date];
}];
RCLog(self);//retain count = 7
有的block用__strong来修饰对象,是为了防止对象引用时,不会已经是nil了,举个例子:
RCLog(self);//retain count = 7
__weak typeof(self) weakself=self;
[self block:^{
__strong typeof (self) strongself = weakself;
strongself.strongPoint = [NSDate date];
}];
RCLog(self);//retain count = 7
2.5 assign, weak区别
- assign与其他的都不一样,只有他是修饰基本数据类型和结构体的,其他的都是修饰对象的
- weak与assign不一样的地方,是他指向的对象消失时候(内存释放),会自动置为nil,而assign则不会,这样给weak修饰的属性发送消息不会crash
举个例子:
#import <Foundation/Foundation.h>
@interface XYPerson : NSObject
@property (nonatomic, strong) id strongPoint;
@property (nonatomic, weak) id weakPoint;
@property (nonatomic, assign) id assignPoint;
@end
测试如下:
self.strongPoint = [NSDate date];
self.weakPoint = self.strongPoint;
self.assignPoint = self.strongPoint;
self.strongPoint = nil;
打断点,当strongPoint置为nil后,weakPoint也置为nil,而assignPoint则出现野指针
参考
理解 iOS 的内存管理 | 唐巧的博客
Objective-C 内存管理——你需要知道的一切 - skyline75489 - SegmentFault 思否
iOS概念攻坚之路(三):内存管理 - 掘金
iOS 宏(define)与常量(const)的正确使用
iOS 内存管理总结