UITableViewCellの再利用でメモリリーク

UITableViewCellでハマってしまったので備忘録。

UITableViewCellは基本的に再利用します。アップルのドキュメントをそのまま引用すると

オブジェクトの割り当ては、パフォーマンスに影響します。特に、短期間に繰り返して割り当てを行わなければならない場合(ユーザがTable Viewをスクロールする場合な ど)は影響が大きくなります。セルを新規に割り当てる代わりに、セルを再利用すると、Table Viewのパフォーマンスを大幅に向上させることができます。

だからです。その方法としては下記のようになります。

- (UnitTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UnitTableViewCell *cell = (UnitTableViewCell*)[tableView
                                                   dequeueReusableCellWithIdentifier:@"UnitTableViewCell"];
    if (cell == nil) {
        NSLog(@"cellを新規に生成");
        UINib* nib = [UINib nibWithNibName:@"UnitTableViewCell" bundle:nil];
        NSArray* array = [nib instantiateWithOwner:nil options:nil];
        cell = [array objectAtIndex:0];
    }
    
    [cell setData:[_dataList objectAtIndex:indexPath.row]];
    
    return cell;
}

上の例だと、"setData"メソッドでそれぞれのセルの情報をセットしています。

しかしなぜか何度も再利用しているとメモリリークで落ちてしまうという現象が発生しました。。。
よくあるミス(他の方のブログをみて、または自分自身でも経験ありw)が、セルのidentifier名の割り当て漏れ。下画像赤枠部の箇所にはしっかり同名のidentifier名を入れてあげましょう。

f:id:ushisantoasobu:20130203143340p:plain

これについては、"cellを新規に生成"というログが毎度毎度出力されないことが確認できればOK。
(逆にいうと、ここの出力数でいくつのセルを生成してあとは再利用されているのかがわかります)


ただし今回のエントリで言いたいのはもっと凡ミスな話です。
というのは、上の"setData"メソッド内で、つまりはセルに情報をセットしてあげるたびに以下のようにある表示オブジェクトを毎度インスタンスとして生成していたからでした。

    _canvas = [[CanvasView alloc] initWithUnitData:data.unitData];
    [self addSubview:_canvas];

再利用しているのですからこれではどんどんメモリがたまっていきますね。なので以下のようにして再利用前にインスタンスを削除する、またはそのインスタンスの中身を変更するようなメソッドを用意してあげればいいです。

    if (_canvas) {
        [_canvas removeFromSuperview];
        _canvas = nil;
    }
    _canvas = [[CanvasViewController alloc] initWithUnitData:data.unitData];
    [self addSubview:_canvas];

いやー、凡ミスですねw
ただセルの中身が複雑だとついこのようなことがあるかもしれないので注意です >