[ iOS ] 로컬 PUSH 와 APNS 푸시 차이점 완벽 정리

아이폰 개발 시 서버에서 발송되는 APNS는 대체적으로 거의 모든 상용 앱에 사용됩니다. 하지만 로컬 푸시는 어떻게 작동되는지 잘 모르고 있다가 이번에 개인 프로젝트를 만들면서 완전히 애플의 푸시 서비스를 이해할 수 있게 되어서 정리합니다. 아마 iOS 개발을 오래 하신 분들도 잘 이해하지 못하고 반사적으로 사용하는 경우가 많을 것 같습니다. 이번에 HTTP/2 서버 변경 이슈와 로컬 노티피케이션 덕분에 푸시에 대해서 완전히 이해하게 되었습니다.

 

애플의 푸시서비스

우선 애플의 푸시 서비스는 두 개로 분리됩니다. 

  • 서버에서 보내는 APNS 푸시
  • 로컬에서 개발자가 직접(?) 보내는 푸시

이 밖에도 FCM이라는 파이어 베이스 기반의 푸시 서비스도 있지만 결국에는 구글이 APNS를 발송하는 거라서 별다른 의미는 없습니다. 

 

그리고 APNS 푸시 서비스를 사용하기 위해서는 두 가지 인증서가 있습니다. 

  • p12
  • p8

이 인증서 부분은 끝 부분에 설명드리고 로컬 푸시에 대해서 정리를 하고 APNS를 이해하도록 하겠습니다.

 

 

로컬 노티피케이션

우선 푸시서비스를 만들기 위해서는 다음과 같은 코드가 불려야 합니다. 주로 appDelegate의 didFinishLaunching에서 호출하는데 이 부분을 뒤로 미룰 수 있습니다. 대부분의 앱들을 보면 앱이 시작하자마자 알림 허용 창이 뜨지만 알림 창이 인트로가 끝나고 특정 시점에 호출되게 할 수도 있습니다. 단 applicaion 객체를 알고 있어야 합니다. 그 이유는 디바이스 토큰을 얻기 위해서 인데 해당 토큰을 얻으려면 applicaion 객체의 메서드인 registerUserNotificationSettings를 호출해야 합니다. 이 값은 앱을 지우기 전에는 바뀌지 않습니다.

configure(application: application, delegate: self)
public class func configure(application: UIApplication, delegate: UNUserNotificationCenterDelegate?) {
        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = delegate
            
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: { (granted, error) in
                    if granted {
                        print("사용자가 푸시를 허용했습니다")
                        DispatchQueue.main.async {
                            application.registerForRemoteNotifications()
                        }
                    } else {
                        print("사용자가 푸시를 거절했습니다")
                    }
                })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
    }

중요한 사실은 노컬 푸시의 경우 이 값이 중요하지 않습니다. 특정 타깃에 푸시를 해야 할 이유가 없기 때문이죠. 자기 자신만 불리면 되기 때문에 토큰이 필요하지 않습니다.

 

또한 iOS 10 이상부터는 권한에 대한 핸들러가 포함되어 있어서 사용자가 푸시를 허용했는지, 거절했는지를 알 수 있습니다. 분기를 하면 좋지만 요즘 앱들은 거의 iOS 10 이상에서 실행되기 때문에 이 역시 의미가 없습니다.

 

정리하자면 디바이스 토큰은 APNS에만 필요하기 때문에 application의 registerUserNotificationSettings 메서드를 호출할 이유가 없다.입니다.

// 디바이스 토큰
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
	let deviceTokenString = MyLib().getDeviceTokenString(deviceToken: deviceToken)
	addToken(deviceTokenString: deviceTokenString)
}

 

하지만 로컬 및 APNS 푸시 델리게이터 설정은 반드시 해줘야 합니다. 푸시 후에 전달되는 userInfo 값을 받을 곳은 필요하니까요. 보통 AppDelegate 전역에 설정합니다.

UNUserNotificationCenter.current().delegate = delegate

 

