iOS和前端内存泄露总结

491 阅读2分钟

项目中遇到的内存泄露

现象:

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();
          };
  }, []);