← 모든 글

watchOS 런타임은 백그라운드 작업이 아니라 계약입니다

장르: shipped-code. 이 글은 Return이 프로덕션에서 출시한 watchOS 런타임 패턴을 기록합니다. Return은 App Store에 출시된 SwiftUI 명상 타이머이며, Watch 앱은 사용자가 손목을 내려도 계속 카운트해야 하는 다중 사이클 타이머를 실행합니다.1 이 제약 조건에서 살아남는 패턴은 WKExtendedRuntimeSession과 앱 스코프의 전역 델리게이트입니다. 그 외의 모든 것은 워치가 잠드는 순간 죽습니다.

watchOS는 화면이 작아진 iOS가 아닙니다. 런타임 모델 자체가 다릅니다. iOS는 앱에 넉넉한 포그라운드 예산을 제공하고, 오디오 세션, 위치 업데이트, BGTaskScheduler, 그리고 몇 가지 다른 수단을 통해 점점 줄어들지만 실재하는 백그라운드 런타임을 제공합니다.2 watchOS는 손목을 내린 후 포그라운드 앱에 초 단위로 측정되는 예산을 주고, 그 후에는 시스템과 런타임 계약을 맺지 않은 한 앱이 일시 중단됩니다. “그냥 백그라운드에서 뭔가 하고 있어요”라는 식의 수단은 없습니다. “운동, 마음챙김 세션, 스마트 알람, 내비게이션 경로, 헬스 모니터링 작업을 실행 중입니다”가 있을 뿐, 그 외에는 없습니다.3

Return의 Watch 타깃은 마음챙김 타이머입니다. 세션 계약은 WKBackgroundModes: mindfulness입니다. 런타임 API는 WKExtendedRuntimeSession입니다. Watch 앱을 손목 내리면 망가짐 상태에서 25분 명상을 견뎌냄 상태로 바꾼 패턴을 이 글에서 설명합니다.

TL;DR

  • watchOS에는 iOS 스타일의 백그라운드가 없습니다. 포그라운드 런타임은 손목을 내린 직후 종료되며, 등록된 세션 유형만 계속 실행됩니다.
  • WKExtendedRuntimeSession은 API 표면입니다. 세션은 세션 유형을 선언해야 하며, 명상 타이머의 경우 유형은 Info.plistWKBackgroundModes: mindfulness를 통해 암묵적으로 지정됩니다.
  • 세션 매니저는 뷰 스코프가 아니라 앱 스코프에서 살아야 합니다. SwiftUI의 뷰 라이프사이클은 내비게이션 시 뷰가 소유한 객체를 해제합니다. 해제된 세션 델리게이트는 세션 자체가 여전히 실행 중이더라도 죽은 세션입니다.
  • WKExtendedRuntimeSessionDelegate 콜백이 곧 계약입니다: didStart, willExpire, didInvalidateWith. expire 콜백은 시스템이 강제 무효화하기 전에 발생하며, Apple은 이를 앱이 “마무리하고 정리할” 창구라고 설명합니다.
  • 활성 확장 세션이 없는 상태에서 손목을 내리면 타이머가 일시 중지됩니다. 활성 확장 세션이 있는 상태에서 손목을 내리면 타이머가 계속됩니다. 세션이 “출시된 제품”과 “두 번째 사용에서 망가짐”의 차이를 만듭니다.

watchOS가 iOS 방식으로 해결하지 않는 백그라운드 문제

iOS 앱은 화면이 꺼진 상태에서도 앱을 계속 실행해야 할 때 여러 백그라운드 수단에 의존합니다:2

  • .playback 카테고리의 AVAudioSession은 음악이 재생되는 동안 오디오 앱을 살려둡니다.
  • CLLocationManager 백그라운드 업데이트는 파란색 바와 함께 내비게이션 앱을 살려둡니다.
  • BGTaskScheduler는 시스템이 자체 일정에 따라 스케줄링하는 짧은 유지보수 작업을 큐에 넣습니다.
  • 포그라운드 UI 확장 (Live Activity, CallKit, PushKit)은 앱 프로세스를 시스템이 제어하는 렌더링 표면에 연결합니다.

