RORK LABEN
RAISE — Rork が1,500万ドルを調達しました。個人開発者がアプリを出すまでの距離を詰める方向に、資金が向かっている形ですENGINE — Rork Max は Claude Code と Opus 4.6 の組み合わせで動きます。複雑なアプリロジックや推論での差は、この構成に由来する部分が大きいと説明されていますCLOUDMAC — 生成された Swift はクラウド上の Mac 群でコンパイルされます。手元に Xcode を置かないまま、実機テストから App Store 提出まで届く導線ですAR — Rork Max は ARKit と LiDAR スキャンにネイティブ対応しています。3D オブジェクトの配置や空間コンピューティングを含むアプリが射程に入りますTRACTION — Rork Max の発表は X で800万表示を超え、2週間で年間収益が倍になったと報告されていますTRAFFIC — Rork は月間743,000訪問・成長率85%と報告されています。ノーコードのアプリ生成が試作の入口として定着しつつある数字ですRAISE — Rork が1,500万ドルを調達しました。個人開発者がアプリを出すまでの距離を詰める方向に、資金が向かっている形ですENGINE — Rork Max は Claude Code と Opus 4.6 の組み合わせで動きます。複雑なアプリロジックや推論での差は、この構成に由来する部分が大きいと説明されていますCLOUDMAC — 生成された Swift はクラウド上の Mac 群でコンパイルされます。手元に Xcode を置かないまま、実機テストから App Store 提出まで届く導線ですAR — Rork Max は ARKit と LiDAR スキャンにネイティブ対応しています。3D オブジェクトの配置や空間コンピューティングを含むアプリが射程に入りますTRACTION — Rork Max の発表は X で800万表示を超え、2週間で年間収益が倍になったと報告されていますTRAFFIC — Rork は月間743,000訪問・成長率85%と報告されています。ノーコードのアプリ生成が試作の入口として定着しつつある数字です
記事一覧/アプリ開発
アプリ開発/2026-08-03上級

1.10.0 の端末が強制アップデートに落ちる — 最小サポートバージョン判定を4実装で実測する

リモート設定で最小サポートバージョンを引き上げるゲートを、バージョン比較の4実装で実測しました。localeCompare が同値と言い切る箇所、fail-open の境界、審査中バージョンを閾値にした事故までを記録しています。

Expo155React Native217リモート設定2バージョン管理アプリ運用4

プレミアム記事

強制アップデートの画面が出たまま先へ進めない、という問い合わせが届いたのは、バージョン 1.10.0 を配信した数日後でした。

こちらの管理画面では最小サポートバージョンを 1.9.0 に設定しています。1.10.0 は当然それより新しい。落ちる理由がありません。

手元の端末で再現しませんでした。1.10.0 を入れた自分の iPhone は素通りします。再現したのは、ゲートの判定関数を切り出して Node で走らせたときでした。"1.10.0" < "1.9.0"true を返します。

文字列として比べれば "1" の次の文字が "1""9" の勝負になり、"1" < "9" で 1.10.0 の負けです。当たり前の話なのですが、9 系のマイナーバージョンを踏むまでの1年半、この条件は一度も偽になりませんでした。バグは静かに待っていました。

個人開発でアプリを6本運用していると、この手の「メジャーバージョンが二桁に届いた瞬間に初めて出る欠陥」を何度か踏みます。今回は判定の実装そのものを実測で選び直したので、その過程を残しておきます。

バージョン文字列を比べる方法は4通りあり、3つは壊れる

まず素朴に思いつく実装を並べます。文字列比較、localeCompare の numeric オプション、parseFloat、セグメントごとの数値比較の4つです。

const sgn = (n) => (n < 0 ? -1 : n > 0 ? 1 : 0);
 
// ① 文字列としてそのまま比較
const naive = (a, b) => sgn(a < b ? -1 : a > b ? 1 : 0);
 
// ② localeCompare の numeric オプション
const loc = (a, b) => sgn(a.localeCompare(b, undefined, { numeric: true }));
 
// ③ 小数として読む
const flt = (a, b) => sgn(parseFloat(a) - parseFloat(b));
 
// ④ ドット区切りを数値セグメントとして比較
function segs(a, b) {
  const A = a.split("."), B = b.split(".");
  const n = Math.max(A.length, B.length);
  for (let i = 0; i < n; i++) {
    const x = parseInt(A[i] ?? "0", 10) || 0;
    const y = parseInt(B[i] ?? "0", 10) || 0;
    if (x !== y) return sgn(x - y);
  }
  return 0;
}

