<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Xcode · 仁才徳</title>
    <link>https://jared.lynskey.co.nz/ja/tags/xcode/</link>
    <description>ソフトウェアエンジニアリングと開発組織のリーダーシップについて。評価、採用とチームの拡大、CI/CD、DevOps モニタリング、エージェンティック AI。ソウルより。</description>
    <generator>Hugo</generator>
    <language>ja</language>
    <copyright>© 2026 仁才徳</copyright>
    <lastBuildDate>Tue, 27 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ja/tags/xcode/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>XcodeでiOSアプリをデバッグする方法：ブレークポイント、LLDB、Instruments、シミュレータと実機</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2026/2026-02-26-debugging-ios-mobile-apps/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2026/2026-02-26-debugging-ios-mobile-apps/</guid>
      <pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>iOS</category>
      <category>Xcode</category>
      <category>Debugging</category>
      <category>Swift</category>
      <category>iOS Simulator</category>
      <category>Mobile Development</category>
      <description>XcodeでiOSアプリをデバッグする方法をまとめました。条件付き・シンボリックブレークポイント、覚えておきたいLLDBコマンド、ビューデバッガとメモリグラフ、Instruments、サニタイザー、クラッシュログ、そしてシミュレータでは見つからず実機で初めて分かるバグまで。</description>
      <content:encoded><![CDATA[<p>XcodeでiOSアプリをデバッグするなら、まず例外ブレークポイントを追加して、クラッシュしたときに例外がスローされた行で止まるようにします。次にバグを再現し、LLDBコンソールの <code>po</code> で状態を確かめます。あとは症状に合わせてツールを選ぶだけです。レイアウトの崩れにはビューデバッガ、リークや遅さにはメモリグラフとInstruments、たまにしか起きないクラッシュにはAddress SanitizerとThread Sanitizer。そして最後は必ず実機で確認します。シミュレータでは表に出てこない種類のバグがあるからです。</p>
<p>デバッグには本当に時間を取られます。誰でもそうだと思います。最近は自分のアプリのバグ追いでXcodeとシミュレータにこもる日が多く、そのたびに思うのは、Xcodeにはほとんど誰も触らない強力なデバッグ機能が山ほどある、ということです。この記事では、長年かけて落ち着いた自分のデバッグ手順のうち、実際に時間の節約になっている部分をまとめます。</p>

<h2 class="relative group">ブレークポイントは思っているより強力
    <div id="ブレークポイントは思っているより強力" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%96%e3%83%ac%e3%83%bc%e3%82%af%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88%e3%81%af%e6%80%9d%e3%81%a3%e3%81%a6%e3%81%84%e3%82%8b%e3%82%88%e3%82%8a%e5%bc%b7%e5%8a%9b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>ほとんどの人は、ガターをクリックしてブレークポイントを置いて終わりです。でも右クリックしてみると、デバッグのやり方が変わるくらいのオプションが出てきます。</p>

<h3 class="relative group">条件付きブレークポイント
    <div id="条件付きブレークポイント" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%9d%a1%e4%bb%b6%e4%bb%98%e3%81%8d%e3%83%96%e3%83%ac%e3%83%bc%e3%82%af%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>1000件を処理するループで、347番目だけがおかしいとします。346回もブレークポイントで止まるのは時間の無駄です。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mf">0.</span><span class="p">.&lt;</span><span class="n">users</span><span class="p">.</span><span class="bp">count</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">processUser</span><span class="p">(</span><span class="n">users</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>  <span class="c1">// ここにブレークポイントを設定、条件: i == 347</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>ブレークポイントを右クリックして条件に <code>i == 347</code> を入れれば、Xcodeは条件が真のときだけ止まります。<code>userId == &quot;abc123&quot;</code> や <code>error != nil</code> のような条件でも同じです。</p>

<h3 class="relative group">シンボリックブレークポイント
    <div id="シンボリックブレークポイント" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%83%b3%e3%83%9c%e3%83%aa%e3%83%83%e3%82%af%e3%83%96%e3%83%ac%e3%83%bc%e3%82%af%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>メソッドがどこから呼ばれているのか分からないときに使います。ブレークポイントナビゲータ（Cmd+8）で「+」ボタンをクリックし、「Symbolic Breakpoint」を選びます。</p>
<p>どのビューコントローラがいつロードされるか全部見たければ、シンボルを <code>viewDidLoad</code> にします。Xcodeがすべての呼び出しで止まります。うるさすぎるなら <code>MyViewController.viewDidLoad</code> のように絞り込みましょう。</p>
<p>私が常設しているのはこの3つです。</p>
<ul>
<li><code>UIViewAlertForUnsatisfiableConstraints</code>：Auto Layoutの競合をその場で捕まえる</li>
<li><code>objc_exception_throw</code>：クラッシュする前にObjective-Cの例外で止まる</li>
<li><code>malloc_error_break</code>：メモリ割り当ての問題を見つける</li>
</ul>

<h3 class="relative group">例外ブレークポイント
    <div id="例外ブレークポイント" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%be%8b%e5%a4%96%e3%83%96%e3%83%ac%e3%83%bc%e3%82%af%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>新しいプロジェクトで最初にやるべき設定だと思います。ブレークポイントナビゲータで「+」→「Exception Breakpoint」を追加しておくと、アプリが落ちた後ではなく、何かがスローされた瞬間にXcodeが止まってくれます。</p>
<p>コンソールに謎のクラッシュとしてしか出てこないnilアンラップの問題を、これで何度も捕まえてきました。</p>

<h3 class="relative group">ブレークポイントアクション
    <div id="ブレークポイントアクション" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%96%e3%83%ac%e3%83%bc%e3%82%af%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88%e3%82%a2%e3%82%af%e3%82%b7%e3%83%a7%e3%83%b3" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>ブレークポイントは実行を止めなくてもいいんです。ブレークポイントを編集して「Add Action」をクリックすると、こんなことができます。</p>
<ul>
<li>止めずに変数を出力する：<code>&quot;User count: @(users.count)@&quot;</code></li>
<li>LLDBコマンドを自動実行する</li>
<li>シェルスクリプトを実行する</li>
<li>音を鳴らす（めったに通らないコードパスの確認に使っています）</li>
</ul>
<p>「Automatically continue after evaluating actions」にチェックを入れれば、ブレークポイントはフローを止めないロガーになります。</p>

<h2 class="relative group">LLDB：実際に使うコンソールコマンド
    <div id="lldb実際に使うコンソールコマンド" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lldb%e5%ae%9f%e9%9a%9b%e3%81%ab%e4%bd%bf%e3%81%86%e3%82%b3%e3%83%b3%e3%82%bd%e3%83%bc%e3%83%ab%e3%82%b3%e3%83%9e%e3%83%b3%e3%83%89" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>ブレークポイントで止まったとき、下のコンソールはクラッシュログを眺めるだけの場所ではありません。あれはLLDBで、本当に便利です。</p>

<h3 class="relative group">基本
    <div id="基本" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%9f%ba%e6%9c%ac" aria-label="アンカー">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) po user
▿ User
  - id: &#34;123&#34;
  - name: &#34;John Doe&#34;
  - email: &#34;john@example.com&#34;</code></pre></div>
<p><code>po</code> はオブジェクトを読みやすい形で出力します。私のLLDB利用の9割はこれです。</p>
<p>もう少し詳しく見たいときは <code>p</code> を使います。</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) p user.name
(String) $R0 = &#34;John Doe&#34;</code></pre></div>
<p>ローカル変数を一度に全部見るなら、こうです。</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) frame variable</code></pre></div>

<h3 class="relative group">実行中に値を変える
    <div id="実行中に値を変える" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%ae%9f%e8%a1%8c%e4%b8%ad%e3%81%ab%e5%80%a4%e3%82%92%e5%a4%89%e3%81%88%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>LLDBの本領はここです。再コンパイルせずに変数を書き換えられます。</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) expr user.name = &#34;Jane Doe&#34;
