logo hsb.horse
← 블로그 목록으로 돌아가기

블로그

Shield Extension에서 메인 앱으로의 언락을 App Group 경유로 구현하기

ShieldActionDelegate에서 메인 앱을 직접 실행할 수는 없다. App Group 공유 스토리지에 언락 요청을 기록하고 앱 쪽에서 읽어 가는 handoff 패턴을 정리했다.

게시일:

ShieldActionDelegate 에서 실드 버튼 처리를 쓰다 보면 “여기서 메인 앱을 열고 언락 화면을 띄우고 싶다”는 순간이 온다. 그런데 그 흐름은 그대로는 쓸 수 없다. iOS를 막 시작한 상태라면 왜 이게 안 되는지 이해하기 어려운 부분이라서 배경부터 정리한다.

왜 Extension에서 앱을 직접 열 수 없는가

ShieldActionDelegate 가 반환할 수 있는 값은 .close.defer 두 가지뿐이다. 다른 플랫폼이라면 URL 스킴으로 앱을 실행하는 코드를 쓰는 자리지만, 실드 문맥에서는 그 경로가 동작하지 않는다.

Apple이 이 경로를 막아 둔 이유는 실드 경험의 일관성을 지키기 위해서라고 보는 편이 자연스럽다. 실드가 “탈출구”가 되어 버리면 차단의 의미가 없어진다. Extension에서 메인 앱으로 직접 점프를 허용하면 구현 방식에 따라 제한을 스스로 해제할 수 있게 되기 때문이다. iOS의 Extension 전반에 공통적인 이야기지만, Extension에서 메인 앱을 직접 조작하는 경로는 기본적으로 제한되어 있다.

App Group을 통한 handoff 패턴

현실적인 구현은 스토리지를 경유하는 비동기 handoff다.

App Group은 메인 앱과 Extension이 데이터를 공유하기 위한 구조다. 일반적인 iOS 앱 프로세스는 서로의 데이터에 접근할 수 없지만, 같은 App Group에 속해 있으면 공유 UserDefaults 나 파일 컨테이너를 읽고 쓸 수 있다.

Shield Action Extension 쪽에서 App Group 공유 컨테이너에 “언락 요청이 있다”는 플래그를 기록한다. 사용자가 나중에 앱을 수동으로 열면 메인 앱이 그 플래그를 읽고 언락 플로우를 시작한다.

// Shield Action Extension 内
override func handle(
action: ShieldAction,
for webDomain: WebDomainToken,
completionHandler: @escaping (ShieldActionResponse) -> Void
) {
let defaults = UserDefaults(suiteName: "group.com.example.stoicdns")
defaults?.set(true, forKey: "pendingUnlockRequest")
defaults?.set(Date(), forKey: "unlockRequestedAt")
completionHandler(.close)
}
// メインアプリの起動時 / フォアグラウンド復帰時
func checkPendingUnlock() {
let defaults = UserDefaults(suiteName: "group.com.example.stoicdns")
guard defaults?.bool(forKey: "pendingUnlockRequest") == true else { return }
defaults?.removeObject(forKey: "pendingUnlockRequest")
showUnlockFlow()
}

실드는 .close 로 닫힌다. 사용자는 한 번 홈 화면으로 돌아간 뒤 앱을 직접 열어야 한다.

타임스탬프를 함께 저장하는 이유

요청 기록과 동시에 시각도 저장해 두면 오래된 요청을 무시하는 로직을 쓰기 쉬워진다. “30분 이상 지난 요청은 무효” 같은 처리를 넣으면 앱을 오래 열지 않았을 때 의도치 않게 언락 화면이 뜨는 문제를 막을 수 있다.

UX 설계의 일부로 받아들이기

“실드에서 한 번 탭해서 바로 언락 화면으로”라는 흐름을 기대하면 이 패턴이 답답하게 느껴질 수도 있다. 하지만 이게 Apple 인터페이스가 허용하는 범위다.

다르게 보면, 실드를 보고 앱을 직접 여는 그 마찰 자체가 셀프컨트롤의 일부이기도 하다. 한 번 더 멈추는 단계가 들어가는 편이 제품 목표에 더 맞을 수도 있다.

구현 측면에서는 이 제약을 받아들이고 진행하는 편이 빠르다.