이제 푸시 서비스가 설정되었으면 노컬 푸시를 보내보겠습니다. 아래의 코드를 순서대로 보면 총 4단계로 이루어졌습니다.

  • 컨텐츠 객체를 설정합니다. userInfo는 푸시를 눌러서 들어왔을 때의 값을 의미합니다.
  • 트리거는 시간, 캘린더, 위치서비스의 총 3개의 타입으로 생성이 가능합니다. UITimerIntervalNotificationTriger의 경우 0초는 에러가 납니다 최소 0.1 초로 발송해야 합니다.
  • 요청을 만들 때는 위의 두 개의 객체를 포함하고 추가적으로 id를 생성합니다. 푸시는 추후에 취소될 가능성이 있다면 이 값이 중요합니다. 해당 푸시를 제거하기 위해서 필요합니다.
  • 마지막으로 요청을 추가합니다. 클로저에 실패에 대한 에러 처리를 추가할 수 있네요.
var x = 1

// 컨텐츠 생성
let content = UNMutableNotificationContent()
content.title = "제목"
content.body = "푸시 \(x)"
content.badge = NSNumber(value: x)
let soundName = UNNotificationSoundName("test.aiff")
content.sound = UNNotificationSound(named: soundName)
content.userInfo = ["ok":"you"]

// 트리거 생성
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: seconds, repeats: false)
//        UNLocationNotificationTrigger
//        UNCalendarNotificationTrigger

let pushID = "notification \(x)"

// 요청 생성
let request = UNNotificationRequest(identifier: pushID,
content: content,
trigger: trigger)

// 요청 추가
UNUserNotificationCenter.current().add(request) { error in
  if let error = error {
  	print("Notification Error: ", error)
  }
}

 

APNS 서비스

지금까지 로컬 푸시에 대해서 알아봤습니다. APNS의 가장 큰 차이점이라면 토큰 값이 있어야 한다는 점입니다. 위에서 설명했듯이 application의 registerUserNotificationSettings 메서드를 호출하면 앱의 토큰 값을 알 수 있습니다. 그리고 서버로 이 값을 전달해서 서버 측에서 해당 사용자의 토큰을 관리합니다. 만약 FCM(Firebase Cloud Message) 서비스를 사용한다면 이 토큰 값을 구글로 보내게 되고 구글에서 반환하는 키를 기준으로 안드로이드와 애플에 동시에 푸시를 보낼 수 있습니다.

 

또한 로컬 푸시는 인증서를 발급받을 필요가 없지만 APNS는 인증서를 발급받아야 한다는 것입니다. 그리고 한 가지 더 다른 게 있습니다. 설정 파일에서 Capability를 추가해줘야 한다는 겁니다. 그럼 Entitlement 파일이 생성되고 앱 단에서는 APNS 노티피케이션 서비스의 모든 준비가 끝납니다.

Signing & Capabillity

 

서버에서는 푸시 발송을 위해서 인증서와 팀 아이디, 앱 아이디, 번들 아이디의 정보를 가지고 해당 단말을 타겟으로 푸시를 보냅니다. 이 모든 처리는 애플의 APNS 서버가 담당하며 최근에 HTTP/2 이슈로 이전에 사용하던 서버 IP를 더 이상 지원하지 않는다고 합니다. 그런데 제가 개발하는 서비스 중에는 아직도 p12인증서를 사용하는 앱이 있는데 다행히 ssl 에 대한 처리는 계속 지원한다고 합니다. p12와 p8의 차이점은 1년마다 갱신하는 게 p12 영구적인 게 p8인증서입니다. 애플 APNS는 워낙에 정보가 많으니 이 정도로 정리합니다. 이제 로컬 푸시와 헷갈리지 않고 사용할 수 있겠네요. 저는 처음에 로컬 푸시 권한은 애플 APNS 푸시와 완전 별 게인 줄 알았는데 그게 아니더군요.

댓글

Designed by JB FACTORY