Screenshots

最近将 Mac 升级到 macOS Beta 以后,iPhone 镜像突然不能用了。Mac 明明可以找到旁边的 iPhone,但每次打开 iPhone Mirroring,最后都只会得到一个 Unable to Connect to iPhone,点 Try Again 也没什么用。

该检查的其实都检查过了:两边是同一个 Apple 账户,Wi-Fi、蓝牙和接力也都开着,手机就在旁边并且处于锁屏状态,VPN、个人热点、AirPlay 和随航也没有占用。同时,还有个比较奇怪的地方,就是「iPhone 镜像 → 设置」里的「撤销 iPhone 访问权限」是灰色的。看起来系统认为没有已经授权的手机,但它又确实能发现这台手机。

最后折腾下来,问题出在 replicatord 留下的配对状态。这里记录下排查和修复过程,免得下次再碰到又从 Wi-Fi 开始查一遍。下面,简单记录下排查和解决的过程。

先看日志

首先确认几个相关服务是否还活着:

pgrep -alf 'iPhone Mirroring|replicatord|sharingd|rapportd'

然后打开 iPhone 镜像,点一次 Try Again,再查看最近五分钟的日志:

log show --style compact --last 5m --info --debug \
    --predicate '(process == "iPhone Mirroring" OR process == "replicatord" OR process == "rapportd" OR subsystem CONTAINS[c] "Replicator" OR subsystem CONTAINS[c] "ScreenContinuity")' \
    | grep -Ei 'noCompatiblePhone|pairing|paired|unsupported|error|fail|eligible|device'

这里比较关键的几行,脱敏以后大概是这样:

Checking if Mac is supported
Checking if Mac is eligible
Checking if iCloud is in a healthy state
Checking if WiFi is powered on
Checking if continuity feature is enabled
Checking if Replicator has a device paired
Fetched 1 devices
Got devices: [...; pairing; phone; ...]
Tearing down the session due to: noCompatiblePhone

与此同时,rapportd 已经能看到下面这些状态:

PairedBT, PairedSys, WiFiP2P, MyiCloud, AcLv CoverClosed

也就是说蓝牙发现、同一 iCloud、Wi-Fi P2P 和手机锁屏其实都没有问题。真正可疑的是 replicatord 找到了设备,但它的状态一直停在 pairing,随后 iPhone 镜像将其当成了 noCompatiblePhone

先试试简单的办法

在动数据库以前,先重启了当前用户的几个连续互通服务:

killall -TERM sharingd rapportd 2>/dev/null
open -a 'iPhone Mirroring'

这个操作不会删除配对信息。如果重新打开以后还是失败,可以再去应用设置里试试「撤销 iPhone 访问权限」。按钮能点的话,直接用系统提供的方式重新配对最好。

但我这里的按钮是灰色的,所以只能手动清掉这条半死不活的记录。

重建 Replicator 配对数据库

replicatord 的当前用户数据库通常在这里:

~/Library/Group Containers/group.com.apple.replicatord/replicatord/

先退出 iPhone 镜像,并且给数据库做个备份:

osascript -e 'tell application "iPhone Mirroring" to quit'

BACKUP="$HOME/Desktop/replicatord-backup-$(date +%Y%m%d-%H%M%S)"
STATE="$HOME/Library/Group Containers/group.com.apple.replicatord/replicatord"
mkdir -p "$BACKUP"

然后停掉当前用户的 replicatord

launchctl bootout "gui/$(id -u)/com.apple.replicatord"

这里没有删除数据库,只是把 SQLite 文件和对应的 WAL 文件挪到刚才的备份目录:

for file in replicatord.sql replicatord.sql-wal replicatord.sql-shm; do
    [ ! -e "$STATE/$file" ] || mv "$STATE/$file" "$BACKUP/$file"
done

最后重新拉起服务:

launchctl bootstrap "gui/$(id -u)" \
    /System/Library/LaunchAgents/com.apple.replicatord.plist

killall -TERM sharingd rapportd 2>/dev/null
open -a 'iPhone Mirroring'

重新打开以后,系统会生成新的数据库,并且再次建立镜像关系。按界面提示解锁或确认 iPhone,完成以后再把手机锁上即可。

验证和回滚

连接成功后,可以用 SQLite 看一下新的关系状态:

sqlite3 \
    "$HOME/Library/Group Containers/group.com.apple.replicatord/replicatord/replicatord.sql" \
    'SELECT RemoteDeviceType, State, count(*) FROM PairingRelationship GROUP BY RemoteDeviceType, State;'

这里重建前设备一直是 pairing,重建后数据库中的关系进入了 State = 2,日志也开始建立 AVConference 音视频流。退出应用、锁定手机再打开一次,镜像仍然可以正常连接,最近的日志里也没有再出现 noCompatiblePhone

如果重建以后反而有其他问题,可以先退出应用并停掉服务,再把备份放回去:

osascript -e 'tell application "iPhone Mirroring" to quit'
launchctl bootout "gui/$(id -u)/com.apple.replicatord"
mv "$BACKUP"/replicatord.sql* "$STATE"/
launchctl bootstrap "gui/$(id -u)" \
    /System/Library/LaunchAgents/com.apple.replicatord.plist

确认稳定使用一段时间以后,桌面上的备份目录就可以删掉了。个人更建议直接去 Finder 里删除,至少比复制一条带变量的 rm -rf 安心些。

总结和思考

这次刚好是 macOS Beta 搭配上一代稳定版 iOS,所以最开始也怀疑是两边的协议版本不兼容。毕竟 iPhone 镜像牵涉到蓝牙近距发现、Wi-Fi P2P、iCloud 身份以及 RapportSharingReplicator 等一串私有协议,系统升级以后旧配对失效并不奇怪。

但就这次的结果来看,清掉 Mac 本地的单条配对数据库以后马上恢复,说明直接原因还是 replicatord 的状态不一致,跨版本更像是触发问题的诱因。

如果重新配对以后仍然失败,同时日志里出现 unsupported、版本协商失败,或者根本找不到可配对设备,那才更像是真正的兼容性问题。这种情况下还是先升级到更新的 macOS Beta 或正式版比较合适,没必要为了验证一个猜测,就把主力 iPhone 也升级到测试版。