iOS中内存管理漫谈
内存管理!MRC
说一堆blabla(当然啦,各种release的痛想必记忆犹新),ARC
说一堆blabla...
多么老生常谈的问题,想必每个开发者在起初不止一次的被问起这个问题,但是一旦被问起,一般大家都会说起'黄金法则’,然后@property
,blabla一大堆…
大家都知道iOS的内存管理是通过引用计数来管理的,当引用计数为0的时候,对象会自动释放掉,在ARC
环境下系统会“自动管理”对象的引用计数,然而事实上并非如此,我们常常遇到试图控制不能正常释放问题,block
使用的时候内存泄露.
好吧,多么痛才能领悟,今天咱们就聊聊这个蛋疼的问题,当然啦是在ARC环境下,分别从这么几个地方聊聊
-
@property
— 几个内存管理相关的修饰符 -
self
— 大家有没有想过,self
在ARC
下到底什么类型的,strong?weak? -
block
— 这玩儿大家每天都在写,但是说起来他的内存管理方式,不知道有多少能说全面的
Property
Property想必大家都写过,当然不排除还有很多一直在使用_ivar的,当然啦他们之间的区别以及该用那个好咱们今天不聊,如有想了解的同学请移步这里.属性的内存管理语义有 以下几个:
assign
:适用于基本数据类型,其set
方法只会进行简单的赋值操作NSInteger
,CGFloat
...
/** 基本数据类型*/
@property (assign, nonatomic) NSInteger age;
strong
:强引用类型,被定义了该类型的属性会被其所指对象持有,为这种属性设置新值的时候,set
方法先保留新值,并释放掉旧值,然后再把新值赋值上去
/** 适用于对象类型,并且被持有*/
@property (strong, nonatomic) UIView *view;
weak
:爱恨交织的weak
!弱引用类型,被定义了该类型的属性不会被持有,在set
的时候,既不保留新值也不会释放旧值,和assign
类似,不同的是在该属性所指对象被销毁之后,属性值也会被置为nil
/** 弱引用类型,对象释放后置为nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;
unsafe_unretained
:这个跟assign
就更像了,但不同的是它适用于对象类型.同样的不会被持有,但与weak
不同是,所指对象被销毁之后不会把属性置为nil
/** 弱引用类型,对象释放后不会置为nil*/
@property (unsafe_unretained, nonatomic) UIView *view;
copy
:它与所指对象是持有关系,与strong
类似,然而set
方法并不会保留新值,而是将其拷贝一份.所以,当你声明一个非可变集合类型(collection type)的时候应该使用这个修饰符,不单单是NSString
,同样的NSArray
,NSDictionary
,NSSet
也应当使用它来修饰,copy
可以保护非可变集合类型(collection type)的封装性,因为在其传递过程中有可能会指向一个可变类型(mutable type),此时如果不是copy
,那么设置完属性之后,其值可能会在对象不知情的情况下被更改了.所以使用copy
修饰符,保证set
的时候copy
一份不可变(immutable)类型的值,确保前后的一致性.
/** 适用于集合类型(collection type)*/
@property (copy, nonatomic) NSString *name;
Self
看到这个self
估计很多人应该开始吐槽了,这SB,self
有啥内存管理可言?需要管理么?
哥负责任的告诉你,需要!需要!需要!!!
行,你说需要就需要吧,哪里需要?Show me the code!!!
也许你可能会看到过这样代码,然后一头雾水的就略过了...
#import "SSYNetwork.h"
- (void)startRequestData {
SSYNetwork *strongSelf = self;
[strongSelf.delegate finishedRequestData:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
}
我头一次看到的时候也是一头雾水,这是干嘛呢?写得看起来很高大.仔细分析后就能够发现,这段代码之所以增加一行strongSelf
,是为了防止提前释放导致crash
.
这是一个请求网络数据的start方法,然而却在里面调用了finish方法,其实还没有等到start
方法执行结束并返回的时候,self
已经被finishedRequestData:
方法释放,造成crash,简单的来说大致是这样的:
- (void)clickAvatar {
// self持有delegate
[self.delegate clickAvatar]; // clickAvatar这个代理方法释放了self
// 这个时候self成了野指针,然后就Boom💣
}
那么现在我们可以考虑一下了,在ARC里面self到底是个什么状态?为何会这样?
其实ARC
下,self
既不是strong
,也不是weak
,而是被我们忽略的unsafe_unretained
在我们调用方法的时候,ARC
不会对self
做retain
或release
,self
的生命周期由他的调用方法来决定
所以self
还是需要做一定的内存管理,不然一不小心就会💣(Boom
)
Block
block
其实是我们几乎每天都在用的东西,然而又让人讨厌的东西,之所以讨厌,是因为没有做好内存管理,产生循环引用导致内存泄露甚至是crash
,下面咱们就唠唠这个烦人的玩意儿
声明方式
// 局部变量
returnType (^blockName)(parameterTypes) = ^returnType(parameters){...}
// 属性,作为属性的时候其修饰符一定是copy
@property (nonatomic, copy) returnType (^blockName)(parameters);
// 作为参数
- (void)someMethodThatTakesABlock:(returnType (^)(parameters))blockName;
// 方法调用是作为参数
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
// 宏定义
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
循环引用
在使用block
的时候产生了循环引用是最让人头疼的,那么为什么会产生循环引用呢?先看看产生循环引用的代码长什么样,再分析分析why?
// 两个对象间相互引用
@property (nonatomic, copy) void(^block)(void);
self.block = ^{
[self doSomething];
};
// 多个对象间相互引用,多对象相互引用更难以发现
ClassA* objA = [[ClassA alloc] init];
self.block = ^{
[self doSomething];
};
self.objA = objA;
上面的这段代码中self
和block
之间相互持有,在block
里面调用的doSomething
方法,使得self
引用计数+1
,这个过程中self
是不会被释放,这就导致了循环引用.OK,那么问题来了,如何解决呢?
其实,解决这个问题就在于如何让block不持有self,要解决这个问题,只需要使用__weak
声明一个弱引用(weakSelf
)就可以了,代码如下:
@property (nonatomic, copy) void(^block)(void);
// 弱引用
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething];
};
搞定,这样之后我们再调用dealloc
方法,打上断点,就可以看出self
已经被释放了
Wow!💐💐💐💐💐
不要天真的以为这样就万事大吉,就可以丢下鼠标等胜利了!
如果我说,有时候即使你使用了__weak
做了弱引用,也还是有crash的危险!
请看下面的代码:
// 弱引用
__weak typeof(self) weakSelf = self;
// 异步线程,这里的block是作为一个参数的
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.xx = xx;
});
让我们分析一下原因,将block作为参数传递给dispatch_async
时,系统会将block
拷贝到堆(heap
)上,如果block
中使用了self
持有的对象,就会retain self
,因为dispatch_async
并不知道self
会在什么时候被释放,所以为了确保系统调度执行block
中的任务时self
没有被意外释放掉,dispatch_async
必须自己retain
一次self
,任务完成后在release self
.但是这里使用了__weak
,使得dispatch_async
没有增加self
的引用计数,这就导致了在调度执行block
之前,self
可能已经被释放掉了,然后就crash💣,控制台提示BAD_ACCESS
好吧,算你对了,那你说该咋办?
嘿嘿,既然没有retain
,那就想办法retain
呗?代码如下:
// 弱引用
__weak typeof(self) weakSelf = self;
// 异步线程,这里的block是作为一个参数的
dispatch_async(dispatch_get_main_queue(), ^{
// 加强引用防止提前释放,并且做判断
__strong typeof(weakSelf) strongSelf = self;
if (strongSelf) {
strongSelf.xx = xx;
}
});
然而这样写就好了么?还有没有更好的方法?程序员都是有追求的,我们是一群高逼格的从业者!
再装逼试试.jpg好吧,我只是想说还有更优雅的方式来写下面两句话
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
别激动,开个小玩笑.jpg
有使用过ReactiveCocoa的同学应该知道,其实我们可以使用影子变量@weakify/@strongify
这种方式同样能解决,至于关于影子变量的详细介绍这里就不多说了,有兴趣的同学可以到这里看看,使用影子变量后,代码就会像下面这样:
@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@strongify(self);
if (!self) return;
self.xx = xx; // 这里就可以放心的使用self,简单粗暴
});
OK,装逼结束!!!
如有错误之处,欢迎讨论.