먹고 기도하고 코딩하라

appDelegate만 있던 프로젝트에 sceneDelegate 추가하기 본문

앱/Swift

appDelegate만 있던 프로젝트에 sceneDelegate 추가하기

사과먹는사람 2023. 1. 13. 23:11
728x90
728x90

 

iOS 개발자라면 iOS 13과 함께 등장한 sceneDelegate를 모를 수가 없을 텐데, 우리 앱은 최소 지원 버전이 iOS 11이다. 지금까지는 sceneDelegate를 도입하지 않고 AppDelegate만 관리했다. 어쨌든 Info에 scene manifest 설정을 안 하고 sceneDelegate 도입을 안 하면 13 이상 버전도 appDelegate를 타긴 타니까.

이번에 카플레이를 지원하게 되면서 sceneDelgate를 도입하게 됐다. 카플레이 스크린을 띄우려면 CPTemplateApplicationSceneDelegate라고 카플레이 씬에 대한 delegate가 필요한데, UISceneDelegate가 없이 단독으로 카플레이 씬만 띄우고 delegate 만드는 게 불가능했기 때문이다.

암튼. 그래서 sceneDelegate를 추가했는데... 어쨌거나 appDelegate의 didFinishLaunchingWithOptions은 sceneDelegate가 있어도 타긴 타니까 여기에 있는 건 됐다 싶었고, 다른 appDelegate 메소드들도 보긴 봤는데 딱히 sceneDelegate로 옮길 건 없어 보였다.

는 내 착각이고 중대한 변경사항이 좀 있었다.

 

 

1. window 설정이 조금 다르다.

sceneDelegate를 도입한 iOS 13 이후의 앱은 최초 구동했을 때 일단 AppDelegate의 application(didFinishLaunchingWithOptions) 메소드를 타고, 그 다음에 SceneDelegate의 scene(willConnectTo) 메소드를 탄다.

AppDelegate에서는 이렇게 window를 생성하고 보이게 한다.

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
	// 생략
	window = UIWindow(frame: UIScreen.main.bounds)
	window?.rootViewController = MainViewController()
	window?.makeKeyAndVisible()
    // 생략
    return true
}

SceneDelegate에서는 한 걸음 더 나아가 windowScene까지 맞춰주게 된다. iOS 13 이후부터 windowScene 개념이 도입돼서 이렇게 windowScene을 정해주게 되어 있다.

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
	guard let windowScene = scene as? UIWindowScene, session.configuration.name == "Info.plist에서 설정한 이름" else { return }
	window = UIWindow(frame: windowScene.coordinateSpace.bounds)
	window?.rootViewController = MainViewController()
	window?.windowScene = windowScene
	window?.makeKeyAndVisible()
}

카플레이를 도입함으로써 이제서야 sceneDelegate과 멀티 씬의 참 의미를 알게 된 것 같은 느낌이다.. -_- 솔직히 아이패드 앱 만들지 않는 이상 아이폰 앱으로는 멀티 씬 개념을 생각하기가 되게 어려운데, 카플레이 씬을 도입하면서 느낌이 한 번에 왔다. 뭐 구현이 어렵고 이런 게 문제라기보다는 scene 때문에 머리에서 스팀 올라왔다.. ㅋㅋ

블로그 참고 글을 보니.. 주말 동안 AppDelegate와 SceneDelegate의 라이프사이클에 대해 좀 더 공부해보고, 월요일에 출근해서 QA 넘기기 전에 AppDelegate에 있는 라이프사이클 메소드를 브레이크포인트 촘촘히 찍어 확인해보고 안 타면 SceneDelegate에 좀 옮겨야겠다는 생각이 든다.

 

 

2. 소셜 로그인 등 외부에서 들어오는 URL을 여는 메소드, 딥링크 처리를 구현해줘야 한다 (매우 중요)

진짜 이건 너무너무너무너무!! 중요하다. 앱이 켜져있으나 꺼져있으나 소셜 로그인, 딥링크 처리되게 해주는 것!

대수롭게 생각하지 않았던 이 이슈는 화요일 오후에 발견됐다.

엥? 내 폰에서는 잘 되는데 왜 QA에서는 안 된다는 거지? 저번 주에 QA 과장님 빌드 머신에 카플레이 시뮬레이터 설치해드리고 이런저런 얘기도 하고 슬랙 주고받고 과장님 남편 분이 만드셨다는 꿀호두? 같은 것도 얻어먹다 보니 좀 친밀감이 쌓여서 (ㅋㅋㅋㅋㅋㅋㅋㅋ) 과장님 자리 기웃기웃거리면서 재현을 봤는데 진짜 안 됐다. 뭔가 문제가 있어보이긴 했는데 정확히 뭐가 문제인지는 몰랐다.

 

그러던 와중에 카카오톡 로그인은 정말 안 되는 걸 확인해서 앱스토어에 있는 버전도 똑같은지 확인했는데, 상용은 카카오톡 로그인이 잘 되지만 테스트에서는 안 됐다.

그래서 2가지를 가정하고 의심했다.

(A) iOS 버전 문제다. (15, 16)

(B) 패키지 버전 문제다. pod update 돌리면 잘 될 거다.

 

보통 에러가 나면 내가 시도해보는 방법은 브레이크포인트를 찍거나, lldb 디버거에서 po 해서 변수값 확인하거나 그것도 안 되면 print 찍어서 확인하는 식인데... 이상한 게 카카오 로그인 팟 파일에 든 AuthController 어딘가에서 냅다 뚝 끊겨버렸다.

