23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
저는 AI Comm Client 팀에서 에이닷 통화녹음과 에이닷 전화 iOS 개발을 하고 있는 이준영입니다.
올해로 2년차 주니어 개발자가 되었는데요, 그간 한 일 중에서 가장 기억 남는 과제는 에이닷 watchOS 앱 개발입니다.
Apple Watch는 올해로 출시한 지 10년이 되었습니다.
하지만 부족한 하드웨어 스펙 등으로 인해 iPhone에 비해 제공할 수 있는 기능들이 부족해, 앱 개수도 적고 참고할 만한 개발 자료도 다소 적습니다.
그렇다보니 개발하면서 여러 어려움을 겪었는데요, 이번 게시글에서는 Notification 관련 기능을 개발하며 겪었던 일들에 대해서 다뤄 보겠습니다.
이번 게시글에서는, Apple에서 제공하는 Creating a watchOS app 샘플 프로젝트를 사용합니다.
직접 빌드해서 확인해 보고 싶은 분들은, Apple Developer 사이트에서 해당 프로젝트를 다운로드 받으시면 됩니다.
그리고 본 게시글은 아래 환경을 기준으로 작성된 내용이므로, 향후 Apple의 OS 업데이트에 따라 동작이 달라질 수 있습니다.
iOS 16, iOS 17
watchOS9, watchOS 10
마지막으로, 본 게시글을 이해하기 위해서는 SwiftUI에 대한 기본적인 이해가 필요합니다.
만약 SwiftUI에 대해 잘 모르신다면, Apple의 Introducing SwiftUI를 먼저 보시는 것을 추천드립니다.
Apple 공식 문서에서는, Notification이 iPhone과 Apple Watch 중 어디에 등장하는지에 대해 아래와 같이 설명하고 있습니다.
Notification 종류 | Source | Destination |
|---|---|---|
Local | iOS 앱 | iPhone과 Apple Watch의 잠금 상태에 따라 결정됨 |
Local | watchOS 앱 | Apple Watch에만 등장함 |
Remote | 서버 | iPhone으로 Notification을 전송한 경우, iPhone과 Apple Watch의 잠금 상태에 따라 결정됨. watchOS 6 이상에서 Apple Watch로 직접 Notification을 전송한 경우, Apple Watch에만 등장함 |
Background | 서버 | iPhone으로 Notification을 전송한 경우, iPhone에만 등장하고 절대 Apple Watch로 전달되지 않음. watchOS 6 이상에서 Apple Watch로 직접 Notification을 전송한 경우, Apple Watch에만 등장함 |
만약 Notification이 어느 기기로든 전송될 수 있는 경우, 시스템은 기기의 잠금 상태를 사용하여 최적의 목적지를 결정합니다.
시스템은 다음 규칙을 나열된 순서대로 적용합니다 :
사용자의 iPhone이 잠금 해제되고 화면이 켜져 있는 경우, 알림은 iPhone으로 전송됩니다.
그렇지 않으면, Apple Watch가 사용자의 손목에 착용되어 있고 잠금 해제된 경우, 알림은 Apple Watch로 전송됩니다.
그렇지 않으면, 알림은 기본적으로 iPhone으로 다시 전송됩니다.
또한, 사용자가 여러 개의 Apple Watch를 가지고 있는 경우, Local Notification은 알림을 예약한 기기에만 나타납니다.
Remote/Background Notification도 지정된 Apple Watch에만 나타나므로 여러 기기로 Notification을 보낼 필요가 있을 수 있습니다.
위 설명에서 2번 항목을 보면, Apple Watch가 사용자의 손목에 착용되어 있고 잠금 해제된 경우에 Apple Watch로 알림이 전송된다고 나와 있습니다.
시스템은 Apple Watch가 손목에 착용되어 있는 것은 어떻게 알 수 있을까요?
iPhone의 Watch 앱을 보면, 손목 인식이라는 설정이 있습니다.
설명만 읽어보면 암호 설정이 되어 있을 때 Apple Watch를 잠그는 것과 관련 있는 내용으로 보입니다.
하지만 설명과는 다르게, 암호를 사용하지 않더라도 손목 인식 옵션은 켜고 끄는 것이 가능합니다.
즉 이 옵션은 시스템이 실시간으로 Apple Watch 착용 여부를 판단하게 해 주는 것입니다.
앞서 'Apple Watch가 사용자의 손목에 착용되어 있고 잠금 해제된 경우에 Apple Watch로 알림이 전송된다'고 말씀 드렸습니다.
손목 인식이 꺼져 있으면 어떻게 될까요? 정답은 'iPhone과 Apple Watch에 모두 전송된다'입니다.
Apple에서 위와 같이 동작하는 지에 대한 설명이 따로 없어, 제가 추론한 내용을 정리했습니다(공식적인 설명이 아니므로, 참고 부탁드립니다).
Apple에서 말하는 iPhone의 잠금 상태, 그리고 Apple Watch의 잠금상태/손목 착용 여부는 곧 사용자가 현재 어떤 기기를 사용하고 있는지를 판단하기 위한 기준입니다.
Apple Watch는 iPhone과 달리, 화면이 항상 켜져 있는 경우는 잘 없습니다.
사용자들은 대부분 Apple Watch를 일반 시계처럼 착용하고 일부 필요한 경우에만 화면을 터치하기 때문입니다.
따라서 "잠금 상태" 만으로는 Apple Watch의 사용 여부를 판단하기 힘들기 때문에, "손목 착용 여부"도 함께 판단 기준으로 사용합니다.
그래서 손목 인식이 꺼져 있는 경우에는 Apple Watch의 사용 여부를 판단할 수 없으므로, iPhone과 Apple Watch의 잠금 상태와 무관하게 두 기기에 모두 Notification이 등장합니다.
이를 정리하면 아래와 같습니다.
Apple Watch 손목 인식 | iPhone | Apple Watch | Notification이 나타나는 기기 |
|---|---|---|---|
O | 잠금 ⭕ | 잠금 ❌ | Apple Watch |
O | 잠금 ⭕ | 잠금 ⭕ | iPhone |
O | 잠금 ❌ | 잠금 ❌ | iPhone |
O | 잠금 ❌ | 잠금 ❌ | iPhone |
X | 무관 | 무관 | iPhone, Apple Watch |
서버에서 Apple Watch를 타깃으로 Remote Notification을 보내면, Apple Watch에만 등장한다고 앞서 말씀드렸습니다.
그런데 여기에는 큰 문제가 하나 있습니다. 바로 Apple Watch에 Notification이 등장하기까지 시간이 다소 걸린다는 것입니다.
Apple Watch가 iPhone과 연결이 끊어진 상태에서는 Remote Notification이 바로 등장하지만, iPhone과 연결된 상태에서는 약 10-15초 정도 지연되어 등장합니다.
공식 문서 어디에도 관련된 내용을 찾을 수 없었는데요, Apple 개발자 포럼에서 정답을 알 수 있었습니다.
If you have both a phone and watch app installed, watchOS and iOS try to coordinate between phone and watch notifications.
위 내용을 통해 아래와 같이 추정할 수 있었습니다.
Apple Watch에 바로 Remote Notification을 보내더라도, iPhone과 Notification 등장을 조율하는 것 같음
동일한 Notification을 iPhone과 Apple Watch에 둘 다 보낼 경우, 동일한 Notification이 두 단말에 모두 등장하는 것을 방지하기 위해 존재하는 것으로 예상됨
Apple Watch가 단독으로 존재하는 상황에서는 조율할 대상이 없기 때문에, 지연 없이 바로 Notification이 노출됨
Apple Watch에 단독으로 Notification을 등장시키기 위해 Remote Notification을 사용하실 때는, 이 점을 반드시 알고 사용하는 게 좋을 것 같습니다.
Apple Watch에서 Notification을 UI에 표현하는 방식은 Short-look Interface, Long-look Interface 총 2가지입니다.
Short-look Interface
Long-look Interface를 노출하기 전, Notification에 대한 간략한 정보를 노출하는 화면
Long-look Interface
Notification에 대한 세부적인 정보를 노출하는 화면
Apple Watch가 알림을 수신하면 Short-look Interface가 잠시 노출되고, 이후 약 1-3초가 지난 후 Long-look Interface가 노출됩니다.
Short-look Interface는 watchOS가 자동으로 생성하는 화면이고, 아래 3가지 정보를 노출합니다.
앱 아이콘
First Line
Second Line
공식 문서 상에는 First Line에는 앱 이름, Second Line에는 Notification Title이 노출된다고 나와 있습니다.
실제로는 Notification Payload 값, 그리고 Apple Watch 설정(Watch 앱 > 알림 > 탭하여 전체 알림 보기)에 따라 First Line과 Second Line에 노출되는 값이 바뀝니다.
탭하여 전체 알림 보기 | Notification Payload 구성 요소 | First Line | Second Line |
|---|---|---|---|
ON | Title만 존재 | Notification Title | 앱 이름 |
ON | Body만 존재 | 앱 이름 | Notification Body |
ON | Title & Body 존재 | Notification Title | Notification Body |
Off | Title만 존재 | Notification Title | 앱 이름 |
Off | Body만 존재 | 앱 이름 | "알림" 텍스트 |
Off | Title & Body 존재 | Notification Title | 앱 이름 |
참고로 “알림” 텍스트는 진짜 말 그대로 “알림”이 노출됩니다.
Long-look Interface는 개발자가 정의하는 화면이고, 아래 정보들을 노출합니다.
System-provided Sash
App Content
SwiftUI View를 노출하기 때문에 Image, Button 등 원하는 형태의 화면 노출 가능
App-defined actions
개발자가 정의하는 Action들이 노출
System-provided Dismiss Button
watchOS가 기본적으로 제공하는 “닫기” 버튼, 임의로 설정 불가
Long-look Interface는 Short-look Interface와 다르게 개발자가 설정할 수 있는 내용이 많습니다. 아래에서 자세하게 살펴보겠습니다.
Long-look Interface는 **WKNotificationHostingController**를 통해 정의할 수 있습니다. 한 번 살펴볼까요?
@MainActor open class WKUserNotificationHostingController<Body> : WKUserNotificationInterfaceController where Body : View {
@MainActor open class var isInteractive: Bool { get }
@MainActor open class var sashColor: Color? { get }
@MainActor open class var wantsSashBlur: Bool { get }
@MainActor open class var titleColor: Color? { get }
@MainActor open class var subtitleColor: Color? { get }
@MainActor open class var coalescedDescriptionFormat: String? { get }
@MainActor open var body: Body { get }
@MainActor override dynamic public init()
}SwiftUI에 익숙하신 분들은 눈치채셨겠지만, WKUserNotificationHostingController는 Long-look Interface 구현 시 WatchKit에서 SwiftUI를 사용하기 위해 정의된 클래스입니다.
UIKit과 SwiftUI를 같이 써보신 분들은 **UIHostingController**를 알고 계실텐데요,
WKUserNotificationHostingController도 비슷합니다. WatchKit 기반의 Long-look Interface가 SwiftUI를 사용하기 위해 정의된 클래스입니다.
watchOS 6부터 SwiftUI를 부분적으로 지원하기 시작했고, watchOS 10 기준으로는 앱 내 모든 View는 SwiftUI를 통해 구현할 수 있습니다.
하지만 아직 Notification의 경우에는 WatchKit 기반으로 구현해야 합니다.
물론 Long-look Interface의 App Content에 해당하는 영역,
즉 실제 노출되는 화면은 모두 SwiftUI로 구성이 가능하기 때문에 Life Cycle이나 Notification 관련 설정만 WatchKit을 통해 구현한다고 생각하시면 될 것 같습니다.
WKUserNotificationHostingController에서는 UI와 관련된 설정만 정의되어 있고, 나머지는 모두 상위 클래스에 정의되어 있습니다.
클래스 상속 구조는 아래와 같습니다.
WKUserNotificationInterfaceController와 WKInterfaceController의 경우, 다양한 프로퍼티와 메소드를 정의하고 있습니다.
이번 게시글에서는 Long-look Interface 구현과 관련된 최소한의 내용만 알아보도록 하겠습니다.
// WKInterfaceController
@MainActor
open class WKInterfaceController: NSObject {
// Push나 Modal을 수행한 Controller로부터 전달 받은 Context
open func awake(withContext context: Any?)
// Watch Interface가 활성화 되었고, 업데이트 가능할 때 호출됩니다.
// UI 상 노출되지 않더라도 호출될 수 있습니다.
open func willActivate()
// Watch Interface가 UI에 노출될 때 호출됩니다.
open func didAppear()
// Watch Interface가 더 이상 UI에 노출되지 않을 때 호출됩니다.
open func willDisappear()
// Watch Interface가 비활성화 되고, 더 이상 업데이트가 불가능할 때 호출됩니다.
open func didDeactivate()
...
}WKInterfaceController는 UIKit의 UIViewController 역할을 하는 클래스입니다.
위 코드 블록에는 Life Cycle을 확인할 수 있는 메소드 위주로 추렸습니다.
총 5가지의 메소드가 있고, 호출 순서는 **awake → willActivate → didAppear → willDisappear → didDeactivate`**입니다.
실제로 그런지 확인해 볼까요?
class NotificationController: WKUserNotificationHostingController<NotificationView> {
...
override func awake(withContext context: Any?) {
print("🔧 awake with context: \\(String(describing: context))")
}
override func willActivate() {
print("🔧 willActivate")
}
override func didAppear() {
print("🔧 didAppear")
}
override func willDisappear() {
print("🔧 willDisappear")
}
override func didDeactivate() {
print("🔧 didDeactivate")
}
}
// Console Output
🔧 awake with context: nil
🔧 willActivate
🔧 didAppear
🔧 willDisappear
🔧 didDeactivate
🔧 willDisappear // <-- willDisappear가 didDeactivate 뒤에 한 번 더 불리는 현상이 있습니다 (watchOS 9, 10 기준)Long-look Interface가 등장해서 사라질 때까지 로그를 print로 출력했습니다.
다 정상적으로 나오는 것 같은데, didDeactivate 뒤에 willDisappear가 한 번 더 불리고 있네요?
공식 문서상에 willDisappear가 여러 번 호출될 수 있다는 말이 없는 것으로 보아, watchOS 버그로 예상됩니다.
@MainActor open class WKUserNotificationInterfaceController : WKInterfaceController {
...
// notificationActions는 didRecieve(_:)가 호출되었을 때 한 번만 변경할 수 있습니다.
open var notificationActions: [UNNotificationAction]
// Notification Interface가 화면에 노출되기 전에 불립니다.
open func didReceive(_ notification: UNNotification)
open func performNotificationDefaultAction()
open func performDismissAction()
}WKUserNotificationInterfaceController는 WKInterfaceController를 상속해 Notification과 관련된 프로퍼티와 메소드를 추가로 정의하고 있습니다.
하나씩 살펴보겠습니다.
우선 notificationActions는 Long-look Interface 하단에 노출되는 App-defined actions에 해당합니다.
iOS에서, 알림센터에 노출된 배너를 꾹 눌렀을 때 나타나는 Action과 동일합니다.
하지만 Notification Action 측면에서 iOS와 가장 큰 차이점이 바로 이 부분인데요,
그 이유는 동적으로 Action을 변경할 수 있기 때문입니다. 예시를 한 번 보겠습니다.
class NotificationController: WKUserNotificationHostingController<NotificationView> {
...
override func didReceive(_ notification: UNNotification) {
let modelData = ModelData()
let notificationData = notification.request.content.userInfo as? [String: Any]
let aps = notificationData?["aps"] as? [String: Any]
let alert = aps?["alert"] as? [String: Any]
title = alert?["title"] as? String
message = alert?["body"] as? String
if let index = notificationData?[landmarkIndexKey] as? Int {
landmark = modelData.landmarks[index]
// index에 따라 다른 Notification Action 노출
switch index {
case 1...3:
let action = UNNotificationAction(identifier: UUID().uuidString, title: "🎉🎉🎉", options: .foreground)
self.notificationActions = [action]
default:
let action = UNNotificationAction(identifier: UUID().uuidString, title: "❤️❤️❤️", options: .destructive)
self.notificationActions = [action]
}
}
}
}샘플 프로젝트 > PushNotificationPayload.apns 파일 내 "landmarkIndex" 값에 따라, 다른 형태의 Notification Action이 노출되도록 설정했습니다. 한번 확인해 봅시다.
landmarkIndex = 1인 경우 | landmarkIndex = 5인 경우 |
|---|---|
"landmarkIndex": 1일 때는 🎉🎉🎉가 노출되고, "landmarkIndex": 5일 때는 ❤️❤️❤️가 노출되는 것을 확인할 수 있습니다.
보신 것처럼 동일한 Long-look Interface에 대해 Notification Action을 다르게 노출할 수 있습니다.
사용자 타입에 따라 다른 Action을 노출하는 등에 활용할 수도 있겠네요.
여기서 중요한 점이 2가지 있습니다.
첫 번째는, 반드시 **didReceive(_:)**를 구현해야 한다는 것입니다. 만약 구현하지 않으면 아래와 같은 오류를 Console에서 볼 수 있는데요.
… doesn't implement didReceiveNotification: or didReceiveNotification:withCompletion:. Just dropping the notification
**didReceive(_:)**를 구현하지 않았기 때문에, Long-look Interface를 등장시키지 않고 그냥 무시하게 됩니다.
물론 Notification이 아예 등장하지 않는 것은 아니고, Long-look Interface가 아닌 watchOS가 생성하는 일반 Notification의 형태로 등장합니다
(Long-look Interface로 설정하지 않은 모든 알림은 watchOS가 생성하는 일반 Notification의 형태를 따릅니다).
따라서 이 점을 반드시 명시하셔야 합니다.
두 번째는 반드시 notificationActions를 didReceive(_:) 내에서만 변경해야 한다는 것입니다.
다른 메소드 (didAppear 등)에서는 변경할 수 없습니다.
여기까지만 보면, watchOS에서 제공하는 App-defined Action이 매력적으로 보입니다.
동일한 Notification에 대해서도, Notificaion Payload 값에 따라 다른 Notification Action을 등장시킬 수 있기 때문입니다.
아쉽게도 하나 문제점이 있습니다. 바로 일반 Notification과 Long-look Interface를 앱 내에서 함께 사용할 때입니다.
(ex. 통화 요약 알림은 일반 Notification으로, 전화 수신 알림은 Long-look Interface로 제공)
보신 것처럼 일반 Notification이 등장한 상태에서 Long-look Interface를 위한 Notification을 수신하는 경우,
일반 Notification에 Long-look Interface의 Notification Action이 노출되는 버그가 있습니다.
정상 동작은 일반 Notification을 Dismiss한 이후 Long-look Interface가 등장하고, Long-look Interface의 Notification Action이 일반 Notification에 노출되지 않는 것입니다.
따라서 일부 Notification만 Long-look Interface로 등장시키고 싶으시다면, 이 부분을 염두에 두시고 진행하시면 좋을 것 같습니다.
이번에는 performNotificationDefaultAction과 performDismissAction에 대해 알아보겠습니다.
UNNotificationAction은 서로 다른 Action을 구분하기 위해 identifier: String을 프로퍼티로 가집니다.
이 중 identifier의 값이 UNNotificationActionIdentifier를 가지는 Action이 바로 defaultAction에 해당합니다.
그러면 이 defaultAction은 개발자가 지정할 수 있을까요?
정답은 “아니다”입니다. 이미 watchOS에서 ‘화면에 노출된 Notification 그 자체를 누르는 경우’로 정의하고 있기 때문에, 개발자가 별도로 지정할 수는 없습니다.
단순히 Notification을 눌러서 앱에 진입한 경우와, 특정한 Notification Action에 해당하는 버튼을 눌러서 앱에 진입한 경우를 구분하고 싶을 때 사용할 수 있을 것 같네요.
그러면 performNotificationDefaultAction을 수행하면 어떻게 될까요? 한번 확인해 봅시다.
class NotificationController: WKUserNotificationHostingController<NotificationView> {
...
override func didAppear() {
// 화면에 노출되고 나서 2초 뒤에 실행
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
guard let self else { return }
self.performNotificationDefaultAction()
}
}
}보신 것처럼, 화면에 노출되고 2초 뒤에 앱이 실행됩니다.
그러면 '특정 Notification에 대해 앱이 즉시 실행되게 할 수 있겠네?'라는 생각이 자연스럽게 드실 수 있습니다.
아쉽지만 그렇게는 동작하지 않습니다. 일정 시간 딜레이를 주지 않고 performNotificationDefaultAction을 수행하면, Default Action이 수행되지 않습니다.
performDismissAction은 System-defined dismiss action, 즉 Long-look Interface에 기본 제공되는 “닫기” 동작을 수행합니다.
이 역시 performNotificationDefaultAction와 유사하게, Long-look Interface가 화면에 노출되고 일정 시간 후부터 정상 동작하게 됩니다.
Long-look Interface의 장점 중 하나는, 여러 개의 Notification에 대해 제공할 수 있다는 것입니다.
Long-look Interface는 Notification Category에 맞게 등장합니다.
UNNotificationCategory.identifier가 바로 Notification Category를 식별할 수 있는 값입니다.
Remote Notification의 경우에는 APNs Payload 내 **aps["category"]`**필드가 해당합니다.
// 샘플 프로젝트 > PushNotificationPayload.apns
{
"aps": {
...
"category": "LandmarkNear", <-- CategoryIdentifier
},
...
}watchOS 앱이 Notification을 수신하면, 해당 Notification의 Category를 확인한 다음 그에 맞는 Long-look Interface를 등장시키게 됩니다.
이를 위해선 몇 가지 작업이 필요한데요, 한번 확인해 보겠습니다.
우선 Long-look Interface를 구성할 WKUserNotificationHostingController를 정의해야 합니다.
샘플 프로젝트의 NotificationController가 여기에 해당하는데요, 앞서 살펴보았으니 여기에선 넘어가겠습니다.
이제 원하는 WKUserNotificationHostingController와 Notification Category를 연결해야 합니다.
이를 위해선 WKNotificationScene을 선언해 줘야 합니다. 이름에서 알 수 있듯이 Scene이기 때문에, SwiftUI의 App 내에 정의해 줘야 합니다.
샘플 프로젝트의 LandmarksApp.swift 파일을 살펴볼까요?
@main
struct LandmarksApp: App {
...
var body: some Scene {
...
#if os(watchOS)
WKNotificationScene(controller: NotificationController.self, category: "LandmarkNear")
#endif
}
}보시면 controller로 NotificationController.self, category로 **"LandmarkNear"**를 전달하고 있습니다.
이를 통해 watchOS는 **"LandmarkNear"**를 Category Identifier로 가지는 Notification을 수신하면 NotificationController로 구성된 Long-look Interface를 띄울 수 있습니다.
예시로 간단한 텍스트를 보여주는 Long-look Interface를 추가로 정의했습니다.
// HelloWorldNotificationController.swift
class HelloWorldNotificationController: WKUserNotificationHostingController<HelloWorldView> {
private var maxTimerCount: CGFloat = 0
override var body: HelloWorldView {
HelloWorldView()
}
override func didReceive(_ notification: UNNotification) {
super.didReceive(notification)
}
}
// HelloWorldView.swift
struct HelloWorldView: View {
var body: some View {
VStack {
Text("🌏")
.font(.largeTitle)
Text("Hello, World!")
.font(.body)
.bold()
}
}
}
// HelloWorldNotification.apns
{
"aps": {
"alert": {
"title": "Hello, World",
},
"category": "HelloWorld",
"thread-id": "777"
},
"Simulator Target Bundle": "com.example.apple-samplecode.Landmarks.watchkitapp"
}
//LandmarksApp.swift
@main
struct LandmarksApp: App {
...
var body: some Scene {
...
#if os(watchOS)
WKNotificationScene(controller: NotificationController.self, category: "LandmarkNear")
WKNotificationScene(controller: HelloWorldNotificationController.self, category: "HelloWorld")
#endif
}
}watchOS 앱 개발을 시작하면서, 가장 당황스러웠던 점은 로그가 보이지 않는 것이었습니다.
물론 print로 출력하는 내용은 Xcode 콘솔에 잘 보였지만, Logger 등을 통해 출력한 로그는 전혀 보이지 않았습니다.
iPhone과 다르게, Apple Watch에서 로그를 보기 위해서는 Sysdiagnose 프로파일을 설치해야 합니다.
해당 프로파일은 Apple에 버그 리포트 등을 하기 위한 시스템 로그 수집할 때 사용하는 프로파일인데요, 이유는 모르겠지만 Apple Watch는 해당 프로파일을 반드시 설치해야 로그를 출력할 수 있었습니다.
해당 프로파일은 Apple Developer 사이트에서 설치하실 수 있습니다.
프로파일 다운로드 후, 로그 수집을 원하는 Apple Watch와 연결된 iPhone에서 해당 프로파일을 설치하시면 됩니다.
watchOS 앱 개발을 하다보면, 디버깅을 위한 로그 수집도 매우 중요합니다.
로컬에 파일을 저장하고, 해당 파일을 추출하는 방식 등으로 구현하실 수 있을 것 같네요.
그러나 개발한 앱 로그뿐만 아니라, 시스템이 남긴 로그도 봐야 하는 경우가 종종 있습니다.
iOS 앱 등을 개발하시는 분들이라면 Mac과 연결한 다음 Console 앱을 통해서 시스템 로그를 보면 된다고 생각하실 수 있습니다.
문제는 이 방식이 Apple Watch에서는 다르게 동작한다는 점입니다.
Apple Watch의 경우 Mac에서 로그를 보려면 반드시 iPhone과 연결된 상태에서만 가능하고, iPhone과 연결이 끊어진 상태에서는 볼 수 없습니다.
하지만 앞서 말씀드린 Sysdiagnose 기능을 사용하면, iPhone과의 연결과 상관 없이 로그를 수집할 수 있습니다.
그리고 앞서 말씀드린 것처럼, 본래 Sysdiagnose는 Apple에 버그 리포트 등을 위한 기능이기 때문에 앱 뿐만 아니라 OS가 남기는 로그까지 함께 볼 수 있습니다.
Apple Watch에서 Sysdiagnose를 수집하는 방법은 아래와 같습니다.
Apple Watch의 Digital Crown과 측면 버튼을 동시에, 약 2초간 누르기 (손가락을 버튼에서 뗀 이후, 햅틱 피드백이 오면 로그 수집 시작됩니다)
iPhone > Watch 앱 > 일반 > 진단 로그 > SYSDIAGNOSE 섹션에서 수집된 로그 파일 다운로드
확인을 원하는 파일 탭 → 좌측 하단 공유하기 버튼을 통해 Mac 등으로 공유 후 파일 압축 해제
system_logs.logarchive 파일 확인
위 방법을 이용하면, iPhone과 연결 여부와 상관없이 Apple Watch의 시스템 로그를 수집할 수 있습니다.
이처럼 Apple Watch 단독 상태에서 로그를 보고 싶으실 때, Sysdiagnose를 참고하시면 좋을 것 같습니다.
watchOS 앱을 개발하면서, 정말 별의별 버그들을 본 것 같습니다.
아직 제가 초보 개발자라서 그런 것도 있겠지만, iOS 앱이랑 비교하면 정말 쉽지 않았습니다.
팀에 시니어 개발자분은 옛날 iOS를 보는 듯한 느낌이라고 하셨습니다 (Obj-c 시절 iOS 개발자분들.. 항상 존경합니다).
개발자 문서에서 나와 있지 않은 내용도 많았던 것 같고, 참고할 만한 코드도 iOS에 비하면 턱 없이 모자랐습니다.
결국 직접 시도해 보고, 현상으로 유추하면서 알아낸 것들도 많았습니다.
그래도 덕분에 이것저것 많은 것들을 시도해 보면서, 공부도 많이 되고 실력도 조금은 늘어난 것 같아서 뿌듯한 경험이었습니다.
긴 글 읽어주셔서 감사드리며, 다음에 다른 글로 또 찾아 뵙겠습니다 🙇
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.