これを、実運用で実際に現れるバージョン文字列の組み合わせに当てました。Node v22.22.3 での結果です。-1 は左が小さい、1 は左が大きい、0 は同値を意味します。

比較ペア① 文字列② localeCompare③ parseFloat④ セグメント
1.9.0 vs 1.10.01-11-1
1.0 vs 1.0.0-1-100
1.02.0 vs 1.2.0-10-10
2.0.0 vs 2.0.0.1-1-10-1
1.4.0 vs 1.41100
1.4.0-beta.1 vs 1.4.01101
10.0.0 vs 9.99.99-1111
1.2.10 vs 1.2.9-1101
3.0.0 vs 3.01100
1.10 vs 1.9-11-11

太字が期待と食い違った箇所です。①は12ペア中6つ、③は5つを取り違えます。

私が事故を起こしたのは①でした。10.0.0 vs 9.99.99 も同じ形で壊れます。メジャーが二桁へ届いた瞬間、そのアプリの全ユーザーが一斉に「古い」と判定されます。

③の parseFloat1.2.101.2 として読むため、パッチバージョンが丸ごと消えます。1.2.10 vs 1.2.9 が同値になる理由がこれです。二番目のドット以降が存在しないものとして扱われます。

localeCompare が「同じです」と言い切る二つの場所

②の localeCompare(b, undefined, { numeric: true }) は、①と③が壊れた数値まわりを全て正しく処理します。1.9.0 vs 1.10.010.0.0 vs 9.99.99 も期待どおりです。ここまで見ると、これで十分に思えます。

私も一度これで書き直しかけました。実際に表へ落としてから気づいた箇所が二つあります。

一つ目。1.02.01.2.00(同値) と判定します。numeric コレーションは数字の並びを数値として読むため、先頭のゼロが無視されます。ビルド番号を桁揃えして 1.02.0 のように振る運用をしていると、二つの別バージョンが区別できません。

二つ目のほうが実害があります。1.4.01.4 を比べると 1(左が大きい) を返します。セグメント数の違いが、そのまま大小の差として扱われます。

Android の versionName1.4 のような二桁表記を使っている場合、リモート設定の閾値を 1.4.0 と書いた瞬間、1.4 の端末は「閾値より古い」と判定されます。同じバージョンを指しているのに、表記のゆれだけで強制アップデート対象へ落ちます。

これは私の予想と逆でした。localeCompare を「数値として賢く比べてくれるもの」と捉えていたのですが、実際には文字列の照合順序に数値の読み方を足しただけで、バージョン番号のセマンティクスは知りません。セグメントが欠けたら短いほうが小さい、というのは辞書順の規則であってバージョン番号の規則ではありません。

④のセグメント比較はこの二つを正しく処理します。ただし 1.4.0-beta.1 vs 1.4.01 を返します。parseInt("0-beta")0 を返し、そのあと4番目のセグメントに 1 が残るため、ベータ版がリリース版より新しいと判定されます。プレリリースを配る運用があるなら、④も素のままでは使えません。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
バージョン比較の4実装を12ペアで実測した対照表。localeCompare が「1.02.0 と 1.2.0 は同値」「1.4.0 は 1.4 より大きい」と判定する箇所まで含めて掲載
16ケースを通した比較関数の完全なコードと、20万回で286.4ms という測定値
リモート設定の取得が遅れたときに fail-open へ倒す実装と、1,500ms のバジェットで切り替わる境界の実測
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

アプリ開発2026-07-14
壁紙を長押しして、プレビューと保存メニューを出す — Rork の Expo アプリで context menu を組む
壁紙ギャラリーのカードを長押ししても何も起きない。iOS利用者はプレビュー付きのメニューを期待します。Pressableだけでは届かない理由から、zeegoでのネイティブcontext menu実装、スクロールとの競合解消、保存・共有アクション、Android向けの自前フォールバックまで、動くコードでまとめます。
アプリ開発2026-07-07
高さの異なる画像を2列で美しく並べる—Rork の Expo アプリで壁紙ギャラリーを段組みにする
縦横比のばらつく画像を numColumns では美しく並べられない理由から、列の高さを見て振り分ける段組みアルゴリズム、FlashList の masonry での仮想化、依存を増やさない実装まで、壁紙ギャラリーを実コードで組みます。
アプリ開発2026-07-05
ワンタイムコードの入力欄をExpoで作る — SMS自動入力とセグメント表示を両立させる
6桁の確認コードを入れる画面は、見た目こそ単純ですが、SMSの自動入力・貼り付け・1文字ずつの削除まで含めると意外と作り込みが必要です。iOSとAndroidの自動入力を最初に押さえ、単一のTextInputで崩れにくいセグメント表示を組む実装をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →