[Swift] UNUserNotificationCenter - 3

r1verfuture
14 min readApr 3, 2024

--

정말 무궁무진한 UNUserNotificationCenter 의 세계 ~ 🎶 이번 주 안으로 끝내고 싶은데 될지 모르겠다 ㅋㅋ 저번 글에 이어서 다음 메소드를 또 얼른 봐야 하기 때문에 잡담 없이 바로 시작 ..!

그 다음에 볼

open func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)? = nil)

이 메소드는 로컬 알림 전달을 예약하는 역할을 하고, 인자로 requestcompletionHandler 를 갖는다.

request

UNNotificationRequest 타입이고, 알림 Payload 와 트리거 정보를 포함하는 요청 객체이다. 주의해야할 점은 이 인자에는 nil 값이 들어갈 수 없다는 것이다.

completionHandler

add 의 결과와 함께 백그라운드 Thread 에서 실행되는 코드 블록이다. 이 블록은 반환값이 없고, 실행 중에 오류가 발생하면 해당 오류로 설정되는 Error 타입의 인자만 가지고 있다. 알림 예약이 성공적으로 되면 오류가 없다는 뜻이기 때문에 이 인자값은 nil 이 된다.

참고로 이 메소드는 로컬 알림만 예약 가능하고, 원격 알림 예약에는 사용할 수 없다고 한다. 이 메소드가 호출되면 시스템은 request 에 있는 트리거 조건을 추적하기 시작하고, 추적하다가 트리거 조건을 만족하게 되면 시스템은 알림을 전달한다. 만약 requestUNNotificationTrigger 객체를 가지고 있지 않다면 트리거 조건이 없다는 뜻이기 때문에 알림이 바로 전달된다. 이 메소드를 써서 알림을 예약하는 예시는

let content = UNMutableNotificationContent()
content.title = "알림 제목"
content.body = "알림 내용"
let notification = UNNotificationRequest(identifier: "com.example.mynotification", content: content, trigger: nil)
UNUserNotificationCenter.current().add(notification) { error in
if let error = error {
print("알림 예약 실패 이유 : \(error.localizedDescription)")
} else {
print("알림 예약 성공")
}
}

이런 식으로 구성할 수 있다. 이쯤 되니까 UNNotificationRequestUNNotificationTrigger 가 무엇인지 궁금해졌다 ㅋㅋ

UNNotificationRequest

는 로컬 알림을 예약하기 위한 요청에 해당하고,

@available(iOS 10.0, *)
open class UNNotificationRequest : NSObject, NSCopying, NSSecureCoding {
open var identifier: String { get }

@NSCopying open var content: UNNotificationContent { get }

@NSCopying open var trigger: UNNotificationTrigger? { get }

public convenience init(identifier: String, content: UNNotificationContent, trigger: UNNotificationTrigger?)
}

이렇게 구성되어 있다. 이 객체는 identifier , content , trigger 라는 인자를 통해 초기화되는데, 이 3가지에 대해 하나씩 살펴보면

identifier

는 앱에서 알림을 식별하기 위해 사용하는 알림 요청 고유 식별자이고, 이 식별자를

  • removePendingNotificationRequests(withIdentifiers:)
  • removeDeliveredNotifications(withIdentifiers:)

와 같은 메소드의 identifiers 인자에 넣어서 대기 중인 알림 요청이나 전달된 알림을 대체하거나 삭제할 수 있다. (위의 2가지 메소드는 이따가 하나씩 설명할 예정이다 ㅎ) 만약 새로운 알림을 예약할 때 기존에 이미 등록되어 있던 식별자를 사용하면 시스템은 기존에 그 식별자로 예약되어 있던 알림을 삭제하고 새로운 알림으로 대체하기 때문에 아예 새로운 알림을 예약하려고 하는 경우에는 기존에 사용하지 않은 식별자를 써야 한다. 로컬 알림의 경우에는 UNNotificationRequest 를 초기화할 때 사용하는 init(identifier:content:trigger:) 메소드를 사용해서 identifier 를 설정하지만, 원격 알림의 경우에는 원격 알림을 생성할 때 내가 APNs 요청 Header 에 지정했던 apns-collapse-id Key 의 값으로 identifier 를 설정한다. 식별자가 따로 설정되지 않은 경우에는 시스템이 자동으로 할당해준다.

content

는 알림에 보일 콘텐츠에 해당하고, UNNotificationContent 타입의 변수이다. UNNotificationContent 에 대해서 들어본 적이 있는 것 같은데 ? 익숙한데 ? 했더니 저번 글에 공식 문서 링크만 놓고 갔던 친구였다 ㅋㅋ UNNotificationContent 내용이 조금 많아서 나중에 더 자세히 알아보는 것으로 하고, 편집할 수 없는 알림 컨텐츠들을 나타내는 친구라는 것만 일단 알아두자 ..!

trigger

는 알림 전달을 발생하도록 하는 트리거이고, UNNotificationTrigger 타입의 변수이다. 이 값이 nil 이라는 것은 트리거가 없다는 뜻이기 때문에 알림이 곧바로 전달된다. UNNotificationTrigger 내용도 꽤 (?) 있기 때문에 나중에 제대로 소개하는 것으로 하고, 로컬 알림 전달이나 원격 알림 전달이 발생하도록 하는 이벤트를 대표하는 추상 클래스라는 것 정도만 알고 넘어가자 !

UNNotificationRequest 객체는

let content = UNMutableNotificationContent()
content.title = "Lunch time"
content.body = "Food is cooked... let's eat!"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60.0, repeats: false)
let request = UNNotificationRequest(identifier: "com.example.mynotification", content: content, trigger: trigger)

이런 식으로 만들 수 있고, 이 코드는 60초 후에 Lunch time 이라는 제목과 Food is cooked… let’s eat! 이라는 내용의 알림이 전달되도록 한다.

UNNotificationTrigger

는 위에서도 말했듯이 로컬 알림 전달이나 원격 알림 전달이 발생하도록 하는 이벤트를 대표하는 추상 클래스이고, 객체를 만들 때 직접 만들지 않고 구체적인 하위 클래스를 객체화하는 방식을 사용한다. 그 다음에 바로 위에서 UNNotificationRequest 객체 만드는 예시 코드처럼 UNNotificationTrigger 객체를 UNNotificationTrigger 객체 만들 때 trigger 파라미터에 넣어주면 알림을 예약할 수 있다. UNNotificationTrigger

@available(iOS 10.0, *)
open class UNNotificationTrigger : NSObject, NSCopying, NSSecureCoding {
open var repeats: Bool { get }
}

이렇게 간단하게 repeats 라는 변수 하나만 가지고 있는데, 이 친구는 알림이 전달된 뒤에 시스템이 그 알림을 새로 예약하는지를 나타내는 Bool 타입의 변수이다. 만약 이 프로퍼티가 false 이면 시스템은 그 알림을 한번만 전달하고, 이 프로퍼티가 true 이면 시스템은 그 알림 요청이 자동으로 다시 예약되어서 트리거 조건을 만족할 때마다 알림이 전달된다. 이쯤에서 방금 전에 언급한 구체적인 하위 클래스가 무엇인지 궁금해졌다 ㅋㅋ 구체적인 하위 클래스에는

  • UNTimeIntervalNotificationTrigger
  • UNCalendarNotificationTrigger
  • UNLocationNotificationTrigger
  • UNPushNotificationTrigger

이렇게 4가지가 있다고 하고, 하나씩 살펴보면

UNTimeIntervalNotificationTrigger

는 특정 시간이 흐른 후 시스템이 로컬 알림을 전달하도록 하는 트리거 조건이고,

@available(iOS 10.0, *)
open class UNTimeIntervalNotificationTrigger : UNNotificationTrigger {
open var timeInterval: TimeInterval { get }

public convenience init(timeInterval: TimeInterval, repeats: Bool)

open func nextTriggerDate() -> Date?
}

이렇게 구성되어 있다. timeInterval 이라는 TimeInterval 타입의 변수와 Date 타입을 반환하는 nextTriggerDate() 메소드를 가지고 있고, timeInterval 은 시스템이 로컬 알림을 전달하도록 하는 트리거가 생성되는 시간 간격 (초 단위) 에 해당한다. 이 프로퍼티의 값은 타이머처럼 시간이 지남에 따라 업데이트되는 것이 아니기 때문에 다음 트리거가 언제 실행되는지 알려면 다음 트리거 조건을 만족하는 날짜를 반환하는 nextTriggerDate() 메소드를 호출해야 한다. UNTimeIntervalNotificationTrigger 객체는

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (30*60), repeats: false)

이런 식으로 만들 수 있고, 이 코드는 1,800초 (30분) 뒤에 트리거가 생성되도록 한다 (알림이 전달된다) 는 뜻이다. repeats 인자는 상위 클래스인 UNNotificationTrigger 를 통해 받아온 것이고, 생성된 트리거를 반복할지를 나타내는데 위 코드에서는 이 인자에 false 를 넣었기 때문에 이 트리거가 반복되지 않는다.

UNCalendarNotificationTrigger

는 특정 날짜와 시간에 시스템이 로컬 알림을 전달하도록 하는 트리거 조건이고,

@available(iOS 10.0, *)
open class UNCalendarNotificationTrigger : UNNotificationTrigger {
open var dateComponents: DateComponents { get }

public convenience init(dateMatching dateComponents: DateComponents, repeats: Bool)

open func nextTriggerDate() -> Date?
}

