一、堆与栈
1. 栈区(stack):由编译器自动分配释放,函数的参数值,局部变量等值。
2. 堆区(heap):一般由开发人员分配释放,若不释放,则可能会引起内存泄漏。
NSString* string = @"sdfsdf";//常量string->栈
NSInteger index = 0; //index->栈
NSMutableString* mString = [[NSMutableString alloc] initWithString:@"sdfsdf"];//mString->堆
二、内存管理
Objective-C提供了两种种内存管理方式:manual reference counting(MRC,手动引用计数器),automatic reference counting(ARC,自动引用计数)。ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;
1、引用计数器
1)采用引用计数进行管理
- 每个对象都有一个关联的整数,称为引用计数器
- 当代码需要使用该对象时,则将对象的引用计数加1
- 当代码结束使用该对象时,则将对象的引用计数减1
- 当引用计数的值变为0时,此时对象将被释放。
2)与之对应的消息发送方法
- 当对象被创建(alloc、new或copy等方法)时,其引用计数初始值为1
- 给对象发送retain消息,其引用计数加
- 给对象发送release消息,其引用计数减1
- 当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象
3)下面通过一个简单的例子来说明
新建RetainCountObject类,重写其创建和销毁的方法
@implementation RetainCountObject
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"初始引用计数为 %ld",self.retainCount);
};
return self;
}
- (void)dealloc {
NSLog(@"对象被释放");
NSLog(@"release后的引用计数为 %ld", self.retainCount);
[super dealloc];
}
@end
在ViewDidLoad方法中创建RetainCountObject对象,给object发送消息
RetainCountObject * object = [[RetainCountObject alloc]init];
[object retain];
NSLog(@"object引用计数为 %ld", object.retainCount);
[object release];
NSLog(@"objec引用计数为 %ld", object.retainCount);
[object release]; //将指针置nil,否则变为野指针
object = nil;
MRC_Project[40145:1469636] 初始引用计数为 1
MRC_Project[40145:1469636] object引用计数为 2
MRC_Project[40145:1469636] objec引用计数为 1
MRC_Project[40145:1469636] 对象被释放
注意一些特殊的情况:
NSString引用计数问题
如果我们尝试查看一个string的引用计数
NSString * str = @" hello"; NSLog(@"hello guys :%ld", str.retainCount);
MRC_Project[40189:1472607] hello :-1
NSString实际上是一个字符串常量,由栈管理,是没有引用计数的。
赋值不会拥有某个对象
NSString* title = object.title;
这里仅仅是指针赋值操作,并不会增加name的引用计数,需要持有对象必须要发送retain消息。
4)Dealloc 析构函数
由于释放对象是会调用dealloc方法,因此重写dealloc方法来查看对象释放的情况,如果没有调用则会造成内存泄露。在上面的例子中我们通过重写dealloc让对象被释放的时候打印日志来告诉我们已经完成释放。
在上面例子中,如果我们增加这样一个操作
//最后release object时
[object release];
NSLog(@"release后的引用计数为 %ld", self.retainCount);
[super dealloc];
MRC_Project[40314:1477373] release后的引用计数为 1
会发现获取到的引用计数为1,为什么不是0呢?
这是因为对引用计数为1的对象release时,系统知道该对象将被回收,就不会再对该对象的引用计数进行减1操作,这样可以增加对象回收的效率。
2、自动释放池
1)AutoreleasePool的原理
自动释放池,系统有一个现成的自动内存管理池,他会随着每一个mainRunloop的结束而释放其中的对像;自动释放池也可以手动创建,他可以让pool中的对象在执行完代码后马上被释放,可以起到优化内存,防止内存溢出的效果(如视频针图片的切换时、创建大量临时对象时等)。
autorelease:自动释放,使对象在超出指定的生存范围时能够自动并正确地释放 (release 即是立即释放)。
2)自动释放池的创建
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
// do something
id obj = [[NSMutableArray alloc] init];
[obj autorelease];
[pool release];
// [pool drain];//GC(垃圾回收机制)环境没影响
对于所有调用过autorelease实例方法的对象,在pool release时,将调用release释放对象
@autoreleasepool {
// do something
}
MRC方法放回对象时需要autorelease
- (NSArray *)getArray{
NSArray* array = [NSArray array];
return array;
/*
NSArray* array = [[[NSArray alloc] init] autorelease];
return array;
*/
}
3)自动释放池的触发
- (void)autoRelease_Test {
@autoreleasepool {
TestModel *model = [[TestModel alloc] init];
[model autorelease];
//model can dongSomething you want
NSLog(@"自动释放:end");
}
}
MRC_Project[2678:287011] 自动释放:end
MRC_Project[2678:287011] TestModel dealloc
可以看到,当自动释放调用后,model对象才被释放,因此在池子释放之前,model可以正常调用。
3、IOS内存管理规则
1)基本原则
- 当你通过new、alloc或copy方法创建一个对象时,它的引用计数为1,当不再使用该对象时,应该向对象发送release或者autorelease消息释放对象。
- 当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为autorelease,则不需要执行任何释放对象的操作;
- 如果你打算取得对象所有权,就需要保留对象并在操作完成之后释放,且必须保证retain和release的次数对等。
2)ARC的修饰变量
strong, weak, autoreleasing, unsafe_unretained
__strong ://强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
比如我们常用的定时器:
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:nil userInfo:nil repeats:NO];
相当于:
NSTimer* __strong timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:nil userInfo:nil repeats:NO];
当不需要使用时,强制销毁定时器:
[timer invalidate];
timer = nil;
__weak : // 弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。
比如避免循环引用的弱引用声明:
__weak typeof(self) weakSelf = self;
__autoreleasing // 自动释放对象的引用,一般用于传递参数
比如一个读取数据的方法:
- (void)doSomething:(NSError **)error { ... }
当你调用时会发现这样的提示:
[self doSomething: (NSError *__autoreleasing *)]
这是编译器自动帮我们插入以下代码:
__autoreleasing NSError *error = nil;
[self doSomething:&error];
__unsafe_unretained // 弱引用,内存被释放后,指针不会置空,而是变成一个野指针
__unsafe_unretained UIWindow *window = nil;
[invocation getReturnValue:&window];
3)ARC及MRC的属性
@property (nonatomic, assign) int value; //简单的赋值
@property (nonatomic, retain) NSArray* array; //引用计数器加一,指针复制
@property (nonatomic, copy) NSString* string; //生成新的内存区域,内容复制
@property (nonatomic, strong) NSNumber* number;//强引用,当所有指向同一块内存的强指针都赋空时,内存将被释放
@property (nonatomic, weak) NSData* data; //弱引用, 当所有指向同一块内存的强指针都赋空时,弱引用失效,为nil
@property (nonatomic, unsafe_unretained) NSData* data1; //不安全性弱引用,当所有指向同一块内存的强指针都赋空时,该对象将成为野指针,再次调用会导致程序崩溃
- (NSString *)string {
return _string;
}
- (void)setString:(NSString*)string {
_string = string;
//weak,strong,assign 的set方法
}
- (void)setString:(NSString*)string {
if(_string != string) {
[_string release];
_string = [string retain];
}
//retain 的set方法
}
- (void)setString:(NSString*)string {
if(_string != string) {
[_string release];
_string = [string copy];
}
//copy 的set方法
}
//释放属性对象
- (void)dealloc {
self.string = nil;
[super dealloc];
}
4)Block内存管理
Block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。
// MRC
@property(nonatomic, copy) void(^block)(void);
MRC中 copy会把block从栈上移动到堆上。
// ARC
@property(nonatomic, strong) void(^block1)(void);
@property(nonatomic, copy) void(^block2)(void);
ARC即时由强引用strong将其从栈复制到堆(copy仍旧可用)
block 的内存管理:
当程序运行到这里时,stack 空间中有 shared 变量和 captured 变量。
这里可以看出,__block 变量开始是处于stack上的。
当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1。
这里可以看出,block 类型的变量开始时也是处在stack上的。
当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1。
这里值得注意的就是当我们直接修改stack 上的captured变量时,block1中的captured变量仍然是原来的数值10。事实上,从const 我们就可以看出,block1中的captured变量是不能被修改的而且是从stack原有变量的一个const 拷贝。在block1中访问的captured变量是const拷贝的,也就是说block1中captured = 10,而不是原有的stack上的值 20。当然,在block1中,我们也不能修改captured变量。
Copy Block
block在一开始是处在stack上的,这是为了考虑到效率的原因,但是,有时候是需要block的生命周期长于一开始的stack,这时,我们就通过copy block 来将block复制到heap。
当程序执行完 block2 = [block1 copy];时,__block 类型变量shared,被复制到了heap中,很显然,shared变量需要被block和block2共享(当然还有stack也要共享),而block2被移动到heap中,很可能生命周期会长于stack,所以,shared也被复制到了heap中。而block2中的captured 也被复制到了heap中。
当程序执行完 block3 = [block2 copy];时, 我们看到的是,block2 和block3 其实指向的是同一片内存空间。事实上,block的数据结构中,保存了引用计数,而对于copy到heap中的block 再copy时,行为同普通对象retain一样,会使引用计数+1。那么如果我们对[block retain]会如何呢? 实际上什么都没有发生,至少在现在的runtime版本下。因为retain中,不仅有引用计数+1在,而且retain的返回值,必须同返回调用对象的地址一样,而block的地址是可能变化的(stack or heap),所以,这里retain的行为几乎是被忽略掉的。
当heap中的block变量先于stack被销毁时,如调用 [block2 release]; [block3 release];,heap中的block2,block3 由于引用计数为0 而被销毁,而 __block 变量shared则还在heap中,因为stack还要使用,block1 也要使用。
当heap中的block变量晚于stack时,显然,stack 被清除,function中也啥都没了。
最后,当block2 和block3 都被release之后,则恢复到最初状态。
5)从MRC到ARC的转变
项目 -> Build Phases -> Compile Sources 找到要修改的文件
ARC工程添加MRC文件则输入:-fno-objc-arc
MRC工程添加ARC文件则输入:-fobjc-arc
引用方式
retain:原对象引用计数加1(ARC中使用strong)
copy:拷贝对象为栈内存时,将栈内存拷贝至堆内存,生成新的内存地址 ,原对象引用计数不变;堆内存拷贝,非可变类型的对象拷贝-NSArray(原对象引用计数加1,与retain类似),可变类型的对象拷贝-NSMutableArray(生成新的非可变类型的对象-NSArray,原对象引用计数不变)
assign:不涉及引用计数的变化(ARC中对象不使用assign,但数据类型仍然可以使用)
weak:比assign多了一个功能,对象释放后把指针置为nil,避免了野指针。
strong:等同于retain + 栈内存拷贝
在你打开ARC时,你是不能使用retain、release、autorelease 操作的,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了,但是你需要在对象属性上使用weak 、strong和copy,其中strong就相当于retain + 栈内存拷贝,而weak相当于assign,数据类型仍旧使用assign声明。
差异性
在MRC中以下代码是行不通的,因为从Array中移除一个对象时,这个对象会发送一条release消息,随后仍使用该对象就可能导致crash。
id obj = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
NSLog(@"%@", obj);
在ARC中这段代码是完全合法的,因为obj变量是一个strong指针,它成为了对象的拥有者,从Array中移除该对象也不会导致对象被释放。
4、经典内存泄漏
1)僵尸对象和野指针
僵尸对象:内存已经被回收的对象。
野指针:指向僵尸对象的指针,向野指针发送消息会导致崩溃。
EXC_BAD_ACCESS
2)循环引用
1. delegate循环引用
ARC中默认的对象声明都是strong性质的,在两个或两个以上的类相互引用时,会导致循环引用,其中一方需要用weak修饰,才不会造成retainCycle,如:delegate 属性用weak声明;MRC中即用assign修饰 。
@interface ClassA : NSObject
@property (nonatomic, weak) id <ClassADelegate> delegate;
@end
@interface ClassB : NSObject <ClassADelegate>
@property (nonatomic, strong) ClassA *objectA;
@end
@implementation ClassB
- (void)doSomething {
self.objectA = [[ClassA alloc] init];
_objectA.delegate = self;
}
@end
2. Block循环引用
在block中引用block所属的类、实例变量或类的属性也会导致循环引用
self.block = ^{
[self doSomething];
};
block是会对内部的对象进行一次retain。也就是说,self会被retain一次。当self释放的时候,需要block释放后才会对self进行释放,但是block的释放又需要等self的dealloc中才会释放。如此一来变形成了循环引用,导致内存泄露。
arc中用__weak修饰self、mrc中用__block修饰,如下代码:
__weak ViewController* weakSelf = self;//arc
//__block ViewController* weakSelf = self;//mrc
self.block = ^{
[weakSelf doSomething];
};
3. NSTimer循环引用
NSTimer在使用期间会强引用target,结束定时器需要手动销毁。
- (void)createTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeCount) userInfo:nil repeats:YES];
}
- (void)destroyTimer {
if (_timer) {
[_timer invalidate];
self.timer = nil;
}
}
delegate与block都是引用了对象指针,NSTimer引用的是对象内容,在定时器没有销毁前,使用__weak并不能解决循环引用问题,可以通过NSProxy将NSTimer对target的强引用转为弱引用。
- (void)createTimer {
// YYKit-YYWeakProxy
YYWeakProxy* weakProxy = [[YYWeakProxy alloc] initWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakProxy selector:@selector(timeCount) userInfo:nil repeats:YES];
}
3)__bridge导致的内存泄漏
1. __bridge,OC对象转成CF对象,内存的所有权还在ocStr手中,由ARC负责释放。
NSString *ocStr = [[NSString alloc] initWithFormat:@"hello"];
CFStringRef cfStr = (__bridge CFStringRef)ocStr;
2. __bridge,CF对象转成OC对象,内存的所有权还在cfStr手中,所以必须手动释放cfStr,否则会有内存泄漏。
CFStringRef cfStr = CFStringCreateWithCString(NULL, "hello", kCFStringEncodingUTF8);
NSString *ocStr = (__bridge NSString *)cfStr;
CFRelease(cfStr);
3. __bridge_transfer,等价于CFBridgingRelease(),CF对象转成OC对象,内存的所有权从cfStr移交到ocStr手上,由ARC负责释放。
CFStringRef cfStr = CFStringCreateWithCString(NULL, "hello", kCFStringEncodingUTF8);
NSString *ocStr = (__bridge_transfer NSString *)cfStr; // 内存由cf转移至oc
/* 等价方法
NSString *ocStr = (NSString *)CFBridgingRelease(cfStr);
*/
4. __bridge_retained,等价于CFBridgingRetain(),OC对象转成CF对象,内存的所有权从ocStr移交到cfStr手上,所以必须手动释放cfStr,否则会有内存泄漏。
NSString *ocStr = [[NSString alloc] initWithFormat:@"hello"];
CFStringRef cfStr = (__bridge_retained CFStringRef)ocStr; // 内存由oc转移至cf
/* 等价方法
CFStringRef cfStr = (CFStringRef)CFBridgingRetain(ocStr);
*/
CFRelease(cfStr);
4)循环中大量的临时对象
示例代码:
for (int i = 0; i < 10000; i ++) {
Person * soldier = [[Person alloc]init];
[soldier fight];
}
该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存急剧上涨。解决方法和上文中提到的自动释放池常见问题类似:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for (int i = 0; i < 10000; i ++) {
@autoreleasepool {
Person* soldier = [[Person alloc]init];
[soldier fight];
}
}
然而有时候autoReleasePool也不是万能的:
for (int i = 0; i < 2000; i ++) {
CGSize size = [UIImage imageNamed:[NSString stringWithFormat:@"%d.jpg",i]].size;
}
用imageNamed方法加载图片占用Cache的内存,autoReleasePool也不能释放。
for (int i = 0; i < 2000; i ++) {
@autoreleasepool {
CGSize size = [UIImage imageWithContentsOfFile:filePath].size;
}
}
5)系统内存告警
系统内存瓶颈发出的内存告警-MemoryWarning
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
self.view = nil;
self.data = nil;
}
5、IOS内存管理相关补充
1. retain操作对stackblock无效,只能用copy
2. __strong强引用__weak,防止self可能被释放
__weak typeof(self) weakSelf = self;
self.reachabilityHandler = ^(ReachabilityStatus status) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething];
};
3. GCD的block中无需__weak,GCD的block里面只引用了self ,self并没有引用GCD的block所以不需要用弱引用
dispatch_async(dispatch_get_main_queue(), ^{
//_Block_object_assingn(&self); self retain
[self doSomething];
//_Block_object_dispose(&self); self release
});
4. 声明copy和strong,NSMutable在copy后变成NS(新的内存空间),strong只是引用计数加一。
@property (nonatomic, strong) NSMutableArray* s_arr;
@property (nonatomic, copy) NSMutableArray* c_arr;
5. NSMutableDictionary生成的allKeys和allValues对象,不受key:value的删除而删除
NSMutableDictionary* dic = [NSMutableDictionary dictionaryWithDictionary:@{@"ss":@"bb", @"AA": @"BB"}];
NSArray* ABCallValue = [dic allValues];
NSArray* ABCallKey = [dic allKeys];
[dic removeObjectForKey:@"AA"];
NSLog(@"[ABCallValue description]:%@\n[ABCallKey description]:%@", [ABCallValue description], [ABCallKey description]);
/*
[ABCallValue description]:(bb,BB)
[ABCallKey description]:(ss,AA)
*/
6. super在block中会导致循环引用,需要注意调用方式
- (void)createTabBarItem {
__weak typeof(self) weakSelf = self;
[tabBarItem setSelectBlock:^(NSInteger index) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf superSetSelectedIndex:index];
}];
[self.tabBar addSubview:tabBarItem];
}
// 单独出来,在setSelectBlock使用super会导致循环引用
- (void)superSetSelectedIndex:(NSUInteger)index {
[super setSelectedIndex:index];
}
7. NSNotificationCenter的usingBlock会强引用self,需要__weak声明
__weak typeof(self) weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething];
}];