이 중 어느 것도 여러분이 생각하는 방식으로 watchOS에서 도움이 되지 않습니다. Watch 앱에는 동일한 백그라운드 작업 스케줄러가 없습니다. 침묵 속에서 타이머를 계속 카운트하게 해주는 백그라운드 AVAudioSession.playback 모드도 없습니다. “사용자가 손목을 내린 후에도 계속 실행하고 싶다”를 위한 단 하나의 구조적 프리미티브가 있을 뿐이며, 그 프리미티브는 선언된 세션 유형을 가진 WKExtendedRuntimeSession입니다.3

Apple이 WKBackgroundModes를 통해 지원하는 세션 유형은 의도적으로 좁습니다:4

  • workout-processing (실제 운동을 위한 HKWorkoutSession과 함께)
  • mindfulness (명상 타이머와 호흡 운동용)
  • self-care (가이드 루틴용)
  • physical-therapy (물리치료 세션 앱용)
  • alarm (시간 기반 기상 알람용)
  • underwater-depth (다이빙 및 수심 추적 앱용)

이 카테고리 중 하나에 맞지 않는 앱은 WKExtendedRuntimeSession을 사용하여 손목을 내린 후 실행할 수 없습니다. 오디오 앱은 다른 코드 경로에서 mediaPlayback 오디오 세션 카테고리와 Now Playing 통합에 의존합니다. 내비게이션 앱은 CLLocationManager 백그라운드 업데이트를 사용합니다. Watch는 범용 컴퓨터가 아닙니다. 런타임 모델이 강제하는 배터리 제약이 있는 디바이스입니다.

명상 타이머는 mindfulness에 해당합니다. 계약은 다음과 같습니다: Info.plist에 백그라운드 모드를 선언하고, WKExtendedRuntimeSession을 요청하고, 델리게이트 콜백을 처리하고, 타이머가 끝나면 세션을 종료합니다. 시스템은 세션당 약 1시간까지의 런타임을 부여하며, 발열 또는 배터리 압력이 있을 경우 시스템 자체 재량으로 단축할 수 있습니다.3

Return이 출시한 패턴

이 패턴은 Info.plist 선언으로 시작합니다:4

<key>WKBackgroundModes</key>
<array>
    <string>mindfulness</string>
</array>

이 모드 선언이 세션 유형을 유효하게 만듭니다. 이것 없이 WKExtendedRuntimeSession().start()를 호출하면 자동으로 실패하고, 백그라운드 모드가 전혀 없는 Watch 앱처럼 손목을 내릴 때 앱이 일시 중단됩니다.

세션 매니저 자체는 앱 스코프에서 살아야 합니다. SwiftUI의 뷰 라이프사이클은 오래 지속되는 상태 저장 객체에 비우호적입니다: @StateObject@State는 이를 소유한 뷰로 스코프가 제한되며, 뷰를 교체하는 내비게이션 푸시는 상태도 함께 떨어뜨립니다. 세션 중간에 델리게이트가 해제된 WKExtendedRuntimeSession은 충돌하지 않습니다. 세션은 계속 실행되지만, 델리게이트 콜백 (willExpire, didInvalidateWith)이 해제된 객체에 도달합니다. 즉, 정리가 절대 일어나지 않으며, 다음 startSession() 호출은 활성 세션이 없다고 판단하여 중복 세션을 시작합니다.

출시 패턴은 앱 스코프의 싱글톤입니다. 아래 스니펫은 구조적 형태이며, 프로덕션에서는 관찰성을 위해 각 메서드 안에 로깅을 추가합니다:

import SwiftUI
import WatchKit

