26
24

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.

UITableViewから渡されるindexPathをそのままキーにしてはいけない

Last updated at Posted at 2015-09-12

UITableViewから渡されるindexPathをそのままキーにしてはいけない

UITableViewから渡されるindexPath、実は2種類ある。
NSIndexPathクラス以外に、UIMutableIndexPathというクラスが利用されることがある。

  • tableView:estimatedHeightForRowAtIndexPath:で渡されるindexPathはUIMutableIndexPathクラスのインスタンス
  • tableView:heightForRowAtIndexPath:tableView:cellForRowAtIndexPath:で渡されるindexPathはNSIndexPathクラスのインスタンス

つまり、「indexPathをそのままNSDictionaryのキーとして高さをキャッシュしておく」みたいな処理をしていると意図しない動きをすることがある。
具体的には、isEqualNOになることがあるため、入れたはずの値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クラスになっている
    01-1.png

  • tableView:cellForRowAtIndexPath:で渡されるindexPathはNSIndexPathクラス
    01-2.png

ログ

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;
}
26
24
4

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
26
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?