뭐 return을 타는 것도 아니고 그냥 증발해버리는 게 너무 이상해서 차장님께 보고했더니, 로그창에 뜨는 로그를 주의깊게 보셨다. 이걸로 또 하나 배웠다. 로그 자체를 중요하게 봐야된다는 거..

근데 웃기는 게 software caused connection abort 하면서 네트워크 연결이 유실됐다는 에러가 떴다. 회사 네트워크 환경도 좋았고, 혹시나 해서 모바일 데이터로도 해봤는데 역시 네트워크 연결 유실이라는 에러 메시지를 받고 어안이 벙벙해졌다.

이 때쯤 돼서 마치 상태이상 걸린 것마냥 pod을 유력한 용의자로 의심해서 목요일 오전에 pod update했다. 그랬더니 lottie가 냅다 4로 올라가서 프로젝트가 순식간에 에러 도떼기시장이 됐다.. AnimationView 이렇게 돼있던 게 뭐 LottieAnimationView 이런 식으로 네이밍이 바뀌어서 에러 로그가 무지막지하게 길었다.

 

결론적으로 가정 2개는 모두 틀렸고, sceneDelegate에서 URL open하는 메소드를 구현하지 않았기 때문에 생기는 일이었다.

보통 이런 문제가 있다면 스택오버플로우 질문 글을 찾는 게 제일 빠르긴 한데 카카오톡 소셜 로그인이 안 되는 건 국가적 특수성(...)이 있는 이슈이기 때문에, 차장님이 자리로 돌아가신 다음 한국어로 검색해봤다. 참고로 카카오 문제는 데브톡 들어가서 검색하는 게 제일 빨라서 로그인 키워드로 검색했고, 이 글을 발견하게 됐다. 그리고 속으로 유레카 외침. 아하 이거였구나! 아후 그냥 속이 시원해짐. 

원래 AppDelegate에는 이렇게 되어 있었다.

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
	// 생략
	if AuthApi.isKakaoTalkLoginUrl(url) {
		return AuthController.handleOpenUrl(url: url)
	}	
}

 

SceneDelgate에는 다음 코드를 추가해주면서 해결했다.

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
	if AuthApi.isKakaoTalkLoginUrl(url) {
		_ = AuthController.handleOpenUrl(url: url)
	}
}

카카오톡 로그인을 시도해보니 잘 됐다.

 

또 중요한 건 딥링크를 잘 타는지 확인을 해줘야 한다는 점이다. willConnectTo scene 메소드에 대해 userActivity 여부에 따라 scene 메소드를 추가로 호출해주는 것이 필요하다. 이게 없으면 앱이 종료된 상태에서 딥링크를 태웠을 때 해당 페이지로 이동하지 않는다. 

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }

        if let userActivity = connectionOptions.userActivities.first {
            self.scene(scene, continue: userActivity)
        } else {
            self.scene(scene, openURLContexts: connectionOptions.urlContexts)
        }

        window = UIWindow(frame: windowScene.coordinateSpace.bounds)
        window?.rootViewController = MainViewController()
        window?.windowScene = windowScene
        window?.makeKeyAndVisible()
    }
}

요렇게 해서 userActivity가 있는 경우에는 continue scene 메소드에서 해결한다.

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        if let incomingURL = userActivity.webpageURL {
            // 딥링크를 위한 동작
        }
    }

네이버, 페이스북 소셜 로그인이 안 된다는 이슈도 있어서 왠지 쎄한 느낌에 테스트폰 여러 개 바리바리 싸들고 와서 해보니까 정말 안 되는 게 있었다. 비슷한 방식으로, AppDelegate의 application(_:open:options:)에 있던 코드를 sceneDelegate에 맞게 변경해서 테스트해봤고 일단 잘 됐다.

이런 이슈들이 왜 나타나나 하고 살펴봤는데 주로 소셜 로그인하려는 플랫폼의 로그인 정보가 없어서 새로 로그인해야 할 때 로그인이 안 되고 그냥 튕겨버리는 이슈들이었다. 이거 만약에 검증 안 하고 이대로 배포가 나갔으면... 정말 상상만 해도 끔찍하다... 카플레이 하나 만들고 유저 미친 듯이 잃고 아마 나도 욕 먹었겠지...

2차 개선에서 소셜 로그인에 대해 빡센 검증을 부탁드렸으니 제발 상용 나가기 전에 빡세게 테스트하고 이슈 없이 잘 나갈 수 있었으면 하는 바람이다.

 

 

2-1. (아직) AppDelegate 라이프사이클 메소드를 SceneDelegate 라이프사이클 메소드로 복붙하거나 SceneDelegate에 맞게 잘 변경해줘야 한다

이거 URL 여는 것만의 문제가 아니라 다른 서드파티 패키지 사용에도 문제가 생길 수 있을 것 같아서 좀... 확인을 해봐야 될 것 같다.

 

일단 기본적으로, 앱을 구동했을 때 AppDelegate와 SceneDelegate의 메소드를 호출하는 순서는 다음과 같다.

func AppDelegate.willFinishLaunchingWithOptions(_:)
func AppDelegate.didFinishLaunchingWithOptions(_:)
func AppDelegate.configurationForSession(_:)
func SceneDelegate.willConnectToSession(_:)

이외에 어떤 이벤트가 일어날 때마다 호출되는 AppDelegate 라이프사이클 메소드(DidBecomeActive, WillResignActive, DidEnterBackground, WillEnterForeground, WillTerminate)는 SceneDelegate에 1:1로 옮겨져 있으므로 SceneDelegate를 사용하는 13 이상에 대해서는 SceneDelegate에서 해당 라이프사이클 메소드를 사용해 대응하면 된다. 

 

 

 

References

 

728x90
반응형
Comments