이렇게 구성되어 있다. dateComponents 라는 DateComponents 타입의 변수와 Date 타입을 반환하는 nextTriggerDate() 메소드를 가지고 있고, dateComponents 는 시스템이 로컬 알림을 전달하도록 하는 트리거가 생성되는 특정 날짜와 시간에 해당한다. UNTimeIntervalNotificationTrigger 와 마찬가지로 다음 트리거가 언제 실행되는지 알려면 다음 트리거 조건을 만족하는 날짜를 반환하는 nextTriggerDate() 메소드를 호출하면 되고, 상위 클래스에서 받아온 repeats 에 어떤 값이 설정되어 있는지에 따라 이 트리거의 반복 여부가 결정된다. 예를 들어, 매일 오전 8시 30분마다 알림을 전달하도록 하고 싶으면

var date = DateComponents()
date.hour = 8
date.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)

이런 식으로 UNCalendarNotificationTrigger 객체를 만들면 되고, 하루만 오전 8시 30분에 알림이 전달되게 하고 싶으면 위의 코드에서 repeatsfalse 로 바꾸어 주면 된다.

UNLocationNotificationTrigger

는 사용자의 기기가 특정 지역에 도착하거나 떠날 때 시스템이 로컬 알림을 전달하도록 하는 트리거 조건이고,

@available(iOS 10.0, *)
open class UNLocationNotificationTrigger : UNNotificationTrigger {
@NSCopying open var region: CLRegion { get }

public convenience init(region: CLRegion, repeats: Bool)
}

이렇게 구성되어 있다. region 이라는 변수 하나만 가지고 있고, 이 변수는 시스템이 로컬 알림을 전달하도록 하는 트리거가 생성되는 특정 지역에 해당한다. 이때 서로 다른 지역으로 알림 요청을 예약할 때 같은 식별자의 CLRegion 을 쓰게 되면 예상하지 못 하게 동작할 수 있기 때문에 CLRegion 의 식별자는 고유한 특성을 띄고 있어야 한다. (말 그대로 식별자이기 때문에 어떻게 보면 고유한 특성을 띄는게 당연하다 ㅎ) 알림 전달되는 시점을 사용자의 기기가 그 지역에 도달했을 때만으로 할지, 떠났을 때만으로 할지, 이 2가지 경우 모두로 할지 정하려면 지역 정보 (region) 를 구성할 때 CLRegionnotifyOnEntry 프로퍼티와 notifyOnExit 프로퍼티를 쓰면 된다. notifyOnEntry 프로퍼티는 기기가 그 지역에 도달했을 때의 알림 전달 가능 여부를 나타내는 Bool 타입의 변수이고, notifyOnExit 프로퍼티는 기기가 그 지역을 떠났을 때의 알림 전달 가능 여부를 나타내는 Bool 타입의 변수이다. 이 트리거를 사용한 예시 코드는

let center = CLLocationCoordinate2D(latitude: 37.335400, longitude: -122.009201)
let region = CLCircularRegion(center: center, radius: 2000.0, identifier: "Headquarters")
region.notifyOnEntry = true
region.notifyOnExit = false
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)

이런 식으로 구성할 수 있고, 이 코드는 위도 37.335400, 경도 -122.009201 인 지점을 기준으로 2km 반경에 있는 지역에 기기가 들어가면 트리거가 생성되도록 한다는 뜻이다. 참고로 이 트리거는 동시에 예약할 수 있는 개수가 제한되어 있고, Core Location 사용 권한이 있으면서 앱을 사용하는 동안 (when-in-use permission) 허용하도록 설정되어 있어야 이 트리거가 정상적으로 작동한다.

UNPushNotificationTrigger

Apple Push Notification service (APNs) 가 알림을 보냈음을 나타내는 트리거 조건이고,

@available(iOS 10.0, *)
open class UNPushNotificationTrigger : UNNotificationTrigger {
}

이렇게 아무 것도 없는 형태로 구성되어 있다 😅 참고로 이 객체는 내가 직접 만드는 것이 아니라 시스템이 만들고, 앱에 전달된 알림 요청을 관리할 때 이 객체를 만날 수 있다. 공식 문서에서도 딱히 이 친구에 대해 더 자세히 다루고 있지 않기 때문에 여기까지만 알아보겠다 ㅎ

그 다음에 알아볼 메소드는

open func add(_ request: UNNotificationRequest) async throws

인데, 이것도 마찬가지로 async 가 붙어있기 때문에 위에서 잔뜩 설명한

open func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)? = nil)

메소드의 Concurrency 버전이라는 것만 알아두고 Concurrency 에 대해 공부하고 나서 제대로 알아보도록 하자 🧐

원래 계획은 UNNotificationContent 에 대한 내용까지 다루고 글 마무리하기였는데 역시 내 계획대로 되지 않았다 ^^ 다음 글에서 이 친구에 대해 알아보는걸로 하고 이쯤에서 마무리 !!

--

--