テーブルを編集モードにして、標準のマイナスボタンタップ、右端の削除ボタンをタップしたときに、
UITableView
のdeleteRowsAtIndexPaths: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のスクショにします。
ログ:
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ボタンタップ:(省略)
ログ:
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後でのスクショです。
MyTableView消えませんね。
メモ
-
deleteRowsAtIndexPaths
辺りがトリガーとなってリークを起こしているっぽいのは判っているのですが、回避方法が不明です(stackoverflowなどを彷徨い中)。 - テーブル行のハイフンボタン操作の後、
deleteRowsAtIndexPaths
を呼ぶとリークしますが、単純なUIButton
をタップされたときに、適当な行をdeleteRowsAtIndexPaths
してもリークにはなりません(ので削除UIを独自のものにしてしまえば回避できる可能性が高い...けどAppleっぽくなくて抵抗はありますね)。 - 駄目元でバグレポート提出済み。
- iOS 7.0 and ARC: UITableView never deallocated after rows animation(stackoverflow)と現象としては同じかな?
- 最悪同じUITableViewのインスタンスを使い回すというのも手かも...
- 回避策引き続き模索中...
最後に
何か基本的な見落としをしている気がしますので、何かお気付きの点がありましたらコメントをお願いいたします。