final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate {
    static let shared = WatchSessionManager()

    private var session: WKExtendedRuntimeSession?

    private override init() {
        super.init()
    }

    var isSessionActive: Bool {
        session != nil
    }

    func startSession() {
        guard session == nil else { return }
        let newSession = WKExtendedRuntimeSession()
        newSession.delegate = self
        newSession.start()
        session = newSession
    }

    func endSession() {
        guard let existing = session else { return }
        existing.invalidate()
        session = nil
    }

    // MARK: - WKExtendedRuntimeSessionDelegate

    func extendedRuntimeSessionDidStart(_ session: WKExtendedRuntimeSession) {}

    func extendedRuntimeSessionWillExpire(_ session: WKExtendedRuntimeSession) {
        // Apple's "about to expire / finish and clean up" hook
    }

    func extendedRuntimeSession(
        _ session: WKExtendedRuntimeSession,
        didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason,
        error: Error?
    ) {
        self.session = nil
    }
}

문서에서 언급하지 않는 그 싱글톤에 대한 세 가지 세부 사항:

@main App 수준에서 static let shared@State private var sessionManager = WatchSessionManager.shared를 결합하면 매니저가 워치 앱 프로세스의 수명 동안 살아남습니다. SwiftUI는 뷰가 싱글톤을 보유한다고 해서 그것을 유지하지 않습니다. 위의 바인딩이 런타임에 참조를 유지하라고 지시하는 것입니다. 앱 수준 바인딩이 없으면, 어떤 뷰도 매니저를 보유하지 않을 때 ARC가 매니저를 떨어뜨릴 수 있습니다.

session 프로퍼티는 중복 세션을 막는 보호 장치입니다. “처음부터 다시 시작” 버튼이 있는 타이머는 여러 경로에서 startSession()을 호출할 수 있습니다. guard session == nil 체크가 잠금 역할을 합니다. 두 개의 동시 확장 세션은 예측할 수 없는 동작을 일으킵니다: 때로는 두 번째가 성공하고 첫 번째가 고아가 되며, 때로는 시작 호출이 자동으로 실패합니다. 단일 세션 불변량이 이 전체 클래스의 문제를 방지합니다.

델리게이트 콜백은 로깅하지만 거의 작동하지 않습니다. didStart 콜백은 세션당 한 번 발생하며 관찰성에 유용한 훅입니다. willExpire 콜백은 시스템이 강제 무효화하기 전에 발생하며, Apple이 앱에게 “마무리하고 정리”하기를 기대하는 곳입니다. didInvalidateWith 콜백은 다음 startSession() 호출이 작동하도록 세션 참조를 지우는 곳입니다. 프로덕션 패턴은 콜백이 상태를 업데이트하고, 상태 머신이 작업을 수행하며, 콜백이 직접 작업을 수행하지 않습니다.

타이머 매니저는 타이머가 능동적으로 카운트되는지 여부가 변경되는 모든 전환에서 세션 매니저를 호출합니다:

final class WatchTimerManager: ObservableObject {
    func start() {
        startExtendedSession()        // -> WatchSessionManager.shared.startSession()
        // ... start the timer state machine ...
    }

    func pause() {
        timer?.invalidate()
        isRunning = false
        endExtendedSession()          // -> WatchSessionManager.shared.endSession()
    }

    func reset() {
        // ... clear timer state ...
        endExtendedSession()
    }

    private func completeCycle() {
        // ... last cycle handling ...
        endExtendedSession()          // ends on final completion
    }
}

세션은 일시 중지, 리셋, 그리고 최종 사이클 완료 시 종료됩니다. 제품 차원의 추론은 다음과 같습니다: 일시 중지된 명상은 mindfulness로 시스템이 부여한 런타임 예산을 계속 점유할 필요가 없습니다. 일시 중지에서 재개하면 새로운 세션을 다시 획득합니다. 제품 차원의 비용은 손목을 내린 채로 일시 중지된 상태는 손목을 들어올리는 것만으로는 재개할 수 없다는 것입니다. 사용자는 재개하기 위해 앱을 포그라운드로 다시 가져와야 합니다. 제품 차원의 이득은 일시 중지된 타이머의 배터리 비용이 0으로 떨어지고 시스템이 오래된 세션을 보지 않는다는 것입니다.

