这是一份可复用的操作指南:准备 Mac 与 iPhone、安装设备通信工具、读取并备份布局、规划文件夹,最后写回并核对结果。

本例使用 Mac 通过 USB 直接连接 iPhone,并用 pymobiledevice3 的 SpringBoard Services 读写主屏图标布局。它不需要在 iPhone 上安装整理 App,也不需要 Surge、Open Minis、CDTK 或越狱。pymobiledevice3 提供 Python 命令行工具和 Python API;项目说明与安装命令见官方仓库及Python API 文档。

需要准备什么

  • 一台 Mac 和一根可传数据的 USB 线。
  • 一台可解锁的 iPhone。首次连接时,在手机上点“信任此电脑”,并输入设备密码。
  • Mac 上的 Python 3 和 pymobiledevice3。
  • 一个用于保存原始布局备份和候选布局的工作目录。

本次实际连接环境是 iPhone(设备标识 iPhone17,2)、iOS 27.2、Python 3.12、pymobiledevice3 11.36.0。不同电脑上的版本可能不同,连接后应以本机命令输出为准。Xcode 可用于其他设备开发流程,但这条 SpringBoard Services 主屏读写路径不要求安装 Xcode。

第一步:在 Mac 安装工具并确认 USB 连接

在终端创建独立 Python 环境并安装:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip pymobiledevice3

把 iPhone 解锁并连接后,检查设备是否已被 USB 多路复用服务识别:

pymobiledevice3 usbmux list --usb

输出中应能看到设备型号和 UDID。若没有设备,先检查线缆、解锁状态与“信任此电脑”提示,不要继续写布局。

第二步:读取当前布局并备份

读取主屏布局:

pymobiledevice3 springboard state get > homescreen-before.json
chmod 600 homescreen-before.json
shasum -a 256 homescreen-before.json

这个 JSON 是完整布局,不只是图标名称:根数组第一个元素是 Dock,后续元素是主屏页面;文件夹内容在 iconLists 中,小组件有自己的元素和标识字段。先把原始文件留好,再解析和生成候选文件。不要用空 JSON 或空状态做试验。

第三步:清点并规划

递归读取布局,先按 displayIdentifier 清点每个图标,再识别文件夹、小组件和特殊图标。规划时以原始对象为基础,只移动图标对象和调整文件夹名称、内容列表;保留未知字段、小组件配置和图标标识。用途不明确的 App 先留在原位或标注待确认,不要凭名字猜测。

适合先确定三件事:

  1. 第一屏放最常用入口,保留用户已经熟悉的排列。
  2. 其余页面按场景分组,例如工作效率、金融、出行、生活服务、系统工具、影音娱乐。
  3. 文件夹内部按使用场景排序;若考虑单手操作,把最常用的文件夹排在屏幕下方。个人使用频率无法从布局 JSON 得知,应由用户指定或在确认时调整。

本次整理中使用的分类示例包括:境内金融/境外金融、公共交通/私人交通/旅行服务、笔记任务/邮件协作/云盘资料/录音转写,以及系统工具/安全与拦截/设备与检测。

第四步:写入前做静态检查并确认

保存候选布局为单独的 JSON 文件,并至少检查:

  • JSON 能正常解析,根结构和原布局一致。
  • 每个原有图标仍恰好出现一次,没有遗漏或意外重复。
  • Dock 和需要保留的页面内容一致。
  • 小组件对象及其内部标识没有变化。
  • 文件夹分页符合设备观察到的容量。
  • 目标布局与原布局的差异只包含计划中的移动和重命名。

把页面顺序、文件夹分类和差异说明展示给用户。只有用户明确同意后才进入写入步骤。

第五步:通过 Python API 写入

当前 pymobiledevice3 的 CLI 提供主屏读取命令;写入使用 SpringBoardServicesService.set_icon_state()。连接和服务调用是异步的。下面是关键调用结构,serial 用 USB 清单中的 UDID,target 是经过静态检查并获用户确认的完整布局:

import asyncio
import json
from pathlib import Path
from datetime import datetime
from pymobiledevice3.lockdown import retry_create_using_usbmux
from pymobiledevice3.services.springboard import SpringBoardServicesService


def normalize(value):
    if isinstance(value, datetime):
        return value.isoformat()
    if isinstance(value, dict):
        return {key: normalize(item) for key, item in value.items()}
    if isinstance(value, list):
        return [normalize(item) for item in value]
    return value


async def main():
    serial = "从 usbmux 清单复制 UDID"
    baseline = json.loads(Path("homescreen-before.json").read_text())
    target = json.loads(Path("homescreen-target.json").read_text())
    lockdown = await retry_create_using_usbmux(serial=serial)

    async with SpringBoardServicesService(lockdown=lockdown) as service:
        current = await service.get_icon_state()
        if normalize(current) != normalize(baseline):
            raise RuntimeError("设备布局已变化;停止写入并重新规划")

        await service.set_icon_state(target)

        actual = await service.get_icon_state()
        if normalize(actual) != normalize(target):
            raise RuntimeError("回读布局与目标不一致;停止后续操作")


asyncio.run(main())

set_icon_state() 是真实的整份布局写入,返回 None 本身不代表设备画面已验证成功。不要调用无参数的 set_icon_state():空状态可能覆盖主屏布局。写入只做一次;连接中断或回读不一致时停止,不自动重试。

第六步:回读和人工查看

程序完成后,再通过 CLI 读取一次:

pymobiledevice3 springboard state get > homescreen-after.json

将其与目标 JSON 比较,并复核图标清单、文件夹内容、Dock 和小组件。最后在 iPhone 上查看实际屏幕,确认图标顺序和文件夹显示符合预期。设备回读证明布局数据已接受目标状态;人工查看用于确认屏幕呈现符合个人习惯。

这次整理的结果示例

最终主屏为三页:第一页保留原布局与常用社交入口;第二页集中放工作、金融、出行和生活分类;第三页放影音、创作、游戏、小组件和少量独立图标。大文件夹按用途整理,社交文件夹及其内部顺序保留。

每次写入前都保存权限为 0600 的备份,并在写后回读核对。过程记录还发现,“拦截猫”曾出现在较早的写后备份中,但在后续实时布局里已不见;后续操作以当时的实时状态为基线,没有主动补回。遇到类似差异时,应先确认是否为用户自行移动,再决定是否恢复图标。

参考

最后修改:2026 年 10 月 19 日
感谢阅读,欢迎交流。