# iOS 메시지 전송 추적

Pushwoosh에는 푸시 알림 전송을 추적하는 [API 메서드](/ko/developer/api-reference/device-api#messagedeliveryevent)가 있습니다. iOS의 푸시 알림은 Pushwoosh SDK가 아닌 OS에서 처리하므로 iOS 앱은 이 메서드를 기본적으로 지원하지 않습니다. 프로젝트에 Notification Service Extension을 추가하여 전송 추적을 추가할 수 있습니다. 이 페이지에서는 iOS 앱의 메시지 전송 추적을 구현하는 방법을 보여줍니다.

<Aside>
Pushwoosh iOS SDK 7.x가 필요하며, iOS 13.0 이상을 지원합니다.
</Aside>

<Aside type="note">
Pushwoosh iOS SDK 7.1.0부터는 아래에 표시된 드롭인 `PushwooshNotificationServiceExtension` 기본 클래스를 사용하는 것이 권장되는 통합 방법입니다. 이 클래스는 메시지 전송 이벤트를 보내고, 배지를 설정하고, 미디어 첨부 파일을 다운로드하며, 필수적인 `serviceExtensionTimeWillExpire` 폴백을 처리합니다. 이전의 `PWNotificationExtensionManager` API는 여전히 작동하지만 더 이상 사용되지 않습니다 — [레거시 통합](#legacy-integration)을 참조하세요.
</Aside>

## Notification Service Extension 추가

1. Xcode에서 **File** > **New** > **Target...**을 선택합니다.

2. **Notification Service Extension**을 선택하고 **Next**를 누릅니다.

<img src="/ios-push-notifications-ios-message-delivery-tracking-1.webp" alt="Notification Service Extension이 선택된 Xcode 대상 템플릿 선택기"/>

3. 제품 이름을 입력하고 **Finish**를 누릅니다.

<Aside type="caution">
**Finish**를 누른 후 표시되는 대화상자에서 **Activate**를 선택하지 마십시오.
</Aside>

4. **Activate scheme** 프롬프트에서 **Cancel**을 누릅니다.

<img
  src="/ios-push-notifications-ios-message-delivery-tracking-2.webp"
  alt="취소가 강조 표시된 스킴 활성화 프롬프트"
  style={{ display: "block", margin: "0 auto", maxWidth: "40%", height: "auto" }}
  width="400"
/>

취소하면 방금 생성한 extension 대신 앱을 계속 Xcode에서 디버깅할 수 있습니다. 실수로 활성화한 경우 Xcode 내에서 앱 디버깅으로 다시 전환할 수 있습니다.

## Notification Service Extension 종속성 (CocoaPods만 해당)

Swift Package Manager를 사용하여 종속성을 관리하는 경우, 종속성이 자동으로 추가되므로 이 단계를 건너뛸 수 있습니다.

`Podfile`을 열고 대상에 대한 종속성을 추가합니다:

```ruby title="Podfile"
target 'NotificationServiceExtension' do
  use_frameworks!
  pod 'PushwooshXCFramework'
end
```

터미널에서 다음 명령을 실행하여 종속성을 설치합니다:

```shell
rm -rf Podfile.lock
pod deintegrate
pod setup
pod repo update
pod install
```

## 메시지 전송 이벤트 추적을 위한 코드 추가

extension을 `PushwooshNotificationServiceExtension`의 서브클래스로 만듭니다. 빈 서브클래스만으로도 충분합니다: Pushwoosh가 메시지 전송 이벤트를 보내고, 배지를 설정하고, 미디어 첨부 파일을 다운로드하며, 타임아웃 폴백을 자동으로 처리합니다.

**NotificationService** 파일의 생성된 내용을 다음으로 교체합니다:

<Tabs>
<TabItem label="Swift">

```swift
import UserNotifications
import PushwooshFramework

class NotificationService: PushwooshNotificationServiceExtension {}
```

</TabItem>

<TabItem label="Objective-C">

```objective-c
#import <PushwooshFramework/PushwooshNotificationServiceExtension.h>

@interface NotificationService : PushwooshNotificationServiceExtension

@end

@implementation NotificationService

@end
```

</TabItem>
</Tabs>

<Aside type="tip">
사용자 지정 코드가 필요하지 않은 경우, 소스 파일을 완전히 건너뛰고 extension의 Info.plist `NSExtensionPrincipalClass`를 `PushwooshNotificationServiceExtension`으로 직접 지정할 수 있습니다.
</Aside>

### 앱 ID

7.1.0부터 extension은 호스트 앱의 Info.plist에서 `Pushwoosh_APPID`(및 기타 `Pushwoosh_*` 키)를 상속받으므로 더 이상 extension에 중복해서 추가할 필요가 없습니다. 호스트 값을 재정의하려는 경우에만 `Pushwoosh_APPID`를 extension의 Info.plist에 추가하십시오:

```xml title="NotificationService/Info.plist"
<key>Pushwoosh_APPID</key>
<string>XXXXX-XXXXX</string>
```

<Aside type="note">
7.1.0 이전 버전의 Pushwoosh iOS SDK에서는 extension이 호스트 앱에서 구성을 상속하지 않습니다. 해당 버전에서는 `Pushwoosh_APPID`를 extension의 Info.plist에 추가해야 합니다.
</Aside>

### 앱 그룹 (배지 및 리버스 프록시)

앱과 extension 간에 공유되는 앱 그룹은 배지 수를 동기화하고 호스트 앱이 저장하는 리버스 프록시 설정을 읽는 데 필요합니다.

1. extension 대상에 **App Groups** 기능을 추가하고 호스트 앱과 동일한 그룹을 활성화합니다. 이는 필수 사항입니다 — 공유 컨테이너가 없으면 배지 수와 리버스 프록시 설정을 동기화할 수 없습니다.

2. 앱 그룹 이름을 제공합니다. `Pushwoosh_APPID`와 마찬가지로, extension은 7.1.0부터 호스트 앱의 Info.plist에서 `PW_APP_GROUPS_NAME`을 상속받으므로, 배지를 위해 이미 설정했다면 extension에 추가할 필요가 없습니다. 호스트 값을 재정의하거나 `pushwooshAppGroupsName`을 재정의하여 프로그래밍 방식으로 제공하려는 경우에만 extension의 Info.plist에 설정하십시오.

```xml title="App Info.plist"
<key>PW_APP_GROUPS_NAME</key>
<string>group.com.example.app</string>
```

<Aside type="caution">
호스트 앱이 리버스 프록시(`Pushwoosh_ALLOW_REVERSE_PROXY`)를 사용하는 경우, extension은 앱이 저장한 프록시 URL을 읽기 위해 이 앱 그룹이 필요합니다. 이것이 없으면 전송 이벤트가 프록시를 우회하여 직접 전송되지 않고 보류됩니다.
</Aside>

## 알림 사용자 지정 (선택 사항)

기본 클래스는 제어 수준이 낮은 것부터 높은 것까지 몇 가지 재정의 지점을 제공합니다. Pushwoosh는 모든 경우에 전송 이벤트, 배지, 첨부 파일 및 타임아웃 폴백을 계속 실행합니다.

Info.plist 키를 사용하는 대신 프로그래밍 방식으로 앱 그룹을 설정합니다:

```swift
override func pushwooshAppGroupsName() -> String? {
    "group.com.example.app"
}
```

표준 `didReceive`를 재정의하지 않고 Pushwoosh가 푸시를 처리하기 전에 비동기 준비를 실행합니다 — 예를 들어, Push Stories 미디어를 미리 가져오는 경우. 메인 스레드에서 `completion`을 정확히 한 번 호출합니다:

```swift
override func pushwooshPrepare(for request: UNNotificationRequest,
                              completion: @escaping () -> Void) {
    // async work here
    completion()
}
```

`didReceive`를 재정의하여 내용이 표시되기 전에 수정합니다. 자신만의 콘텐츠 핸들러로 `super`를 호출하고, 그 안에서 내용을 변경한 다음, 원래 핸들러로 전달합니다:

```swift
override func didReceive(_ request: UNNotificationRequest,
                         withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    super.didReceive(request) { content in
        let mutable = (content.mutableCopy() as? UNMutableNotificationContent) ?? content
        // customize `mutable` here
        contentHandler(mutable)
    }
}
```

## 레거시 통합

<Aside type="caution">
`PWNotificationExtensionManager`는 7.1.0부터 더 이상 사용되지 않습니다. 이미 다른 SDK의 기본 클래스를 확장하는 extension이나 크로스 플랫폼 래퍼(React Native, Flutter, Unity)와 같이 `PushwooshNotificationServiceExtension`을 서브클래싱할 수 없는 경우에만 사용하십시오. 새로운 통합은 위의 기본 클래스를 사용해야 합니다.
</Aside>

이 로우레벨 API는 일반 `UNNotificationServiceExtension`에서 동일한 처리(전송 이벤트, 배지, 첨부 파일)를 수행합니다:

<Tabs>
<TabItem label="Swift">

```swift
import UserNotifications
import PushwooshFramework

class NotificationService: UNNotificationServiceExtension {

    override func didReceive(_ request: UNNotificationRequest,
                             withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        PWNotificationExtensionManager.sharedManager()
            .handleNotificationRequest(request, contentHandler: contentHandler)
    }
}
```

</TabItem>

<TabItem label="Objective-C">

```objective-c
#import "PWNotificationExtensionManager.h"

@interface NotificationService : UNNotificationServiceExtension

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                   withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
    [[PWNotificationExtensionManager sharedManager] handleNotificationRequest:request
                                                              contentHandler:contentHandler];
}

@end
```

</TabItem>
</Tabs>

## 피드백을 공유해주세요

여러분의 피드백은 더 나은 경험을 만드는 데 도움이 됩니다. SDK 통합 과정에서 문제가 발생하면 언제든지 알려주시기 바랍니다. 어려움이 있으시면 주저하지 마시고 [이 양식을 통해](https://docs.google.com/forms/d/e/1FAIpQLSd_0b8jwn-V_JmoPLIxIFYbHACCQhrzidOZV3ELywoQPXRSxw/viewform) 의견을 공유해주세요.