손목 내리기가 곧 테스트입니다

시뮬레이터의 watchOS 테스트는 정중한 허구입니다. 시뮬레이터는 실제 Apple Watch처럼 손목 내리기 런타임 모델을 강제하지 않습니다. 시뮬레이터 창에 포커스가 있는 한 시뮬레이터는 앱을 포그라운드 상태로 유지합니다. 시뮬레이터의 확장 런타임 세션은 세션이 전혀 없는 것과 동일하게 보이는데, 이는 포그라운드 앱이 어느 쪽이든 계속 실행되기 때문입니다.

실제 테스트는 실제 Apple Watch에서 이루어집니다:5

  1. 타이머를 실행합니다.
  2. 손목을 내립니다 (또는 사이드 버튼을 눌러 화면을 잠급니다).
  3. 30초 기다립니다.
  4. 다시 손목을 들어올립니다.

활성 확장 런타임 세션이 없으면 워치 앱은 일시 중단됩니다. 타이머 상태는 손목 내리기 순간에 동결되고 그 동결된 상태에서 재개됩니다. 사용자가 눈을 감는 5분 명상의 경우, 버그는 눈을 감고 있던 시간만큼 타이머가 잘못될 때까지 보이지 않습니다.

활성 확장 런타임 세션이 있으면 타이머가 계속 카운트됩니다. 손목을 들어올리면 타이머가 정확한 경과 위치에 있음이 드러납니다. 오디오 큐 (타이머가 완료 시 하나를 재생한다면)는 손목을 들어올린 시각이 아니라 정확한 벽시계 시각에 발생합니다.

손목 내리기 시나리오는 Return이 v1에서 출시하고 v2에서 패치한 버그였습니다. 수정은 위의 싱글톤 패턴이며, 버그는 내비게이션 푸시에서 해제된 SwiftUI 뷰가 보유한 WatchSessionManager 인스턴스였습니다. 시스템 측에서는 세션이 기술적으로 실행 중이었지만, 델리게이트가 해제되었습니다. 매니저의 session 프로퍼티가 이제 죽은 객체에 설정되어 있었기 때문에 다음 세션 시작 호출은 자동으로 무동작이 되었습니다. 실제 디바이스 테스트는 몇 초 안에 실패를 드러냅니다. 시뮬레이터 테스트는 절대 그것을 드러내지 않습니다.

델리게이트 콜백이 실제로 알려주는 것

WKExtendedRuntimeSessionInvalidationReason은 세션이 종료되는 방식을 열거합니다:6

사유 발생 시점
none 앱이 invalidate()를 호출하여 세션이 명시적으로 무효화되었습니다
sessionInProgress 동일한 유형의 세션이 이미 실행 중입니다
expired 시스템이 부과한 시간 제한에 도달했습니다
resignedFrontmost 세션이 실행 중에 다른 앱이 최전면이 되었습니다
suppressedBySystem 시스템이 세션을 억제했습니다 (저전력, 발열 압력)
error 복구 불가능한 오류가 발생했습니다. error 매개변수를 확인하세요

제품 설계에 중요한 사유들:

expired는 사용자가 요청한 전체 세션을 받았다는 의미입니다. 세션이 자연스러운 끝까지 실행되었습니다. Return의 가장 긴 명상 지속 시간은 60분이며, 이는 mindfulness 세션이 일반적으로 부여받는 한계 바로 직전입니다. 90분 명상은 일상적으로 expired에 도달하고 타이머가 세션 중간에 죽을 것입니다. 제품 결정은 사용 가능한 지속 시간을 런타임 모델이 실제로 제공할 수 있는 것으로 제한하는 것입니다.

