Posts iOS27 iOS27のFoundation.Dataのハッシュ計算

iOS27のFoundation.Dataのハッシュ計算

個人開発アプリのパフォーマンス劣化

個人のiPhoneをiOS27にアップデートした。 個人開発アプリの挙動を確かめてみると、ある画面でパフォーマンスが明確に劣化していた。 すでにXcode27にも対応済みで、iOS26の端末では実機でも快適に動作していた。

調べてみると、iOS27の端末では、FoudationフレームワークのData型のハッシュ値を計算するときの仕様が変わっている模様。 不具合の修正と合わせて、少し調べてみることにする。

そもそもなぜパフォーマンスが劣化したか

パフォーマンスが劣化していた原因は主に2点ある。

  • アプリにHashableな値を頻繁にハッシュするコードが含まれていた
  • Data型のハッシュ計算の仕様がiOS27から変化した

アプリにHashableな値を頻繁にハッシュするコードが含まれていた

アプリの潜在不具合として、Hashableな値を頻繁にハッシュするコードが含まれていた。

例としては以下のようなコードが該当する。 ListのIDとして\.selfを設定しており、値全体がIDとなってしまっている。

struct Item: Hashable {
    let id: UUID
    let title: String
    let image: Data   // 画像データ。数十〜数百 KB
}

struct ItemListView: View {
    let items: [Item]
    @State private var isScrolling = false

    var body: some View {
        List {
            // ID が値全体なので、List を更新するたびに全行の Item がハッシュされる。
            // その中で`image`プロパティも全バイト読まれる
            ForEach(items, id: \.self) { item in
                ItemRow(item: item, isScrolling: isScrolling)
            }
        }
        // スクロールの開始・終了のたびに body が評価し直され、List の更新が走る
        .onScrollPhaseChange { _, phase in
            isScrolling = phase.isScrolling
        }
    }
}

struct ItemRow: View {
    let item: Item
    let isScrolling: Bool

    var body: some View {
        HStack {
            if let image = UIImage(data: item.thumbnail) {
                Image(uiImage: image)
                    .resizable()
                    .frame(width: 44, height: 44)
            }
            Text(item.title)
        }
        .opacity(isScrolling ? 0.8 : 1)
    }
}

実際には、入れ子になっているstructの子の型がData型になっており、大きなデータサイズを持ち得るプロパティがあることに気づけなかった。 List表示では、一意なidさえ渡せればよいため、Item.idを利用するのが正しい。

Data型のハッシュ計算の仕様がiOS27から変化した

iOS18からこのアプリを運用しており、当初からHashableな値を頻繁にハッシュするコードが存在していたが、これまでは表面化されていなかった。 その要因は、iOS26までの仕様と、iOS27での変化にある。

iOS26までは、Data型は先頭80Bのみを利用してハッシュを生成していた。 iOS27では、600KBでも全体をハッシュするようになっていた。 おそらく、全体をハッシュする仕様に変更されたのだと考えられる。

これによって、これまで一定であったハッシュの計算コストが、画像データのファイルサイズに比例して増加するようになってしまった。

実際に、Data.hashValueの計算時間を測定すると、以下のようになった。

ファイルサイズ iOS26.5 iOS27.0
80B 0.06μs 0.06μs
1KB 0.05μs 0.06μs
60KB 0.05μs 32.1μs
600KB 0.05μs 319.63μs

ハッシュの衝突が正確に防げるようになっている改善であるが、頻繁にハッシュが計算されてしまう実装の場合、この仕様変更が大きなパフォーマンス劣化に繋がる可能性がある。

まとめ

iOS27移行では、Data型のハッシュの計算仕様が変更されており、データ全体を見るようになっている。 これによって、頻繁にハッシュ値を計算する処理では大きなパフォーマンスの劣化を招くことがあるため気をつける必要がある。

そして当然ではあるが、Listを始めとしたコンポーネントのidに対しては、必ずユニークなIDを渡すように注意したい