4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

(iOS8.1) UITableViewで行を削除するとUITableViewやセルがメモリリークする

Last updated at Posted at 2014-11-07

テーブルを編集モードにして、標準のマイナスボタンタップ、右端の削除ボタンをタップしたときに、
UITableViewdeleteRowsAtIndexPaths:withRowAnimation:メソッドを呼び出すと、後でUITableViewを破棄してもメモリ中に残ってしまう...というお話。

そんな馬鹿な、これは流石に自分のミスでしょ、と疑って掛かっているのですが、なかなか解決できません。
そろそろAppleのバグと言ってもいい頃合い。

検証コード

新規プロジェクト>シングルビュー

  • Xcode6.1
  • ARC
  • Objective-C
  • iOS8.1
ViewController.h
# import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@end
ViewController.m
# import "ViewController.h"

/// カスタムテーブルビュー
@interface MyTableView : UITableView
@end
@implementation MyTableView
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
    self = [super initWithFrame:frame style:style];
    if (self) {
        NSLog(@"MyTableView init");
    }
    return self;
}
- (void)dealloc {
    NSLog(@"MyTableView dealloc");
}
@end

/// カスタムテーブルビューセル
@interface MyTableViewCell : UITableViewCell
@end
@implementation MyTableViewCell
@end

/// テスト用ビューコントローラー
@implementation ViewController {
    UITableView *_tableView;
    NSMutableArray *_items;
}

- (void)loadView {
    [super loadView];

    [self addButtonInRect:CGRectMake(0, 20, 100, 44) title:@"Create Table" action:@selector(didTouchCreateTable)];
    [self addButtonInRect:CGRectMake(100, 20, 100, 44) title:@"Delete Table" action:@selector(didTouchDeleteTable)];
}

/// ボタンを生成します。
- (void)addButtonInRect:(CGRect)rect title:(NSString *)title action:(SEL)action {
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    btn.frame = rect;
    btn.layer.borderWidth = 1;
    btn.layer.borderColor = [UIColor blackColor].CGColor;
    [btn setTitle:title forState:UIControlStateNormal];
    [btn addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}

/// テーブルを生成します。
- (void)didTouchCreateTable {
    if (!_tableView) {
        NSLog(@"didTouchCreateTable");
        
        // アイテムをついでに作っておく
        _items = [NSMutableArray new];
        for (int i = 0; i < 6; i++) {
            [_items addObject:[NSString stringWithFormat:@"Item %d", i]];
        }
        
        // テーブル生成
        CGRect frame = [UIScreen mainScreen].applicationFrame;
        frame = CGRectInset(frame, 5, 5);
        frame.origin.y += 70;
        frame.size.height -= 70;
        _tableView = [[MyTableView alloc] initWithFrame:frame style:UITableViewStylePlain];
        _tableView.layer.borderWidth = 1;
        _tableView.layer.borderColor = [UIColor blackColor].CGColor;
        _tableView.dataSource = self;
        _tableView.delegate = self;
        [_tableView setEditing:YES animated:NO];
        [self.view addSubview:_tableView];
    }
}

/// テーブルを破棄します。
- (void)didTouchDeleteTable {
    if (_tableView) {
        NSLog(@"didTouchDeleteTable");
        
        [_tableView removeFromSuperview];
        _tableView = nil;
    }
}

# pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [_items count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"Cell";
    MyTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }
    cell.textLabel.text = _items[indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    
    switch (editingStyle) {
        case UITableViewCellEditingStyleDelete:
            NSLog(@"delete row %d", (int)indexPath.row);
            [_items removeObjectAtIndex:indexPath.row];
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        
        default:
            break;
    }
}

# pragma mark - UITableViewDelegate

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewCellEditingStyleDelete;
}

@end

実行結果

  • iPhone 6 Plus (iOS8.1)
  • iPhone 4s (iOS8.1) 
  • iPad3 (iOS8.1)

どれでも再現しますが、4sのスクショにします。

起動後
Screen Shot 2014-11-07 at 10.39.23.png

Create Tableボタンタップ:
Screen Shot 2014-11-07 at 10.39.25.png

Delete Tableボタンタップ:
Screen Shot 2014-11-07 at 10.39.27.png

ログ:

2014-11-07 10:39:22.819 TableTest[1018:34604] didTouchCreateTable
2014-11-07 10:39:22.824 TableTest[1018:34604] MyTableView init
2014-11-07 10:39:25.097 TableTest[1018:34604] didTouchDeleteTable
2014-11-07 10:39:25.101 TableTest[1018:34604] MyTableView dealloc

ちゃんとMyTableViewのdeallocが呼ばれていますね。


一旦アプリを再起動して、今度は行削除をやってみます。

起動:(省略)
Create Tableボタンタップ:(省略)

[Item 0]の赤丸マイナスボタンをタップ:
Screen Shot 2014-11-07 at 10.40.15.png

削除ボタンタップ:
Screen Shot 2014-11-07 at 10.40.18.png

Delete Tableボタンタップ:
Screen Shot 2014-11-07 at 10.40.23.png

ログ:

2014-11-07 10:40:08.325 TableTest[1064:35319] didTouchCreateTable
2014-11-07 10:40:08.331 TableTest[1064:35319] MyTableView init
2014-11-07 10:40:15.018 TableTest[1064:35319] delete row 0
2014-11-07 10:40:20.516 TableTest[1064:35319] didTouchDeleteTable

MyTableViewのdealloc来ません。

一応Profileしてみた

行削除>Delete Table後でのスクショです。

スクリーンショット 2014-11-07 11.02.52.png

MyTableView消えませんね。

ちなみにMyTableViewの参照カウンタは1でした。
スクリーンショット 2014-11-07 11.20.45.png

メモ

  • deleteRowsAtIndexPaths辺りがトリガーとなってリークを起こしているっぽいのは判っているのですが、回避方法が不明です(stackoverflowなどを彷徨い中)。
  • テーブル行のハイフンボタン操作の後、deleteRowsAtIndexPathsを呼ぶとリークしますが、単純なUIButtonをタップされたときに、適当な行をdeleteRowsAtIndexPathsしてもリークにはなりません(ので削除UIを独自のものにしてしまえば回避できる可能性が高い...けどAppleっぽくなくて抵抗はありますね)。
  • 駄目元でバグレポート提出済み。
  • iOS 7.0 and ARC: UITableView never deallocated after rows animation(stackoverflow)と現象としては同じかな?
  • 最悪同じUITableViewのインスタンスを使い回すというのも手かも...
  • 回避策引き続き模索中...

最後に

何か基本的な見落としをしている気がしますので、何かお気付きの点がありましたらコメントをお願いいたします。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?