resignedFrontmost는 사용자가 다른 Watch 앱을 열었고 여러분의 세션이 졌다는 의미입니다. Watch 사용자는 다른 앱으로 스와이프한 다음 잊어버리는 데 능숙합니다. 제품 결정은 사임 시 일시 중지 (상태 보존, 사용자가 돌아올 수 있음) 또는 사임 시 종료 (세션 종료, 사용자가 “일찍 멈췄다”는 신호를 받음) 중 하나를 선택하는 것입니다. Return은 사용자가 명상 중에 전화를 받고 돌아올 수 있도록 사임 시 일시 중지를 선택합니다.

suppressedBySystem은 “워치가 뜨겁다”의 정중한 버전입니다. 발열 압력이나 저배터리 상태의 watchOS 디바이스는 앱의 오용 없이도 확장 런타임 세션을 취소할 수 있습니다. 세션 매니저는 이 경우를 우아하게 처리해야 합니다: 참조를 지우고, 차단되지 않는 경고를 표면화하며, 시스템이 방금 거부한 세션을 다시 시작하려는 상태로 진입하지 않습니다.

willExpire 콜백은 세션이 곧 만료되려 할 때 발생하며, 앱이 “마무리하고 정리할” 순간으로 문서화되어 있습니다.3 이 콜백은 앱이 최종 상태 스냅샷을 작성하거나, 마무리 오디오 큐를 재생하거나, “세션이 곧 종료됨” UI를 표시할 수 있는 곳입니다. Return은 현재 콜백을 로깅만 합니다. 더 풍부한 정리 (HealthKit 로그 항목, 오디오 페이드 아웃)는 타이머의 리셋 및 완료 경로에서 발생하며, willExpire 창에 대한 다르게 만들었을 것 목록에 있습니다.

다르게 만들었을 것

Return을 처음부터 다시 시작한다면 두 가지가 있습니다.

값이 HealthKit 통합으로 증가하는 모든 세션에 대해 HKWorkoutSession을 사용하세요. 명상 타이머는 mindfulnessworkout-processing 사이의 경계에 있습니다. 데이터 모델이 더 단순하고 사용자 기대가 “이건 명상이지 운동이 아니야”이기 때문에 v1에서는 마음챙김이 올바른 선택이었습니다. HKWorkoutSession은 더 세분화된 HealthKit 통합 (세션 시작, 세션 종료, 세그먼트, 이벤트)을 수행하며, 데이터를 누적하기 위한 더 풍부한 LiveWorkoutBuilder 인터페이스를 제공합니다. 문서화된 Apple의 보장이 아니라 아키텍처적 판단입니다: 가치가 자세한 세션 텔레메트리에 의존하는 앱의 경우, 운동 세션 경로는 WKExtendedRuntimeSession이 처리하지 않는 구조를 처리합니다.

1일차부터 세션 상태 관찰성 표면을 추가하세요. Return의 첫 번째 버전은 세션 이벤트를 콘솔에 로깅했습니다. 두 번째 버전은 디버깅을 위한 디바이스 내 세션 상태 가시성을 추가했습니다. 세 번째는 세션 무효화를 블랙박스로 취급하는 대신, 무언가 잘못되었을 때 세션 사유 기록을 사용자에게 표면화하는 개발자 모드 토글을 노출할 것입니다. watchOS 런타임은 불투명합니다. 디버그 표면은 이를 보완할 필요가 있습니다.

WKExtendedRuntimeSession이 잘못된 답인 경우

세션 유형이 맞지 않는 세 가지 경우:

