项目中遇到的内存泄露
现象:
RN页调用客户端的选择相册图片功能,然后RN页通过广播来接收结果,关闭这个RN页之后发现,广播还在接收。
排查:
1、检查这个广播广播listener有没有移除,发现RN的componentWillUnmount()里有调用listener.remove();
2、添加log发现返回上一页之后,没有走到componentWillUnmount(),判定是页面没有被释放。
3、iOS Debug,发现ReactController(iOS中的RN页容器),在返回上一页时,没有调用dealloc方法,这种情况通常是内存泄露引起的。
4、使用Xcode的Instruments检测解决iOS内存泄露(Leak)发现该页确实有内存泄露。
5、定位到iOS中有一个Block的循环引用。
原代码:
- (void)showImagePicker:(UIImagePickerControllerSourceType)sourceType edit:(BOOL)edit
{
switch (sourceType) {
case UIImagePickerControllerSourceTypeCamera:
{
[SystemAuthHelper checkIsAuthorized:SystemAuthTypeCamera result:^(BOOL result) {
if (result) [self doShowImagePicker:sourceType edit:edit];
} forWriteOnly:NO];
}
break;
case UIImagePickerControllerSourceTypePhotoLibrary:
case UIImagePickerControllerSourceTypeSavedPhotosAlbum:
{
[SystemAuthHelper checkIsAuthorized:SystemAuthTypeAlbum result:^(BOOL result) {
if (result) [self doShowImagePicker:sourceType edit:edit];
} forWriteOnly:NO];
}
break;
default:
break;
}
}
可以看到Block内用的self,导致了循环引用
修复后
- (void)showImagePicker:(UIImagePickerControllerSourceType)sourceType edit:(BOOL)edit
{
__weak typeof(self) weakSelf = self;
switch (sourceType) {
case UIImagePickerControllerSourceTypeCamera:
{
[SystemAuthHelper checkIsAuthorized:SystemAuthTypeCamera result:^(BOOL result) {
if (result) [weakSelf doShowImagePicker:sourceType edit:edit];
} forWriteOnly:NO];
}
break;
case UIImagePickerControllerSourceTypePhotoLibrary:
case UIImagePickerControllerSourceTypeSavedPhotosAlbum:
{
[SystemAuthHelper checkIsAuthorized:SystemAuthTypeAlbum result:^(BOOL result) {
__typeof(self) weakSelf2 = weakSelf;
if (!weakSelf2) {
return;
}
if (result) [weakSelf2 doShowImagePicker:sourceType edit:edit];
} forWriteOnly:NO];
}
break;
default:
break;
}
}
反思
RN开发遇到监听有调用remove()但是没有移除,或者说页面没有销毁,大概率是内存泄露导致的。
本次是因为客户端内存泄露导致的,但是平时开发RN也要注意内存泄露,因此总结一下。
iOS内存泄露
内存泄露会导致dealloc无法调用,内存泄露根本原因是循环引用。
内存泄露三大原因:
一、NSTimer没有销毁
[
NSTimer
scheduledTimerWithTimeInterval:self.autoDismissDuration
target:self
selector:@selector(dismiss)
userInfo:nil
repeats:NO
];
注意:别忘了调用[timer invalidate]
二、ViewController中的代理delegate
@protocol ClassADelegate <NSObject>
@optional
- (void)didChange;
@end
@interface ClassA : NSObject
@property (nonatomic, weak) id<ClassADelegate> delegate;
@end
注意:delegate要用weak修饰,不能用strong
三、Block循环引用
见上面的问题修复的例子
RN内存泄露
一、文件循环引用
View1:
import { View2 } from './View2';
export const ConstValueA = 'aaaaaaaa';
export const View1 = (props) => {
return <View2/>;
};
View2:
import { ConstValueA } from './View1';
export const View2 = (props) => {
return <Text>{ConstValueA}</Text>
};
View1和View2循环引用
修改:
View1:
import { View2 } from './View2';
export const View1 = (props) => {
return <View2/>;
};
utils:
export const ConstValueA = 'aaaaaaaa';
View2:
import { ConstValueA } from './utils';
export const View2 = (props) => {
return <Text>{ConstValueA}</Text>
};
二、广播没有及时清理
componentDidMount() {
this.listener = NativeEmitter.addListener(
'info_change',
event => {
}
);
}
//注意移除
componentWillUnmount() {
if (this.listener) {
this.listener.remove();
}
}
Hooks的写法
useEffect(() => {
let listener = MaiMaiNativeEmitter.addListener(
'action.to.webview',
handleBoradcast
);
//注意这里要remove
return () => {
listener.remove();
};
}, []);