(lldb) expr index = 0</code></pre></div>
<p>バグを見つけて、リビルドせずに修正を試したい。そんなときは変数を変えてそのまま続行すればいいわけです。エッジケースを絞り込むとき、いつもこれで値を差し替えています。</p>
<p>メソッドの呼び出しもできます。</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) po self.refreshUI()
(lldb) expr navigationController?.popViewController(animated: true)</code></pre></div>

<h3 class="relative group">ナビゲーション
    <div id="ナビゲーション" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%8a%e3%83%93%e3%82%b2%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3" aria-label="アンカー">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) bt              # コールスタックを表示
(lldb) frame select 3  # 別のスタックフレームにジャンプ
(lldb) continue        # 実行を続ける（または単に &#39;c&#39;）
(lldb) n               # ステップオーバー（次の行）
(lldb) s               # 関数にステップイン</code></pre></div>

<h3 class="relative group">ウォッチポイント
    <div id="ウォッチポイント" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%a6%e3%82%a9%e3%83%83%e3%83%81%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>変数がいつ変わるのか知りたければウォッチポイントです。</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) watchpoint set variable user.isLoggedIn</code></pre></div>
<p>コードのどこから変更されても、その瞬間に実行が止まります。想定外の状態変更を追うときには手放せません。</p>

<h2 class="relative group">Console.app：隠れたデバッグツール
    <div id="consoleapp隠れたデバッグツール" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#consoleapp%e9%9a%a0%e3%82%8c%e3%81%9f%e3%83%87%e3%83%90%e3%83%83%e3%82%b0%e3%83%84%e3%83%bc%e3%83%ab" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>Xcodeのコンソールで十分な場面は多いですが、システムログもクラッシュレポートも全部見たい、となったらConsole.appの出番です。</p>