세그먼트 마커, 심박수 스트림, 활성 칼로리 추적이 필요한 운동. HKLiveWorkoutBuilder와 함께 HKWorkoutSession을 직접 사용하세요. Workout API는 실제 운동 (그리고 걷기 명상이나 격렬한 활동)을 위해 Apple이 문서화한 경로입니다. WKExtendedRuntimeSession은 마음챙김이나 알람과 같은 비운동 세션을 위해 문서화된 경로입니다. 명상 앱은 운동이 필요 없습니다. Couch-to-5K 앱은 필요합니다.

Now Playing 표면이 필요한 오디오 재생. watchOS 오디오 세션 자격과 함께 재생용으로 구성된 AVAudioSession을 사용하세요. Now Playing 통합과 시스템 재생 표면은 오디오 앱이 원하는 것이며, 오디오 경로는 WKExtendedRuntimeSession과 완전히 별개입니다. WKExtendedRuntimeSession은 Now Playing이나 시스템 오디오 라우팅을 제공하지 않습니다.

사용자 인지 없는 장기 실행 데이터 동기화. 시스템이 스케줄링하는 주기적 새로 고침 창에는 WKApplicationRefreshBackgroundTask를 사용하세요. 사용자는 앱에 없습니다. 앱은 계속 실행될 필요가 없습니다. 잠시 깨어나서 새로 고치기만 하면 됩니다. 두 가지 백그라운드 작업 및 확장 런타임 세션 모델은 매우 다른 요구를 충족시킵니다.

이 패턴이 watchOS 11+에 출시되는 앱에 의미하는 것

세 가지 핵심 사항.

  1. Watch 런타임 모델은 옵트인입니다. 세션 유형을 선택하고 그 규칙 안에서 사세요. watchOS에서 “범용 백그라운드 작업”을 시도하는 앱은 패배할 것입니다. mindfulness, workout-processing, self-care, physical-therapy, alarm 또는 underwater-depth를 선택하고, 선택한 세션 유형이 동반하는 런타임 예산을 중심으로 사용자 경험을 설계하세요.

  2. 세션 델리게이트는 앱 스코프에서 살아야 합니다. SwiftUI의 뷰 라이프사이클은 오래 지속되는 상태 저장 객체를 보호하지 않습니다. @main App 수준에서 바인딩된 static let shared 싱글톤이 내비게이션 푸시, 뷰 교체, SwiftUI의 일반적인 해제 동작에서 살아남는 가장 작은 패턴입니다.

  3. 실제 하드웨어에서 테스트하세요. 시뮬레이터는 손목 내리기 런타임 모델을 강제하지 않습니다. Watch 앱이 시뮬레이터에서 테스트할 수 없는 버그가 곧 사용자에게 출시하는 버그입니다.

이 글을 같은 앱 계열에 대한 이전 글들과 함께 짝지어 보세요: 크로스 플랫폼 SwiftUI 출시 (Return은 iPhone, iPad, Watch, Mac, Apple TV에 출시됩니다); Live Activities 상태 머신 (동일한 타이머의 iOS 측 표면); HealthKit 패턴 (Watch의 마음챙김 세션이 사용자의 Health 데이터에 안착하는 곳). 전체 세트는 Apple Ecosystem Series 허브에 있습니다. 더 폭넓은 iOS와 AI 에이전트 컨텍스트는 iOS Agent Development guide를 참조하세요.

FAQ

watchOS 확장 런타임 세션이란 무엇인가요?

watchOS 확장 런타임 세션 (WKExtendedRuntimeSession)은 Watch 앱이 사용자가 손목을 내린 후에도 계속 실행하기 위해 사용하는 API입니다. 세션은 Info.plistWKBackgroundModes를 통해 유형 (mindfulness, workout-processing, alarm 등)을 선언해야 합니다. 활성 확장 세션이 없으면 watchOS는 손목을 내린 직후 앱을 일시 중단합니다.

사용자가 손목을 내릴 때 watchOS 타이머가 카운트를 멈추는 이유는 무엇인가요?

