ぴよログ

↓に移転したのでこっちは更新されません、多分。

AVFoundationを使ったカメラ機能はちゃんとメモリを開放しましょう

移転しました →

こちらを参考にAVFoundationを使ったカメラを実装したのですが、このやり方だとどうもメモリを圧迫するということがわかりました。

iOSのカメラ機能を使う方法まとめ【13日目】 | Developers.IO

カメラ機能を使うビューに移動→戻る、を繰り返すとだんだんと移動が重くなっていきます。1分以上反応しなかったりとか。Xcodeでモニタリングしているメモリ使用量も増え続けます。

調べてみると、viewDidLoadでセットアップを行ったAVFoundation系のオブジェクトを開放をしていないのが原因のようです。

先の記事ではセットアップメソッドsetupAVCaptureviewDidLoadで呼んでいます。ビューの移動、戻るを繰り返すとviewDidLoadでひたすらsetupAVCaptureが呼ばれ続けるわけですね。多分ARCで開放してくれるんでしょうけど、実用上は自前で開放を実装しておかないと使い物にならないようです。

// iOSのカメラ機能を使う方法まとめ【13日目】から引用
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 略
    [self setupAVCapture];
}
 
- (void)setupAVCapture
{
    NSError *error = nil;
    
    // 入力と出力からキャプチャーセッションを作成
    self.session = [[AVCaptureSession alloc] init];
    
    // 正面に配置されているカメラを取得
    AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    // カメラからの入力を作成し、セッションに追加
    self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];
    [self.session addInput:self.videoInput];
    
    // 画像への出力を作成し、セッションに追加
    self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
    [self.session addOutput:self.stillImageOutput];
    
    // キャプチャーセッションから入力のプレビュー表示を作成
    AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    captureVideoPreviewLayer.frame = self.view.bounds;
    captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    // レイヤーをViewに設定
    CALayer *previewLayer = self.previewView.layer;
    previewLayer.masksToBounds = YES;
    [previewLayer addSublayer:captureVideoPreviewLayer];
    
    // セッション開始
    [self.session startRunning];
} 

変更後

setupAVCaptureviewDidLoadではなくviewWillAppearで呼び、開放処理をviewDidDisappearで呼ぶようにすれば良いです。

- (void)tearDownAVCapture
{
    [self.session stopRunning];
    for (AVCaptureOutput *output in self.session.outputs) {
          [self.session removeOutput:output];
     }
     for (AVCaptureInput *input in self.session.inputs) {
          [self.session removeInput:input];
     }
    self.stillImageOutput = nil;
    self.videoInput = nil;
    self.session = nil;
} 

- (void)viewWillAppear:(BOOL)animated
{
    [self setupAVCapture];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [self tearDownAVCapture];
}

これでメモリを消費しすぎるということはなくなります。