学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

iOS内存泄漏总结

内存泄漏和内存溢出

内存泄漏(memory leak):是指申请的内存空间使用完毕之后未回收。 一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序crash

内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用。
通俗理解就是内存不够用了,通常在运行大型应用或游戏时,应用或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。最终导致机器重启或者程序crash

内存泄漏常见场景

非OC对象内存处理
  1. CF类型内存以creat,copy作为关键字的函数都是需要释放内存的,注意配对使用。比如:CGColorCreate<-->CGColorRelease
  2. CoreFoundation类型
  3. C语言代码中的malloc等需要对应free等
delegate循环引用

注意将代理属性修饰为weak即可. 例如:
ViewController的self.view持有了UITableView. 所以UITableView的delegate和dataSource就不能持有ViewController. 所以要使用weak来修饰.

NSTimer

NSTimer会造成循环引用,timer会强引用target即self,一般self又会持有timer作为属性,这样就造成了循环引用。

只要申请了timer,加入了runloop,并且target是self,虽然不是循环引用,但是self却没有释放的时机。如下方式申请的定时器,self已经无法释放了。

1
2
3
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(commentAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

解决方案:

NSProxy方式
建立一个proxy类,让timer强引用这个实例,这个类中对timer的使用者target采用弱引用的方式,再把需要执行的方法都转发给timer的使用者。

NSNotification

使用block的方式增加notification,引用了self,在删除notification之前,self不会被释放,与timer的场景类似,其实这段代码已经声明了weakself,但是调用_eventManger方法还是引起了循环引用。
也就是说,即使我们没有调用self方法,_xxx也会造成循环引用。

1
2
3
4
5
6
7
8
[[NSNotificationCenter defaultCenter] addObserverForName:kUserSubscribeNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
if (note) {
Model *model=(Model *)note.object;
if ([model.subId integerValue] == [weakSelf.subId integerValue]) {
[_eventManger playerSubsciption:NO];
}
}
}
Block

block中导致的内存泄漏常常就是因为强引用互相之间持有而发生了循环引用无法释放. AFNetWorking上的经典代码, 防止循环引用.

1
2
3
4
5
6
7
8
9
10
11
//创建__weak弱引用,防止强引用互相持有
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
//创建局部__strong强引用,防止多线程情况下weakSelf被析构
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};

大次数循环内存暴涨问题

循环内产生大量的临时对象, 直至循环结束才释放, 可能导致内存泄漏, 解决方法为在循环中创建自己的autoReleasePool, 及时释放占用内存大的临时变量, 减少内存占用峰值.

1
2
3
4
5
6
7
8
or (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
RAC中潜在的内存泄漏
RACObserve内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad
{
[super viewDidLoad];
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //1
MTModel *model = [[MTModel alloc] init]; // MTModel有一个名为的title的属性
[subscriber sendNext:model];
[subscriber sendCompleted];
return nil;
}];
self.flattenMapSignal = [signal flattenMap:^RACStream *(MTModel *model) { //2
return RACObserve(model, title);
}];
[self.flattenMapSignal subscribeNext:^(id x) { //3
NSLog(@"subscribeNext - %@", x);
}];
}

控制器返回时,控制器并没有被释放,看下RACObserve的定义:

1
2
3
4
5
6
7
8
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})

注意这一句:[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; 如果将宏简单展开就变成了下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)viewDidLoad
{
[super viewDidLoad];
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id < RACSubscriber > subscriber) { //1
GJModel *model = [[GJModel alloc] init];
[subscriber sendNext:model];
[subscriber sendCompleted];
return nil;
}];
self.flattenMapSignal = [signal flattenMap:^RACStream *(GJModel *model) {//2
__weak GJModel *target_ = model;
return [target_ rac_valuesForKeyPath:@keypath(target_, title) observer:self];
}];
[self.flattenMapSignal subscribeNext:^(id x) {//3
NSLog(@"subscribeNext - %@", x);
}];
}

flattenMap操作接收的block里面出现了self,对self进行了持有,而flattenMap操作返回的信号又由self的属性flattenMapSignal进行了持有,这就造成了循环引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad
{
[super viewDidLoad];
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id < RACSubscriber > subscriber) {
GJModel *model = [[GJModel alloc] init];
[subscriber sendNext:model];
[subscriber sendCompleted];
return nil;
}];
@weakify(self); //
self.signal = [signal flattenMap:^RACStream *(GJModel *model) {
@strongify(self); //
return RACObserve(model, title);
}];
[self.signal subscribeNext:^(id x) {
NSLog(@"subscribeNext - %@", x);
}];
}
RACSubject内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *subject = [RACSubject subject];
[subject.rac_willDeallocSignal subscribeCompleted:^{
NSLog(@"subject dealloc");
}];

[[subject map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"next = %@", x);
}];
[subject sendNext:@1];
}

输出结果:2016-06-13 09:21:42.450 RAC[5404:248584] next = 3,可以看到subject dealloc没有输出,也就是说subject没有释放,将RACSubject换成RACSignal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
[super viewDidLoad];
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
return nil;
}];
[signal.rac_willDeallocSignal subscribeCompleted:^{
NSLog(@"signal dealloc");
}];
[[signal map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"next = %@", x);
}];
}

输出结果:

1
2
 2016-06-12 23:32:31.669 RACDemo[5085:217082] next = 3
2016-06-12 23:32:31.674 RACDemo[5085:217082] signal dealloc

同样的逻辑,signal能正常释放,subject却不能正常释放, 给RACSubject发送一个完成信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *subject = [RACSubject subject];
[subject.rac_willDeallocSignal subscribeCompleted:^{
NSLog(@"subject dealloc");
}];

[[subject map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"next = %@", x);
}];
[subject sendNext:@1];
[subject sendCompleted];
}

输出结果:

1
2
2016-06-12 23:40:19.148 RAC_bindSample[5168:221902] next = 3
2016-06-12 23:40:19.153 RAC_bindSample[5168:221902] subject dealloc

subject被释放了

RACSignal和RACSubject虽然都是信号,但是它们有一个本质的区别: RACSubject会持有订阅者(因为RACSubject是热信号,为了保证未来有事件发送的时候,订阅者可以收到信息,所以需要对订阅者保持状态,做法就是持有订阅者),而RACSignal不会持有订阅者。

原因:map函数内部会调用bind,来看下bind的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
* 2. Any time the original signal sends a value, transform it using the binding block.
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACStreamBindBlock bindingBlock = block();
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
// 此处省略了80行代码
// ...
}] setNameWithFormat:@"[%@] -bind:", self.name];
}

在didSubscribe的开头,就创建了一个数组signals,并且持有了self,也就是源信号:NSMutableArray *signals = [NSMutableArray arrayWithObject:self];

为什么发送完成可以修复内存泄漏?

发送完成调用了一下completeSignal这个block。再看下这个block内部在干嘛:

1
2
3
4
5
6
7
8
9
10
11
12
13
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal]; //1
if (signals.count == 0) {
[subscriber sendCompleted]; //2
[compoundDisposable dispose]; //3
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; //4
};

总之,一句话: 使用ReactiveCocoa必须要保证信号发送完成或者发送错误

参考

  1. ReactiveCocoa中潜在的内存泄漏及解决方案