지원되는 유형의 활성 WKExtendedRuntimeSession이 실행 중이지 않으면 Watch 앱은 손목을 내린 직후 일시 중단됩니다. 그러한 세션을 시작하지 않는 타이머 매니저는 백그라운드 런타임이 차단되며, 사용자가 다시 손목을 들어올릴 때까지 타이머 상태가 손목을 내린 순간에 동결됩니다.

WKExtendedRuntimeSessionHKWorkoutSession의 차이점은 무엇인가요?

WKExtendedRuntimeSession은 mindfulness, alarm, self-care와 같은 비운동 세션을 위한 범용 확장 런타임 API입니다. HKWorkoutSession은 실제 운동을 위한 API입니다. HealthKit과 통합되고, 세그먼트 마커를 지원하며, 걷기 명상이나 격렬한 활동을 위해 문서화된 경로입니다. 운동 등급의 텔레메트리가 없는 마음챙김 앱은 첫 번째를 사용합니다. 운동 앱은 두 번째를 사용합니다.

시스템이 내 확장 런타임 세션을 취소할 수 있나요?

네. WKExtendedRuntimeSessionInvalidationReason에는 expired (시스템 시간 제한 도달), resignedFrontmost (다른 Watch 앱이 최전면이 됨), suppressedBySystem (저전력 또는 발열 압력)이 포함됩니다. 세션 매니저는 각각을 깔끔하게 처리해야 합니다: 참조가 지워지고, 타이머 상태가 적절하게 반응하며, 다음 세션 시작 호출이 올바르게 작동합니다.

SwiftUI watchOS 앱에서 세션 매니저는 어디에 있어야 하나요?

앱 스코프에서, @main App 구조체에서 바인딩된 싱글톤으로 있어야 합니다. SwiftUI의 뷰 스코프 상태 (@State, @StateObject)는 내비게이션 푸시, 뷰 교체 또는 앱 백그라운딩 시 해제됩니다. 세션 중간에 해제되는 뷰 소유의 세션 델리게이트는 세션 참조를 누수시키고 후속 세션이 깨끗하게 시작되는 것을 방지합니다.

References


  1. 저자의 Return, 2026년 4월 21일 App Store에 출시된 SwiftUI 명상 타이머로, iPhone, iPad, Mac, Apple Watch 및 Apple TV에서 사용 가능합니다. Watch 앱은 사이클 타이머 런타임을 위해 mindfulness 백그라운드 모드와 함께 WKExtendedRuntimeSession을 사용합니다. 

  2. Apple Developer, “About the background execution sequence”. iOS 측 백그라운드 런타임 수단 (오디오 세션, 위치, BGTaskScheduler) 및 watchOS와의 차이점. 

  3. Apple Developer, “WKExtendedRuntimeSession”. 세션 유형, 라이프사이클, 델리게이트 콜백, 런타임 제한 및 WKBackgroundModes Info.plist 키. 

  4. Apple Developer, “Information Property List: WKBackgroundModes”. 지원되는 세션 유형 문자열: workout-processing, mindfulness, self-care, physical-therapy, alarm, underwater-depth

  5. Apple Developer, “Building a watchOS app” 및 WatchKit 테스트 가이드. 실제 디바이스 런타임 동작은 watchOS 시뮬레이터에서 재현할 수 없습니다. 시뮬레이터는 손목 내리기 일시 중단을 강제하지 않습니다. 

  6. Apple Developer, “WKExtendedRuntimeSessionInvalidationReason”. 열거형 케이스: none, sessionInProgress, expired, resignedFrontmost, suppressedBySystem, error

관련 게시물

HealthKit + SwiftUI on iOS 26: Authorization, Sample Types, and Cross-Platform Patterns

Real production patterns from Water (water tracking, HKQuantitySample) and Return (mindful sessions, HKCategorySample). …

17 분 소요

What SwiftUI Is Made Of

SwiftUI is a result-builder DSL on top of a value-typed View tree. Once the substrate is visible, AnyView, Group, and Vi…

17 분 소요

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 분 소요