<?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/zh-cn/tags/xcode/</link>
    <description>关于软件工程与研发团队领导力的短文——绩效评估、招聘与团队扩张、CI/CD、DevOps 监控、智能体 AI。写于首尔。</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <copyright>© 2026 仁才德</copyright>
    <lastBuildDate>Tue, 27 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/zh-cn/tags/xcode/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>如何用 Xcode 调试 iOS 应用：断点、LLDB、Instruments 和模拟器</title>
      <link>https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-02-26-debugging-ios-mobile-apps/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/zh-cn/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、各种 sanitizer、崩溃日志，以及模拟器发现不了、只有真机才能抓到的问题。</description>
      <content:encoded><![CDATA[<p>在 Xcode 里调试 iOS 应用，第一步是加一个异常断点，这样一崩溃就会停在抛异常的那一行；然后重现 bug，在 LLDB 控制台里用 <code>po</code> 查看状态。接下来按症状挑工具：布局问题用视图调试器，内存泄漏和卡顿用 Memory Graph 和 Instruments，时有时无的崩溃用 Address Sanitizer 和 Thread Sanitizer。最后一定要在真机上跑一遍，因为有一整类 bug 在模拟器里根本看不到。</p>
<p>我花在调试上的时间实在太多了。谁不是呢。最近大部分时间都在 Xcode 和模拟器里追自己应用的 bug，越用越有一个感受：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="#%e6%96%ad%e7%82%b9%e6%af%94%e4%bd%a0%e6%83%b3%e7%9a%84%e6%9b%b4%e5%bc%ba%e5%a4%a7" 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%e6%96%ad%e7%82%b9" 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="#%e7%ac%a6%e5%8f%b7%e6%96%ad%e7%82%b9" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>不知道方法在哪被调用时，就靠它。打开断点导航器（Cmd+8），点 + 按钮，选“Symbolic Breakpoint”。</p>
<p>想看每个视图控制器什么时候加载？把符号设成 <code>viewDidLoad</code>，Xcode 会在每一处暂停。太吵就缩小范围：<code>MyViewController.viewDidLoad</code>。</p>
<p>我常年留着这三个：</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="#%e5%bc%82%e5%b8%b8%e6%96%ad%e7%82%b9" 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="#%e6%96%ad%e7%82%b9%e5%8a%a8%e4%bd%9c" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>断点其实不必停止执行。编辑断点，点“Add Action”，可以：</p>
<ul>
<li>不暂停地输出变量：<code>&quot;User count: @(users.count)@&quot;</code></li>
<li>自动执行 LLDB 命令</li>
<li>执行 shell 脚本</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%e4%bd%a0%e7%9c%9f%e6%ad%a3%e4%bc%9a%e7%94%a8%e5%88%b0%e7%9a%84%e6%8e%a7%e5%88%b6%e5%8f%b0%e5%91%bd%e4%bb%a4" 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%e7%a1%80" 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> 用可读的格式打印对象。我 90% 的 LLDB 操作就是它。</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="#%e8%bf%90%e8%a1%8c%e6%97%b6%e6%94%b9%e5%80%bc" 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>发现 bug，想不重新构建就试一下修复？改掉变量继续跑就行。缩小边界情况范围的时候，我一直这么换值测试。</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="#%e5%af%bc%e8%88%aa" 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="#%e7%9b%91%e8%a7%86%e7%82%b9" 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%e8%a2%ab%e5%9f%8b%e6%b2%a1%e7%9a%84%e8%b0%83%e8%af%95%e5%b7%a5%e5%85%b7" 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>常用的过滤谓词可以存起来。我存了三个：看网络请求的、看数据库操作的、只显示错误和警告的。</p>

<h3 class="relative group">为什么用 OSLog 而不是 print()
    <div id="为什么用-oslog-而不是-print" 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%b8%ba%e4%bb%80%e4%b9%88%e7%94%a8-oslog-%e8%80%8c%e4%b8%8d%e6%98%af-print" 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-%e8%bf%87%e6%bb%a4%e8%bf%9b%e9%98%b6" 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>日志还能导出来附在 bug 报告里，比从 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="#%e8%a7%86%e5%9b%be%e8%b0%83%e8%af%95" 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-%e8%b0%83%e8%af%95" 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="#%e7%9c%81%e6%97%b6%e9%97%b4%e7%9a%84%e6%a8%a1%e6%8b%9f%e5%99%a8%e5%8a%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="#%e6%85%a2%e9%80%9f%e5%8a%a8%e7%94%bb" 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="#%e6%a8%a1%e6%8b%9f%e5%86%85%e5%ad%98%e8%ad%a6%e5%91%8a" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>Debug → Simulate Memory Warning，测试应用在低内存下的表现。我靠它发现过不少图片缓存 bug：内存警告触发之前一切正常，一触发所有图片全没了。</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、高丢包率或者完全离线。如果你遇到过 API 请求在 WiFi 上好好的、一到蜂窝网络就超时，用它就能复现。</p>
<p>我常备一个“Bad Network”配置：500ms 延迟加 10% 丢包。那些在办公室高速 WiFi 上永远看不到的超时 bug 和加载状态问题，全在这现形。</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="#%e7%8a%b6%e6%80%81%e6%a0%8f%e8%a6%86%e7%9b%96" 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%a8%a1%e6%8b%9f" 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="#%e5%86%85%e5%ad%98%e8%b0%83%e8%af%95" 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>delegate 也得是 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 张图一次全加载了？）、该释放却还活着的对象。</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="#%e6%80%a7%e8%83%bd%e5%88%86%e6%9e%90" 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”排序找瓶颈。哪个函数占了 40% 的 self weight，时间就是耗在那了。</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="#%e8%bf%90%e8%a1%8c%e6%97%b6%e8%af%8a%e6%96%ad" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>Edit Scheme → Run → Diagnostics。这里一排复选框，专抓那些靠手动永远找不到的 bug。</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 倍，但它抓的 bug 换别的办法几乎无从下手。追那种偶尔才出现的崩溃时，我会把它打开。</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>抓数据竞争和线程问题。开着它跑应用，正常用就行，只要有竞态条件，它自然会找出来。</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 bug，它是最好的。</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%e8%a7%81%e8%b0%83%e8%af%95%e5%9c%ba%e6%99%af" 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="#%e4%b8%80%e5%90%af%e5%8a%a8%e5%b0%b1%e5%b4%a9%e6%ba%83" 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-%e4%b8%8d%e6%9b%b4%e6%96%b0" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>每次。每一次，都是其中之一：</p>
<ol>
<li>更新不在主线程上</li>
<li>outlet 没连上</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="#%e5%86%85%e5%ad%98%e8%ad%a6%e5%91%8a%e5%af%bc%e8%87%b4%e5%b4%a9%e6%ba%83" 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="#%e7%a5%9e%e7%a7%98%e5%b4%a9%e6%ba%83" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>sanitizer 全开。间歇性的多半是线程问题，用 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%a4%8d%e7%8e%b0%e4%b8%8d%e4%ba%86%e7%9a%84%e5%b4%a9%e6%ba%83" 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="#%e5%9c%a8%e6%a8%a1%e6%8b%9f%e5%99%a8%e9%87%8c%e8%b0%83%e8%af%95-ota-%e6%9b%b4%e6%96%b0" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>我的应用会推送 OTA（空中下载）更新，而调试这整条流程，模拟器是最省事的地方。</p>
<p>我先盯网络流量。把 Console.app 过滤到应用的子系统，清单请求和响应就实时滚出来，发了什么头部、服务器回了什么，一清二楚。</p>
<p>然后故意把网络弄坏。用 Network Link Conditioner 看看更新流程在慢连接、丢包下什么表现。应用挂住了吗？加载指示器出来了吗？有没有体面地降级？</p>
<p>更新检查逻辑周围都埋上日志：开始检查时、发现可用更新时、开始下载时、加载新 bundle 时，每个节点都记。</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="#%e7%ac%ac%e4%b8%89%e6%96%b9%e5%b7%a5%e5%85%b7" 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 的 UI 比 Charles 好，在 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="#%e6%a8%a1%e6%8b%9f%e5%99%a8%e5%92%8c%e7%9c%9f%e6%9c%ba%e7%9a%84%e5%8c%ba%e5%88%ab" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>我大部分调试都在模拟器里做，因为快：不用插线，不用签名，也不用等安装。但模拟器说到底是一个跑在 Mac 处理器上的 iOS 构建，用的是 Mac 的内存、Mac 的网络，有些 bug 只有在手机上才会出现。</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>模拟器</th>
					<th>真机</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>蜂窝网络、信号弱、在 Wi-Fi 和移动数据之间来回切换</td>
			</tr>
			<tr>
					<td>相机、蓝牙、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>慢一些：签名、安装、连线或 Wi-Fi</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%9c%a8%e7%9c%9f%e6%9c%ba%e4%b8%8a%e8%bf%90%e8%a1%8c" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>把 iPhone 插上，在运行目标里选中它，再到 Signing &amp; Capabilities 里设好 Team（只装到自己的手机上，免费的 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="#%e6%97%a0%e7%ba%bf%e8%b0%83%e8%af%95" 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="#%e6%88%91%e8%b0%83%e8%af%95%e6%97%b6%e7%9a%84%e5%ae%9e%e9%99%85%e9%a1%ba%e5%ba%8f" aria-label="锚点">#</a>
    </span>
    
</h2>
<ol>
<li>没有异常断点就先加一个</li>
<li>重现 bug</li>
<li>在怀疑出问题的地方附近设断点</li>
<li>在 LLDB 里用 <code>po</code> 查值</li>
<li>改变量，不重编译就验证修复</li>
<li>UI 相关就查视图层级</li>
<li>性能相关就用 Instruments 分析</li>
<li>内存或线程相关就开 sanitizer</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="#%e5%b8%8c%e6%9c%9b%e6%97%a9%e7%82%b9%e7%9f%a5%e9%81%93%e7%9a%84%e4%ba%8b" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>用断言。逻辑错误还没变成 bug 就能被拦下：</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> 包起来。旧的调试日志会挡住下一个 bug。</p>
<p>把 LLDB 学明白，比把应用重新构建二十遍快。</p>
<p>还有，模拟器不是真手机。只在真机上出现的 bug，几乎全是线程或内存问题。再不然就是性能问题，毕竟模拟器跑的是 Mac 的 CPU，不是真正的 iPhone 芯片。</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="#%e6%88%91%e4%b8%80%e7%9b%b4%e5%bc%80%e7%9d%80%e7%9a%84%e5%b7%a5%e5%85%b7" 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>
