内存泄漏和内存溢出
内存泄漏(memory leak):是指申请的内存空间使用完毕之后未回收。 一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序crash
内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用。 通俗理解就是内存不够用了,通常在运行大型应用或游戏时,应用或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。最终导致机器重启或者程序crash
内存泄漏常见场景 非OC对象内存处理
CF类型内存以creat,copy作为关键字的函数都是需要释放内存的,注意配对使用。比如:CGColorCreate<-->CGColorRelease
CoreFoundation类型
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 __typeof (self )weakSelf = self ; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __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) { MTModel *model = [[MTModel alloc] init]; [subscriber sendNext:model]; [subscriber sendCompleted]; return nil ; }]; self .flattenMapSignal = [signal flattenMap:^RACStream *(MTModel *model) { return RACObserve(model, title); }]; [self .flattenMapSignal subscribeNext:^(id x) { 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) { GJModel *model = [[GJModel alloc] init]; [subscriber sendNext:model]; [subscriber sendCompleted]; return nil ; }]; self .flattenMapSignal = [signal flattenMap:^RACStream *(GJModel *model) { __weak GJModel *target_ = model; return [target_ rac_valuesForKeyPath:@keypath(target_, title) observer:self ]; }]; [self .flattenMapSignal subscribeNext:^(id x) { 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 ); return [[RACSignal createSignal:^(id <RACSubscriber> subscriber) { RACStreamBindBlock bindingBlock = block(); NSMutableArray *signals = [NSMutableArray arrayWithObject:self ]; }] 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]; if (signals.count == 0 ) { [subscriber sendCompleted]; [compoundDisposable dispose]; } else { removeDisposable = YES ; } } if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; };
总之,一句话: 使用ReactiveCocoa必须要保证信号发送完成或者发送错误 。
参考
ReactiveCocoa中潜在的内存泄漏及解决方案