iOS GameCenter 在私有 API `GKNotificationBannerViewController::hideBannerQuickly` 中崩溃

问题描述 投票:0回答:2

我在 Firebase 中找到了这个崩溃日志:

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)。我不明白为什么在进入后台时忽略它不起作用,以及我还有什么其他解决方案。

ios swift uikit game-center
2个回答
1
投票

这些崩溃的原因可能是死锁:在这两种情况下,崩溃的线程都会尝试获取信号量,但在看门狗取消应用程序之前无法获取它。
我不知道

GameCenterUI
,但是由于它是UI相关的,也许它只能在主线程上调用。但是,您的第二个堆栈跟踪显示它是在另一个线程上调用的。
所以,请检查在其他线程上调用
GameCenterUI
是否可以。


0
投票

根据您提供的崩溃日志,此问题似乎可能与 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% 用户的用户组,为他们禁用此自动关闭功能,根据结果您可以做出相应的反应。

Firebase A/B 教程
Kodeco(又名 Raywenderlich)A/B 测试教程

© www.soinside.com 2019 - 2024. All rights reserved.