Crashed: com.apple.main-thread
0 libdispatch.dylib 0x4b20 dispatch_semaphore_signal + 8
1 GameCenterUI 0x9ebd8 __56-[GKNotificationBannerViewController hideBannerQuickly:]_block_invoke_2 + 40
2 libdispatch.dylib 0x3f88 _dispatch_client_callout + 20
3 libdispatch.dylib 0x7418 _dispatch_continuation_pop + 504
4 libdispatch.dylib 0x1aa58 _dispatch_source_invoke + 1588
5 libdispatch.dylib 0x12748 _dispatch_main_queue_drain + 756
6 libdispatch.dylib 0x12444 _dispatch_main_queue_callback_4CF + 44
7 CoreFoundation 0x9a6c8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
8 CoreFoundation 0x7c02c __CFRunLoopRun + 2036
9 CoreFoundation 0x80eb0 CFRunLoopRunSpecific + 612
10 GraphicsServices 0x1368 GSEventRunModal + 164
11 UIKitCore 0x3a1668 -[UIApplication _run] + 888
12 UIKitCore 0x3a12cc UIApplicationMain + 340
13 libswiftUIKit.dylib 0x35308 UIApplicationMain(_:_:_:_:) + 104
14 BinMinesweeper 0x7050 main + 4345163856 (AppSceneDelegate.swift:4345163856)
15 ??? 0x1e6d6c960 (Missing)
上面的例子来自iOS 16.3.1、iPhone XR。我无法重现这次崩溃。这种情况很少发生(不到 1% 的用户)。
我认为
GKNotificationBannerViewController
是您启动应用程序时出现的顶部横幅“Welcome user_abc”。
我与游戏中心交互的唯一一次是当用户单击排行榜按钮时,我会显示游戏中心 VC。这是一些代码:
import GameKit
public enum GameCenterUtil {
private static var g_isEnabled: Bool = false
public static func setupIfNeeded() {
GKLocalPlayer.local.authenticateHandler = { loginVC, error in
if loginVC != nil {
// Nothing. Do not present login.
// User generally don't use game center. It's annoying.
return
}
if error != nil {
g_isEnabled = false
return
}
g_isEnabled = true
}
}
public static func reportScore(_ score: Int, category: String) {
guard g_isEnabled else { return }
GKLeaderboard.submitScore(score, context: 0, player: GKLocalPlayer.local, leaderboardIDs: [category]) { error in
// nothing
}
}
public static func presentLeaderboard(in vc: UIViewController) {
// when disabled, this will be an alert.
let gameCenterVC = GKGameCenterViewController()
gameCenterVC.gameCenterDelegate = LeaderboardDismisser.shared
vc.present(gameCenterVC, animated: true, completion: nil)
}
}
final class LeaderboardDismisser: NSObject, GKGameCenterControllerDelegate {
static let shared = LeaderboardDismisser()
func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
// This is required. When user is not logged in, the alert prompts up. If we don't dismiss it, the game vc will be not responding.
gameCenterViewController.dismiss(animated: true, completion: nil)
}
}
然后在
didFinishLaunching
中,我调用GameCenterUtil.setupIfNeeded()
,在排行榜按钮回调中,我调用GameCenterUtil.presentLeaderboard(in: vc)
。
编辑: 还有一个非常类似的崩溃,也无法重现并且很少发生:
Crashed: com.apple.GameKit.banner
0 libdispatch.dylib 0x4a60 dispatch_semaphore_wait + 8
1 GameCenterUI 0x9abe4 __42+[GKNotificationBannerWindow enqueBanner:]_block_invoke_2 + 60
2 libdispatch.dylib 0x2320 _dispatch_call_block_and_release + 32
3 libdispatch.dylib 0x3eac _dispatch_client_callout + 20
4 libdispatch.dylib 0xb534 _dispatch_lane_serial_drain + 668
5 libdispatch.dylib 0xc0d8 _dispatch_lane_invoke + 436
6 libdispatch.dylib 0x16cdc _dispatch_workloop_worker_thread + 648
7 libsystem_pthread.dylib 0xddc _pthread_wqthread + 288
8 libsystem_pthread.dylib 0xb7c start_wqthread + 8
编辑2:
这就是我展示/关闭游戏 VC 的方式:
在按钮单击中,我只需调用当前函数:
let button = MySpriteButton(texture: GAME_TEXTURE(.leaderboardBig))
{
PLAY_APP_SOUND(.click)
GameCenterUtil.presentLeaderboard(in: vc)
}
然后当用户进入后台时我会自动关闭它。这是之前的一个bug的解决方法(参考代码中的注释):
// MySceneDelegate.swift
open func sceneDidEnterBackground(_ scene: UIScene) {
// When we background the app with game center VC presented,
// Then a while later when we foreground the app, sometimes the game center VC becomes transparent for some reason,
// and covers the whole screen, making whole app not interactive
// This could be a bug in game center
// A naive solution is simply dismiss the game center VC when entering background.
if
let topVC = NAV_VC?.topViewController,
let gameCenterVC = topVC.presentedViewController as? GKGameCenterViewController
{
gameCenterVC.dismiss(animated: false, completion: nil)
}
}
现在我有一种感觉,这个解决方法可能就是原因。因为文档(https://developer.apple.com/documentation/gamekit/gknotificationbanner)说:
如果游戏在前台,横幅会立即出现。如果游戏在后台,则当游戏激活时会出现横幅。
这听起来像一个队列结构。我的崩溃日志之一有
GKNotificationBannerWindow enqueBanner:
。 2个声音相关。
我想我仍然想保留这个解决方法,但不确定它是如何导致崩溃的。我尝试在最新版本中将关闭延迟 1 秒,但这并不能解决问题:
// A helper function
func fetchTopGameCenterVC() -> GKGameCenterViewController? {
if
let topVC = NAV_VC?.topViewController,
let gameCenterVC = topVC.presentedViewController as? GKGameCenterViewController
{
return gameCenterVC
}
return nil
}
// MySceneDelegate.swift
open func sceneDidEnterBackground(_ scene: UIScene) {
if let vcBeforeDelay = fetchTopGameCenterVC() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let vcAfterDelay = fetchTopGameCenterVC(),
vcAfterDelay === vcBeforeDelay
{
vcAfterDelay.dismiss(animated: false, completion: nil)
}
}
}
}
编辑:
我添加了一些事件日志,并且非常确定它与我内部的逻辑相关
sceneDidEnterBackground
,因为所有崩溃都已进入后台事件记录。
我不能简单地删除这个逻辑,因为我需要它来修复透明游戏中心 vc 的一个非常罕见的问题(可能是 uikit bug)。我不明白为什么在进入后台时忽略它不起作用,以及我还有什么其他解决方案。
这些崩溃的原因可能是死锁:在这两种情况下,崩溃的线程都会尝试获取信号量,但在看门狗取消应用程序之前无法获取它。
我不知道
GameCenterUI
,但是由于它是UI相关的,也许它只能在主线程上调用。但是,您的第二个堆栈跟踪显示它是在另一个线程上调用的。GameCenterUI
是否可以。
根据您提供的崩溃日志,此问题似乎可能与 GameKit 内部逻辑有关。两个崩溃日志都提到了
dispatch_semaphore
的存在。
如如何正确释放包含 DispatchSemaphore 的对象中所述,您的解决方法可能会破坏与
dispatch_semaphore
平衡相关的内部对象生命周期。
if
let topVC = NAV_VC?.topViewController,
let gameCenterVC = topVC.presentedViewController as? GKGameCenterViewController
{
gameCenterVC.dismiss(animated: false, completion: nil)
}
我建议您在这种情况下尝试A/B 测试。 尝试设置一个包含 10-15% 用户的用户组,为他们禁用此自动关闭功能,根据结果您可以做出相应的反应。