<p>OSLogを使っていれば（使うべきです）、Console.appでログを検索したりフィルタしたりできます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">import</span> <span class="nc">os</span><span class="p">.</span><span class="nc">log</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">logger</span> <span class="p">=</span> <span class="n">Logger</span><span class="p">(</span><span class="n">subsystem</span><span class="p">:</span> <span class="s">&#34;com.example.app&#34;</span><span class="p">,</span> <span class="n">category</span><span class="p">:</span> <span class="s">&#34;networking&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">&#34;Starting API request&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">&#34;Request URL: </span><span class="si">\(</span><span class="n">url</span><span class="p">.</span><span class="n">absoluteString</span><span class="si">)</span><span class="s">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">error</span><span class="p">(</span><span class="s">&#34;Failed to decode: </span><span class="si">\(</span><span class="n">error</span><span class="p">.</span><span class="n">localizedDescription</span><span class="si">)</span><span class="s">&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Console.app側の手順はこうです。</p>
<ol>
<li>シミュレータか接続中のデバイスを選ぶ</li>
<li>サブシステムで絞る：<code>subsystem:com.example.app</code></li>
<li>レベルで絞る：<code>subsystem:com.example.app AND level:error</code></li>
</ol>
<p>よく使うフィルタ述語は保存できます。私はネットワークリクエスト用、データベース操作用、エラーと警告だけを出す用の3つを持っています。</p>

<h3 class="relative group">print()ではなくOSLogを使う理由
    <div id="printではなくoslogを使う理由" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#print%e3%81%a7%e3%81%af%e3%81%aa%e3%81%8foslog%e3%82%92%e4%bd%bf%e3%81%86%e7%90%86%e7%94%b1" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>OSLogは、<code>print()</code> をあちこちに撒くよりずっとましです。構造化されているのでレベルやカテゴリで絞り込めますし、ログ自体が最適化されていてアプリを遅くしません。本番では機密データを自動でマスクしてくれて、アプリが落ちた後もログは残ります。</p>
<p>とっさの確認には <code>print()</code> で構いません。後で見返す可能性があるものはOSLogに残しましょう。</p>

<h3 class="relative group">Console.appの高度なフィルタリング
    <div id="consoleappの高度なフィルタリング" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#consoleapp%e3%81%ae%e9%ab%98%e5%ba%a6%e3%81%aa%e3%83%95%e3%82%a3%e3%83%ab%e3%82%bf%e3%83%aa%e3%83%b3%e3%82%b0" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Console.appはかなり凝った述語クエリに対応しています。私がよく使うのはこのあたりです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># アプリ内のすべてのエラーと障害
</span></span><span class="line"><span class="cl">subsystem:com.example.app AND (level:error OR level:fault)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 失敗したネットワークリクエスト
</span></span><span class="line"><span class="cl">subsystem:com.example.app AND category:networking AND eventMessage CONTAINS &#34;failed&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 過去5分間のすべて
</span></span><span class="line"><span class="cl">subsystem:com.example.app AND timestamp &gt;= now(-5m)</span></span></code></pre></div></div>
<p>ログはエクスポートしてバグレポートに添付することもできます。Xcodeのコンソールからコピペするよりずっと楽です。</p>

<h2 class="relative group">ビューデバッグ
    <div id="ビューデバッグ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%93%e3%83%a5%e3%83%bc%e3%83%87%e3%83%90%e3%83%83%e3%82%b0" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>UIが壊れているのに原因が分からないとき、いちばん早いのはビューデバッガです。</p>
<p>アプリを実行して壊れた画面まで進み、Xcodeのデバッグバーで「Debug View Hierarchy」をクリックします（またはCmd+Shift+D）。ビュー階層全体が3Dで分解表示されます。</p>
<p>回してみると、いろいろ見えてきます。</p>
<ul>
<li>他のビューの裏でレンダリングされているビュー</li>
<li>画面のはるか外にいるビュー</li>
<li>サイズがゼロのビュー</li>
<li>選択したビューの制約チェーン全体</li>
</ul>
<p>私はこれで、タップイベントを吸い込んでいた透明なボタンや、0x0でレンダリングされていたラベル、いつの間にか幅10,000ピクセルになっていた画像を見つけたことがあります。</p>

<h3 class="relative group">Auto Layoutのデバッグ
    <div id="auto-layoutのデバッグ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#auto-layout%e3%81%ae%e3%83%87%e3%83%90%e3%83%83%e3%82%b0" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>ビュー階層に紫の警告アイコンが出ていたら制約の競合です。クリックすると、どの制約同士がぶつかっているのかXcodeが正確に教えてくれます。</p>
<p>制約には識別子を付けておきましょう。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="n">heightConstraint</span><span class="p">.</span><span class="n">identifier</span> <span class="p">=</span> <span class="s">&#34;ProfileImageHeight&#34;</span></span></span></code></pre></div></div>
<p>制約が壊れたとき、エラーにメモリアドレスではなく <code>&quot;ProfileImageHeight&quot;</code> が出るようになります。ログの読みやすさが段違いです。</p>
<p>LLDBから制約ツリーを出力することもできます。</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-lldb" data-lang="lldb">(lldb) po view.hasAmbiguousLayout
(lldb) po view._autolayoutTrace()</code></pre></div>

<h2 class="relative group">時間を節約してくれるシミュレータ機能
    <div id="時間を節約してくれるシミュレータ機能" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%99%82%e9%96%93%e3%82%92%e7%af%80%e7%b4%84%e3%81%97%e3%81%a6%e3%81%8f%e3%82%8c%e3%82%8b%e3%82%b7%e3%83%9f%e3%83%a5%e3%83%ac%e3%83%bc%e3%82%bf%e6%a9%9f%e8%83%bd" aria-label="アンカー">#</a>
    </span>
    
</h2>

<h3 class="relative group">スローアニメーション
    <div id="スローアニメーション" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b9%e3%83%ad%e3%83%bc%e3%82%a2%e3%83%8b%e3%83%a1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Debug → Slow Animations で、すべてが1/10の速度になります。トランジション中に何が起きているのか、アニメーションがなぜ変に見えるのかを確かめるのにちょうどいいです。</p>

<h3 class="relative group">メモリ警告のシミュレート
    <div id="メモリ警告のシミュレート" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a1%e3%83%a2%e3%83%aa%e8%ad%a6%e5%91%8a%e3%81%ae%e3%82%b7%e3%83%9f%e3%83%a5%e3%83%ac%e3%83%bc%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Debug → Simulate Memory Warning。メモリ不足のときのアプリの挙動をテストできます。これで画像キャッシュのバグをいくつも見つけました。メモリ警告が来るまでは普通に動いているのに、来た途端に画像が全部消えるやつです。</p>

<h3 class="relative group">Network Link Conditioner
    <div id="network-link-conditioner" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#network-link-conditioner" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Xcode → Open Developer Tools → Network Link Conditioner</p>
<p>3GやLTE、高パケットロス、完全オフラインをシミュレートできます。WiFiでは平気なAPIリクエストがモバイル回線でタイムアウトする、という経験があるなら、原因はこれで再現できます。</p>
<p>私は遅延500ms・パケットロス10%の「Bad Network」プロファイルを常備しています。速いオフィスWiFiでは絶対に出ない、タイムアウトのバグやローディング表示の問題が引っかかります。</p>

<h3 class="relative group">ステータスバーのオーバーライド
    <div id="ステータスバーのオーバーライド" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b9%e3%83%86%e3%83%bc%e3%82%bf%e3%82%b9%e3%83%90%e3%83%bc%e3%81%ae%e3%82%aa%e3%83%bc%e3%83%90%e3%83%bc%e3%83%a9%e3%82%a4%e3%83%89" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>シミュレータのステータスバーを右クリックすると、時刻、バッテリー残量、電波強度、キャリアを上書きできます。スクリーンショットを揃えたいときや、バッテリー残量ごとのUI確認に便利です。</p>

<h3 class="relative group">位置情報のシミュレーション
    <div id="位置情報のシミュレーション" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%bd%8d%e7%bd%ae%e6%83%85%e5%a0%b1%e3%81%ae%e3%82%b7%e3%83%9f%e3%83%a5%e3%83%ac%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Debug → Location で、机から動かずにいろいろな場所を再現できます。任意の座標、街歩き、高速道路の走行まで一通り揃っています。位置情報を使う機能のテストや、特定の地域でしか起きない不具合の調査で重宝します。</p>

<h2 class="relative group">メモリデバッグ
    <div id="メモリデバッグ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a1%e3%83%a2%e3%83%aa%e3%83%87%e3%83%90%e3%83%83%e3%82%b0" aria-label="アンカー">#</a>
    </span>
    
</h2>

<h3 class="relative group">Debug Memory Graph
    <div id="debug-memory-graph" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#debug-memory-graph" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Debug Memory Graphボタン（Cmd+Shift+M）を押すと、メモリ上のすべてのオブジェクトとその参照関係、そして肝心の「どれがリークしているか」をXcodeが見せてくれます。</p>
<p>紫のビックリマークがリークです。クリックすると保持サイクルが見えます。</p>
<p>いちばんよく見かけるリークはこれです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">ViewController</span><span class="p">:</span> <span class="n">UIViewController</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nv">onComplete</span><span class="p">:</span> <span class="p">(()</span> <span class="p">-&gt;</span> <span class="nb">Void</span><span class="p">)?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">setupHandler</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">onComplete</span> <span class="p">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kc">self</span><span class="p">.</span><span class="n">dismiss</span><span class="p">(</span><span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>  <span class="c1">// ❌ selfを強く参照</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>weak selfで直します。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="n">onComplete</span> <span class="p">=</span> <span class="p">{</span> <span class="p">[</span><span class="kr">weak</span> <span class="kc">self</span><span class="p">]</span> <span class="k">in</span>
</span></span><span class="line"><span class="cl">    <span class="kc">self</span><span class="p">?.</span><span class="n">dismiss</span><span class="p">(</span><span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>  <span class="c1">// ✓ 保持サイクルなし</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>デリゲートもweakにしておきます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kr">weak</span> <span class="kd">var</span> <span class="nv">delegate</span><span class="p">:</span> <span class="n">ManagerDelegate</span><span class="p">?</span>  <span class="c1">// 単なる &#39;var&#39; ではなく</span></span></span></code></pre></div></div>

<h3 class="relative group">Instruments
    <div id="instruments" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#instruments" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>本腰を入れてメモリを調べるならInstrumentsです（Product → Profile、またはCmd+I）。</p>
<p>Allocationsを使うと、オブジェクトの割り当てすべて、時間経過に伴うメモリの増え方、どのクラスがメモリを食っているか、割り当てが起きた場所のスタックトレースまで見えます。</p>
<p>私が探すのはだいたい、時間に比例して増え続けるメモリ（ほぼリークです）、想定外に大きい割り当て（画像1000枚をうっかり一気に読んでいないか？）、解放されているはずなのに残っているオブジェクト、この3つです。</p>
<p>操作の前後で世代をマークして（小さな旗のボタン）、残ってはいけないものが残っていないか確認しましょう。</p>

<h2 class="relative group">パフォーマンスプロファイリング
    <div id="パフォーマンスプロファイリング" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%91%e3%83%95%e3%82%a9%e3%83%bc%e3%83%9e%e3%83%b3%e3%82%b9%e3%83%97%e3%83%ad%e3%83%95%e3%82%a1%e3%82%a4%e3%83%aa%e3%83%b3%e3%82%b0" aria-label="アンカー">#</a>
    </span>
    
</h2>

<h3 class="relative group">Time Profiler
    <div id="time-profiler" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#time-profiler" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Product → Profile → Time Profiler。アプリで重い操作をしながら記録して、コールツリーを読みます。</p>
<p>「Self Weight」でソートするとボトルネックが見つかります。ある関数のself weightが40%なら、時間はそこに消えています。</p>
<p>以前、JSONのパース関数が毎秒1000回呼ばれているのを見つけたことがあります。バックグラウンドキューに移したら、UIのカクつきが止まりました。</p>

<h3 class="relative group">Main Thread Checker
    <div id="main-thread-checker" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#main-thread-checker" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>バックグラウンドスレッドでのUI更新は、Xcodeが自動で捕まえてくれます。こんな表示が出たら、</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Main Thread Checker: UI API called on a background thread: -[UILabel setText:]</span></span></code></pre></div></div>
<p>こういうことをやってしまっています。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="n">URLSession</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
</span></span><span class="line"><span class="cl">    <span class="kc">self</span><span class="p">.</span><span class="n">label</span><span class="p">.</span><span class="n">text</span> <span class="p">=</span> <span class="s">&#34;Loaded&#34;</span>  <span class="c1">// ❌ クラッシュ！</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>直すとこうなります。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="n">URLSession</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
</span></span><span class="line"><span class="cl">    <span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kc">self</span><span class="p">.</span><span class="n">label</span><span class="p">.</span><span class="n">text</span> <span class="p">=</span> <span class="s">&#34;Loaded&#34;</span>  <span class="c1">// ✓</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>

<h2 class="relative group">ランタイム診断
    <div id="ランタイム診断" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a9%e3%83%b3%e3%82%bf%e3%82%a4%e3%83%a0%e8%a8%ba%e6%96%ad" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>Edit Scheme → Run → Diagnostics。ここに並んでいるチェックボックスは、手作業では絶対に見つからないバグを捕まえてくれます。</p>

<h3 class="relative group">Address Sanitizer
    <div id="address-sanitizer" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#address-sanitizer" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>メモリ破壊を見つけます。use-after-free、バッファオーバーフロー、メモリリーク。アプリは2〜3倍遅くなりますが、他の方法ではほぼ追跡不可能なバグを捕まえてくれます。たまにしか起きないクラッシュを追うときにオンにします。</p>

<h3 class="relative group">Thread Sanitizer
    <div id="thread-sanitizer" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thread-sanitizer" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>データ競合とスレッド絡みの問題を捕まえます。有効にしてアプリを普通に触っているだけで、競合状態があればThread Sanitizerが見つけてくれます。</p>
<p>Address SanitizerとThread Sanitizerは同時に使えないので、妙なクラッシュを追うときは交互に切り替えています。</p>

<h3 class="relative group">Zombie Objects
    <div id="zombie-objects" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#zombie-objects" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>解放済みオブジェクトへのメッセージを捕まえます。有効にしておくと、解放されたメモリに触った瞬間をXcodeが正確に教えてくれます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">*** -[MyViewController viewDidLoad]: message sent to deallocated instance 0x600001234000</span></span></code></pre></div></div>
<p>つけっぱなしは禁物です。オブジェクトが解放されなくなるので、メモリは増える一方になります。ただ、特定のuse-after-freeバグを突き止めるには最高です。</p>

<h2 class="relative group">よくあるデバッグシナリオ
    <div id="よくあるデバッグシナリオ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%88%e3%81%8f%e3%81%82%e3%82%8b%e3%83%87%e3%83%90%e3%83%83%e3%82%b0%e3%82%b7%e3%83%8a%e3%83%aa%e3%82%aa" aria-label="アンカー">#</a>
    </span>
    
</h2>

<h3 class="relative group">起動時にクラッシュする
    <div id="起動時にクラッシュする" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e8%b5%b7%e5%8b%95%e6%99%82%e3%81%ab%e3%82%af%e3%83%a9%e3%83%83%e3%82%b7%e3%83%a5%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>まず例外ブレークポイントを追加します。たいていはこれで捕まります。</p>
<p>だめなら、Info.plistのキー漏れ、初期化コードでの強制アンラップ、依存性注入の失敗あたりを疑います。</p>

<h3 class="relative group">UIが更新されない
    <div id="uiが更新されない" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ui%e3%81%8c%e6%9b%b4%e6%96%b0%e3%81%95%e3%82%8c%e3%81%aa%e3%81%84" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>毎回、原因はこのどれかです。</p>
<ol>
<li>更新がメインスレッドで行われていない</li>
<li>アウトレットがつながっていない</li>
<li>ビューがそもそも表示されていない（LLDBで <code>po view.window</code> を確認。nilなら階層に入っていません）</li>
</ol>
<p>テーブルビューやコレクションビューなら、<code>reloadData()</code> の呼び忘れです。</p>

<h3 class="relative group">メモリ警告でアプリが落ちる
    <div id="メモリ警告でアプリが落ちる" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a1%e3%83%a2%e3%83%aa%e8%ad%a6%e5%91%8a%e3%81%a7%e3%82%a2%e3%83%97%e3%83%aa%e3%81%8c%e8%90%bd%e3%81%a1%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Instruments Allocationsで何がメモリを掴んでいるか確認します。たいていは、キャッシュから解放されない画像、解放されないビューコントローラ（保持サイクル）、際限なく育つ配列や辞書のどれかです。</p>
<p>シミュレータでメモリ警告をシミュレートして、クリーンアップ処理をテストしておきましょう。</p>

<h3 class="relative group">原因不明のクラッシュ
    <div id="原因不明のクラッシュ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%8e%9f%e5%9b%a0%e4%b8%8d%e6%98%8e%e3%81%ae%e3%82%af%e3%83%a9%e3%83%83%e3%82%b7%e3%83%a5" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>サニタイザーを全部オンにします。間欠的ならたぶんスレッドの問題なのでThread Sanitizerで、システムフレームワーク内で落ちているならたぶんメモリ破壊なのでAddress Sanitizerで走らせます。</p>

<h3 class="relative group">再現できないクラッシュ
    <div id="再現できないクラッシュ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%86%8d%e7%8f%be%e3%81%a7%e3%81%8d%e3%81%aa%e3%81%84%e3%82%af%e3%83%a9%e3%83%83%e3%82%b7%e3%83%a5" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>クラッシュが他人のスマホで起きた場合でも、レポートはたいていどこかに残っています。TestFlightやApp Storeのビルドなら、XcodeのOrganizer（Window → Organizer → Crashes）に、解析データの共有をオンにしているユーザーのクラッシュレポートが集まってきます。ビルドと一緒にdSYMもアップロードしてあれば、シンボル化された状態で見られます。テスターの端末なら、レポートは端末そのものの「設定 → プライバシーとセキュリティ → 解析および改善 → 解析データ」に、アプリ名で始まる <code>.ips</code> ファイルとして残っているので、AirDropで送ってもらいましょう。まずはクラッシュしたスレッドのスタックトレースを読み、そこから上のシナリオを順に当たっていきます。</p>

<h3 class="relative group">シミュレータでOTA更新をデバッグする
    <div id="シミュレータでota更新をデバッグする" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%83%9f%e3%83%a5%e3%83%ac%e3%83%bc%e3%82%bf%e3%81%a7ota%e6%9b%b4%e6%96%b0%e3%82%92%e3%83%87%e3%83%90%e3%83%83%e3%82%b0%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>私のアプリはOTA（Over-the-Air）アップデートを配信しているのですが、このフロー全体のデバッグにはシミュレータがいちばん手軽です。</p>
<p>まずネットワークトラフィックを見ます。アプリのサブシステムで絞ったConsole.appに、マニフェストのリクエストとレスポンスがリアルタイムで流れてくるので、どのヘッダーを送っていてサーバーが何を返しているかが正確に分かります。</p>
<p>次に、わざとネットワークを悪くします。Network Link Conditionerで、遅い回線やパケットロスのときに更新フローがどう振る舞うかを確かめます。アプリは固まらないか。ローディング表示は出るか。ちゃんとフォールバックするか。</p>
<p>更新チェックの周辺にはログを仕込みます。チェック開始時、更新があったとき、ダウンロード開始時、新しいバンドルのロード時、それぞれで記録しています。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">import</span> <span class="nc">os</span><span class="p">.</span><span class="nc">log</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">logger</span> <span class="p">=</span> <span class="n">Logger</span><span class="p">(</span><span class="n">subsystem</span><span class="p">:</span> <span class="s">&#34;com.example.app&#34;</span><span class="p">,</span> <span class="n">category</span><span class="p">:</span> <span class="s">&#34;updates&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">&#34;Checking for updates...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">update</span> <span class="p">=</span> <span class="k">try</span> <span class="n">await</span> <span class="n">Updates</span><span class="p">.</span><span class="n">checkForUpdateAsync</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">&#34;Update available: </span><span class="si">\(</span><span class="n">update</span><span class="p">.</span><span class="n">isAvailable</span><span class="si">)</span><span class="s">&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>最後に、マニフェストのエンドポイント単体もテストしておくと安心です。アプリが送るのと同じヘッダーでcurlできます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -H <span class="s2">&#34;expo-protocol-version: 1&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">     -H <span class="s2">&#34;expo-platform: ios&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">     -H <span class="s2">&#34;expo-runtime-version: 1.0.0&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">     https://updates.example.com/manifest</span></span></code></pre></div></div>
<p>クライアント側を掘り始める前に、サーバーが正しいデータを返しているかをここで確認できます。</p>

<h2 class="relative group">サードパーティツール
    <div id="サードパーティツール" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b5%e3%83%bc%e3%83%89%e3%83%91%e3%83%bc%e3%83%86%e3%82%a3%e3%83%84%e3%83%bc%e3%83%ab" aria-label="アンカー">#</a>
    </span>
    
</h2>

<h3 class="relative group">Charles Proxy / Proxyman
    <div id="charles-proxy--proxyman" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#charles-proxy--proxyman" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>ネットワークトラフィックの全表示、リクエストとレスポンスの改変、エラー条件のテスト。APIデバッグには欠かせません。</p>
<p>ProxymanはCharlesよりUIがよく、macOSに馴染む感じがします。去年乗り換えて、戻る気はありません。</p>

<h3 class="relative group">Reveal
    <div id="reveal" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#reveal" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Xcodeのビューデバッガに似ていますが、もっと強力です。Xcodeにつながずに実機で動くので、テスターの端末でレイアウト問題を追うのに向いています。</p>

<h2 class="relative group">シミュレータと実機の違い
    <div id="シミュレータと実機の違い" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%83%9f%e3%83%a5%e3%83%ac%e3%83%bc%e3%82%bf%e3%81%a8%e5%ae%9f%e6%a9%9f%e3%81%ae%e9%81%95%e3%81%84" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>デバッグの大半はシミュレータでやっています。ケーブルも署名もいらず、インストールを待つ必要もないので速いからです。とはいえ、シミュレータの正体はMacのプロセッサの上で動くiOSのビルドで、メモリもネットワーク接続もMacのものをそのまま使っています。スマホの上でしか起きないバグは、そこでは出てきません。</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>シミュレータ</th>
					<th>実機のiPhone</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>速度</td>
					<td>MacのCPUで動くので、たいていスマホより速い</td>
					<td>本当の数字が出る。パフォーマンスの計測はこちらで</td>
			</tr>
			<tr>
					<td>メモリ</td>
					<td>実質的に上限がないので、メモリ警告はシミュレートする</td>
					<td>使いすぎたアプリはシステムに終了させられる</td>
			</tr>
			<tr>
					<td>ネットワーク</td>
					<td>Macの回線そのまま</td>
					<td>モバイル回線、弱い電波、WiFiとモバイルデータの切り替え</td>
			</tr>
			<tr>
					<td>カメラ、Bluetooth、NFC</td>
					<td>使えない</td>
					<td>使える</td>
			</tr>
			<tr>
					<td>プッシュ通知</td>
					<td><code>xcrun simctl push</code> で疑似的に送れる</td>
					<td>ユーザーが受け取るのと同じ経路で届く</td>
			</tr>
			<tr>
					<td>試行の速さ</td>
					<td>数秒</td>
					<td>遅い（署名、インストール、ケーブルかWiFi）</td>
			</tr>
	</tbody>
</table>
<p>なので、ロジックの組み立てとデバッグはシミュレータで済ませ、リリース前にはサポート対象の中でいちばん古いiPhoneでアプリを動かしています。パフォーマンス、メモリの逼迫、ハードウェアに触れる部分は、実機で動かすまでは本当の意味でテストしたことになりません。</p>

<h3 class="relative group">実機で動かす
    <div id="実機で動かす" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%ae%9f%e6%a9%9f%e3%81%a7%e5%8b%95%e3%81%8b%e3%81%99" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>iPhoneをつないで実行先に選び、Signing &amp; Capabilities でチームを設定します（自分のスマホで動かすだけなら無料のApple IDで足ります）。iOS 16以降は、スマホ側でも「設定 → プライバシーとセキュリティ」でデベロッパモードをオンにする必要があり、初回は再起動がかかります。オンにすると、スマホ自体の「設定 → デベロッパ」メニューにNetwork Link Conditionerが現れ、Macと同じ悪条件のネットワークプロファイルが使えるようになります。</p>

<h3 class="relative group">ワイヤレスデバッグ
    <div id="ワイヤレスデバッグ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%af%e3%82%a4%e3%83%a4%e3%83%ac%e3%82%b9%e3%83%87%e3%83%90%e3%83%83%e3%82%b0" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>一度だけUSBでつなぎ、Window → Devices and Simulators で「Connect via network」にチェックを入れて、ケーブルを抜きます。同じWiFiにいる限り、デバイスはXcodeから見え続けます。</p>

<h3 class="relative group">Device Console
    <div id="device-console" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#device-console" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Window → Devices and Simulators → Open Console。システムログ、アプリログ、クラッシュレポートと、何でも見えます。Xcode側のコンソールよりずっと詳細です。</p>
<p>テスターから「アプリが落ちた」と言われたら、必ずデバイスをつないでコンソールログを取らせてもらいます。すべてを説明してくれるシステムレベルのエラーが残っていることが多いんです。</p>

<h2 class="relative group">実際のデバッグ手順
    <div id="実際のデバッグ手順" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%ae%9f%e9%9a%9b%e3%81%ae%e3%83%87%e3%83%90%e3%83%83%e3%82%b0%e6%89%8b%e9%a0%86" aria-label="アンカー">#</a>
    </span>
    
</h2>
<ol>
<li>例外ブレークポイントがなければ追加する</li>
<li>バグを再現する</li>
<li>怪しい場所の近くにブレークポイントを置く</li>
<li>LLDBの <code>po</code> で値を確認する</li>
<li>変数を書き換えて、再コンパイルせずに修正を試す</li>
<li>UI絡みならビュー階層を見る</li>
<li>パフォーマンス絡みならInstrumentsでプロファイルする</li>
<li>メモリかスレッド絡みならサニタイザーをオンにする</li>
</ol>
<p>コツは症状から逆算することです。クラッシュ？例外ブレークポイント。遅い？Time Profiler。メモリ？Instruments。UIが変？ビューデバッガ。</p>

<h2 class="relative group">もっと早く知りたかったこと
    <div id="もっと早く知りたかったこと" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%82%e3%81%a3%e3%81%a8%e6%97%a9%e3%81%8f%e7%9f%a5%e3%82%8a%e3%81%9f%e3%81%8b%e3%81%a3%e3%81%9f%e3%81%93%e3%81%a8" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>アサーションを使いましょう。ロジックの誤りがバグになる前に捕まります。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="bp">assert</span><span class="p">(</span><span class="n">users</span><span class="p">.</span><span class="bp">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">,</span> <span class="s">&#34;Users array should never be empty here&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>デバッグを始める前にコミットしておくこと。仮説を試すためにあちこち書き換え始めると、必ず戻したくなります。</p>
<p>終わったらprint文は消すこと。少なくとも <code>#if DEBUG</code> で囲んでおきましょう。古いデバッグログは、次のバグを見えにくくします。</p>
<p>LLDBはちゃんと覚えること。アプリを20回リビルドするより速いです。</p>
<p>そして、シミュレータは本物の電話ではありません。実機でしか出ないバグは、ほぼ例外なくスレッドかメモリの問題です。あるいはパフォーマンスの問題。シミュレータはiPhoneのチップではなくMacのCPUで動いていますから。</p>

<h2 class="relative group">常に開いているツール
    <div id="常に開いているツール" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%b8%b8%e3%81%ab%e9%96%8b%e3%81%84%e3%81%a6%e3%81%84%e3%82%8b%e3%83%84%e3%83%bc%e3%83%ab" aria-label="アンカー">#</a>
    </span>
    
</h2>
<ul>
<li>Xcode（当然）</li>
<li>アプリのサブシステムで絞ったConsole.app</li>
<li>ネットワークデバッグ用のProxyman</li>
<li>事態が深刻になってきたらInstruments</li>
</ul>
<hr>
<p>どのツールも万能ではありません。ビューデバッガはメモリリークを見つけてくれませんし、InstrumentsはAuto Layoutの競合を解いてくれません。結局のところ、腕の見せどころは症状に合ったツールを選ぶことで、それは回数をこなすうちに身につきます。保持サイクルを何度も踏めば、スタックトレースを見た瞬間に匂いで分かるようになります。デバッグが楽しくなることはたぶんないですが、少なくとも苦行ではなくなります。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2026/2026-02-26-debugging-ios-mobile-apps/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
