UITableViewから渡されるindexPathをそのままキーにしてはいけない
UITableViewから渡されるindexPath、実は2種類ある。
NSIndexPathクラス以外に、UIMutableIndexPathというクラスが利用されることがある。
-
tableView:estimatedHeightForRowAtIndexPath:
で渡されるindexPathはUIMutableIndexPath
クラスのインスタンス -
tableView:heightForRowAtIndexPath:
やtableView:cellForRowAtIndexPath:
で渡されるindexPathはNSIndexPath
クラスのインスタンス
つまり、「indexPath
をそのままNSDictionaryのキーとして高さをキャッシュしておく」みたいな処理をしていると意図しない動きをすることがある。
具体的には、isEqual
がNO
になることがあるため、入れたはずの値objectForKey:
で取り出せない。
これ、コード的には問題なく見えるため、ハマると原因がわかりづらくなかなか厄介なやつ。日本語の情報とか見たことないし。。。
実際に確認
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.items count];
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (![self.heightDic objectForKey:indexPath]) {
// indexPathをキーにしてセルの高さをキャッシュに保存
NSLog(@"S: %ld, R: %ld に30.0をセット", indexPath.section, indexPath.row);
[self.heightDic setObject:@30 forKey:indexPath];
return 30.0f;
}
return 44.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 30.0f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil) {
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
}
// indexPathをキーにして、キャッシュが取り出せるか否かを確認
if ([self.heightDic objectForKey:indexPath]) {
NSLog(@"S: %ld, R: %ld はキャッシュあり", indexPath.section, indexPath.row);
cell.textLabel.text = @"キャッシュあり";
} else {
NSLog(@"S: %ld, R: %ld はキャッシュなし", indexPath.section, indexPath.row);
cell.textLabel.text = @"キャッシュなし";
}
return cell;
}
Xcodeで見てみた
-
tableView:estimatedHeightForRowAtIndexPath:
で渡されたindexPathはUIMutableIndexPath
クラスになっている
-
tableView:cellForRowAtIndexPath:
で渡されるindexPathはNSIndexPath
クラス
ログ
2015-09-12 02:34:17.689 IndexPathBehavior[66159:607] S: 0, R: 0 に30.0をセット
2015-09-12 02:34:17.689 IndexPathBehavior[66159:607] S: 0, R: 1 に30.0をセット
2015-09-12 02:34:17.690 IndexPathBehavior[66159:607] S: 0, R: 2 に30.0をセット
2015-09-12 02:34:17.690 IndexPathBehavior[66159:607] S: 0, R: 3 に30.0をセット
2015-09-12 02:34:17.690 IndexPathBehavior[66159:607] S: 0, R: 4 に30.0をセット
2015-09-12 02:34:17.691 IndexPathBehavior[66159:607] S: 0, R: 5 に30.0をセット
2015-09-12 02:34:17.691 IndexPathBehavior[66159:607] S: 0, R: 6 に30.0をセット
2015-09-12 02:34:17.691 IndexPathBehavior[66159:607] S: 0, R: 7 に30.0をセット
2015-09-12 02:34:17.692 IndexPathBehavior[66159:607] S: 0, R: 8 に30.0をセット
2015-09-12 02:34:17.692 IndexPathBehavior[66159:607] S: 0, R: 9 に30.0をセット
2015-09-12 02:34:17.692 IndexPathBehavior[66159:607] S: 0, R: 10 に30.0をセット
2015-09-12 02:34:17.692 IndexPathBehavior[66159:607] S: 0, R: 11 に30.0をセット
2015-09-12 02:34:17.693 IndexPathBehavior[66159:607] S: 0, R: 12 に30.0をセット
2015-09-12 02:34:17.693 IndexPathBehavior[66159:607] S: 0, R: 13 に30.0をセット
2015-09-12 02:34:17.693 IndexPathBehavior[66159:607] S: 0, R: 14 に30.0をセット
2015-09-12 02:34:17.694 IndexPathBehavior[66159:607] S: 0, R: 15 に30.0をセット
2015-09-12 02:34:17.694 IndexPathBehavior[66159:607] S: 0, R: 16 に30.0をセット
2015-09-12 02:34:17.694 IndexPathBehavior[66159:607] S: 0, R: 17 に30.0をセット
2015-09-12 02:34:17.695 IndexPathBehavior[66159:607] S: 0, R: 18 に30.0をセット
2015-09-12 02:34:17.695 IndexPathBehavior[66159:607] S: 0, R: 19 に30.0をセット
2015-09-12 02:34:17.695 IndexPathBehavior[66159:607] S: 0, R: 20 に30.0をセット
2015-09-12 02:34:17.696 IndexPathBehavior[66159:607] S: 0, R: 21 に30.0をセット
2015-09-12 02:34:17.696 IndexPathBehavior[66159:607] S: 0, R: 22 に30.0をセット
2015-09-12 02:34:17.696 IndexPathBehavior[66159:607] S: 0, R: 23 に30.0をセット
2015-09-12 02:34:17.697 IndexPathBehavior[66159:607] S: 0, R: 24 に30.0をセット
2015-09-12 02:34:17.697 IndexPathBehavior[66159:607] S: 0, R: 25 に30.0をセット
2015-09-12 02:34:17.704 IndexPathBehavior[66159:607] S: 0, R: 26 に30.0をセット
2015-09-12 02:34:17.704 IndexPathBehavior[66159:607] S: 0, R: 27 に30.0をセット
2015-09-12 02:34:17.705 IndexPathBehavior[66159:607] S: 0, R: 28 に30.0をセット
2015-09-12 02:34:17.705 IndexPathBehavior[66159:607] S: 0, R: 29 に30.0をセット
2015-09-12 02:34:17.706 IndexPathBehavior[66159:607] S: 0, R: 0 はキャッシュあり
2015-09-12 02:34:17.707 IndexPathBehavior[66159:607] S: 0, R: 1 はキャッシュあり
2015-09-12 02:34:17.708 IndexPathBehavior[66159:607] S: 0, R: 2 はキャッシュあり
2015-09-12 02:34:17.709 IndexPathBehavior[66159:607] S: 0, R: 3 はキャッシュあり
2015-09-12 02:34:17.709 IndexPathBehavior[66159:607] S: 0, R: 4 はキャッシュあり
2015-09-12 02:34:17.709 IndexPathBehavior[66159:607] S: 0, R: 5 はキャッシュあり
2015-09-12 02:34:17.710 IndexPathBehavior[66159:607] S: 0, R: 6 はキャッシュあり
2015-09-12 02:34:17.710 IndexPathBehavior[66159:607] S: 0, R: 7 はキャッシュあり
2015-09-12 02:34:17.725 IndexPathBehavior[66159:607] S: 0, R: 8 はキャッシュあり
2015-09-12 02:34:17.727 IndexPathBehavior[66159:607] S: 0, R: 9 はキャッシュあり
2015-09-12 02:34:17.728 IndexPathBehavior[66159:607] S: 0, R: 10 はキャッシュあり
2015-09-12 02:34:17.729 IndexPathBehavior[66159:607] S: 0, R: 11 はキャッシュあり
2015-09-12 02:34:17.730 IndexPathBehavior[66159:607] S: 0, R: 12 はキャッシュあり
2015-09-12 02:34:17.731 IndexPathBehavior[66159:607] S: 0, R: 13 はキャッシュあり
2015-09-12 02:34:17.732 IndexPathBehavior[66159:607] S: 0, R: 14 はキャッシュあり
2015-09-12 02:34:17.733 IndexPathBehavior[66159:607] S: 0, R: 15 はキャッシュあり
2015-09-12 02:34:21.369 IndexPathBehavior[66159:607] S: 0, R: 16 はキャッシュあり
2015-09-12 02:34:21.538 IndexPathBehavior[66159:607] S: 0, R: 17 はキャッシュあり
2015-09-12 02:34:21.779 IndexPathBehavior[66159:607] S: 0, R: 18 はキャッシュあり
2015-09-12 02:34:22.138 IndexPathBehavior[66159:607] S: 0, R: 19 はキャッシュあり
2015-09-12 02:34:22.154 IndexPathBehavior[66159:607] S: 0, R: 20 はキャッシュなし
2015-09-12 02:34:22.171 IndexPathBehavior[66159:607] S: 0, R: 21 はキャッシュなし
2015-09-12 02:34:22.188 IndexPathBehavior[66159:607] S: 0, R: 22 はキャッシュなし
2015-09-12 02:34:22.237 IndexPathBehavior[66159:607] S: 0, R: 23 はキャッシュなし
2015-09-12 02:34:22.505 IndexPathBehavior[66159:607] S: 0, R: 24 はキャッシュなし
2015-09-12 02:34:22.573 IndexPathBehavior[66159:607] S: 0, R: 25 はキャッシュなし
2015-09-12 02:34:22.641 IndexPathBehavior[66159:607] S: 0, R: 26 はキャッシュなし
2015-09-12 02:34:23.428 IndexPathBehavior[66159:607] S: 0, R: 27 はキャッシュなし
2015-09-12 02:34:23.554 IndexPathBehavior[66159:607] S: 0, R: 28 はキャッシュなし
2015-09-12 02:34:23.687 IndexPathBehavior[66159:607] S: 0, R: 29 はキャッシュなし
結果
最初に30セル分の高さをNSMutableDictionaryに格納している。
しかし、20セル目以降は[self.heightDic objectForKey:indexPath]
でキャッシュしたはずの値が取り出せず「キャッシュなし」と出力されている。
ちなみにこれはXcode6, iOS7で確認した挙動。iOS8ではすべて「キャッシュあり」が出力された。
OSバージョンで挙動が変わるのも、この問題の厄介な点の一つ。
対応方法
セルの高さを複雑な計算で動的に変えようとすると、パフォーマンスの面からキャッシュしておきたくなることは多い。
そんなときは受け取ったindexPath
から自分で作り直せば問題ない。
自分でNSIndexPath
クラスを指定することで、「入れたはずの値が取り出せない」ということがなくなる。
// 受け取ったindexPathと同じ位置を指すNSIndexPathを作成
NSIndexPath *key = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
追記(2015/09/12)
tomohisaotaさんのコメントを受けてcopy
を試したところ、iOS7でもすべて「キャッシュあり」が出力されました。
もちろん、usagimaruさんのコメントにあるようにNSStringとの相互変換メソッドを用意するのもいいと思います。
3つの中ではcopy
メソッドを利用する方法が一番無駄がなさそうですね。
tomohisaotaさん、usagimaruさん貴重なアドバイスありがとうございます。
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (![self.heightDic objectForKey:indexPath]) {
NSLog(@"S: %ld, R: %ld に30.0をセット", indexPath.section, indexPath.row);
- [self.heightDic setObject:@30 forKey:indexPath];
+ [self.heightDic setObject:@30 forKey:[indexPath copy]];
return 30.0f;
}
return 44.0f;
}