10 유니티 게임 애플 화면 미러링시 크래시

A. 증상

  • 아이폰/아이패드에서 AirPlay 활성화 후 게임 실행 했을 때 크래시 발생(스플래시 스크린 전 종료)

  • iOS 11에서만 발생

  • Stack trace: "F-1) 크래시 Stack trace" 참고

B. 재현방법

  1. 아이폰/아이패드에서 "화면 미러링" 선택

  2. AirPlay를 이용해 애플 TV와 연결

  3. 유니티 게임 실행

  4. 게임 시작하자마자 크래시 발생

C. 원인

  • iOS 11에서 외부 모니터가 연결되었을 때 게임 시작 시 "UIScreenDidConnectNotification" 알림이 이전 iOS 버전보다 예상치 못하게 빨리 호출되어 발생함

D. 해결방법

  1. 공식패치 적용

  • 2017.1.3p1, 2017.2.1p2, 2017.3.0p2: iOS: Fixed iOS 11 crash when application is launched from URL and airplay screen mirroring is enabled.

  • 5.6은 추후 릴리즈 예정

  1. Workaround 적용

  • 공식패치 사용이 어려울 경우 다음의 workaround를 적용

  • 유니티 설치폴더 또는 Xcode의 

DisplayManager.mm을 "F-2) DisplayManager.mm 수정 부분"을 참고하여 수정(해당 코드 부분 주석 처리)
-> 유니티 설치폴더(iOS 빌드 전): Unity/PlaybackEngines/iOSSupport/Trampoline/Classes/Unity/DisplayManager.mm
-> 프로젝트 당 생성된 Xcode: ProjectRoot/Classes/Unity/DisplayManager.mm

  • 아이폰/아이패드와 외부 모니터를 동시에 사용하는 경우 게임이 정상 작동하지 않을 수 있음(미러링은 정상 작동함)

E. 테스트 결과

  • 테스트 기기: iPhone 5(iOS 11.2.6), iPhone 6(iOS 11.2.1), iPhone X(iOS 11.2.2)

  • 테스트 버전: Unity 5.4, 5.5, 5.6(수정 부분은 Unity 4.7를 포함한 5.3 이하 버전에서도 동일함, 4.6 이하 버전은 확인 필요)

  • AirPlay 뿐만 아니라 라이팅-HDMI 어탭터 연결을 통한 미러링이 정상 작동함 

F. 참고

  1. 크래시 Stack trace

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x88) * frame #0: 0x000000010494ce20 remotear`::UnityUpdateDisplayList() at LibEntryPoint.mm:614 [opt]   frame #1: 0x00000001046c7148 remotear`::-DisplayManager updateDisplayListInUnity at DisplayManager.mm:338 [opt]   frame #2: 0x00000001046c74ec remotear`::-DisplayManager screenDidConnect: at DisplayManager.mm:377 [opt]   frame #3: 0x000000018305e12c CoreFoundation`__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20   frame #4: 0x000000018305d6cc CoreFoundation`_CFXRegistrationPost + 420   frame #5: 0x000000018305d430 CoreFoundation`___CFXNotificationPost_block_invoke + 60   frame #6: 0x00000001830da9f4 CoreFoundation`-[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1408   frame #7: 0x0000000182f943e0 CoreFoundation`_CFXNotificationPost + 380   frame #8: 0x00000001839b4498 Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 68   frame #9: 0x000000018ca0a7f8 UIKit`+[UIScreen _FBSDisplayConfigurationConnected:andNotify:] + 280   frame #10: 0x000000018c740d30 UIKit`-[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 292   frame #11: 0x000000018cb446ec UIKit`-[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 364   frame #12: 0x000000018576d768 FrontBoardServices`-[FBSSceneImpl _didCreateWithTransitionContext:completion:] + 364   frame #13: 0x0000000185776070 FrontBoardServices`__56-[FBSWorkspace client:handleCreateScene:withCompletion:]_block_invoke_2 + 224   frame #14: 0x0000000182a51048 libdispatch.dylib`_dispatch_client_callout + 16   frame #15: 0x0000000182a586c8 libdispatch.dylib`_dispatch_block_invoke_direct$VARIANT$mp + 288   frame #16: 0x00000001857a1a04 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36   frame #17: 0x00000001857a16a8 FrontBoardServices`-[FBSSerialQueue _performNext] + 404   frame #18: 0x00000001857a1c44 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 56   frame #19: 0x0000000183074358 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24   frame #20: 0x00000001830742d8 CoreFoundation`__CFRunLoopDoSource0 + 88   frame #21: 0x0000000183073b60 CoreFoundation`__CFRunLoopDoSources0 + 204   frame #22: 0x0000000183071738 CoreFoundation`__CFRunLoopRun + 1048   frame #23: 0x0000000182f922d8 CoreFoundation`CFRunLoopRunSpecific + 436   frame #24: 0x0000000184e23f84 GraphicsServices`GSEventRunModal + 100   frame #25: 0x000000018c53e880 UIKit`UIApplicationMain + 208   frame #26: 0x00000001046b9f00 remotear`main(argc=1, argv=0x000000016b74bb50) at main.mm:33 [opt]   frame #27: 0x0000000182ab656c libdyld.dylib`start + 4

 

2. DisplayManager.mm 수정 부분

 

수정전

- (id)init {     if ((self = [super init]))     {         [[NSNotificationCenter defaultCenter] addObserver: self          selector: @selector(screenDidConnect:)          name: UIScreenDidConnectNotification          object: nil         ];           [[NSNotificationCenter defaultCenter] addObserver: self          selector: @selector(screenDidDisconnect:)          name: UIScreenDidDisconnectNotification          object: nil         ];           _displayConnection = [NSMapTable                               mapTableWithKeyOptions: NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality                               valueOptions: NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality             ];           for (UIScreen* screen in[UIScreen screens])             [self registerScreen: screen];           _mainDisplay = self[[UIScreen mainScreen]];     }     return self; }

수정후

- (id)init {     if ((self = [super init]))     {         /*         [[NSNotificationCenter defaultCenter] addObserver: self          selector: @selector(screenDidConnect:)          name: UIScreenDidConnectNotification          object: nil         ];           [[NSNotificationCenter defaultCenter] addObserver: self          selector: @selector(screenDidDisconnect:)          name: UIScreenDidDisconnectNotification          object: nil         ];          */           _displayConnection = [NSMapTable                               mapTableWithKeyOptions: NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality                               valueOptions: NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality             ];           for (UIScreen* screen in[UIScreen screens])             [self registerScreen: screen];           _mainDisplay = self[[UIScreen mainScreen]];     }     return self; }