Notification Service Extension là một tiện ích mở rộng của iOS chặn thông báo push trước khi hiển thị cho người dùng và cho phép sửa đổi nội dung. Tiện ích mở rộng có thể giải mã dữ liệu đã mã hóa, tải xuống tệp đính kèm phương tiện, thay đổi văn bản hoặc thêm trường tùy chỉnh. Theo Apple Developer, 2025, Notification Service Extension có tối đa 30 giây để hoàn thành tác vụ trong nền.
Những điểm chính
Notification Service Extension là một tiện ích mở rộng ứng dụng trên iOS chạy trước khi thông báo push được hiển thị. Nó cho phép máy chủ gửi payload tối thiểu trong khi tiện ích mở rộng làm phong phú nó với nội dung: tải xuống hình ảnh, giải mã dữ liệu, thay thế văn bản. Người dùng thấy thông báo đã được xử lý.
Khi iOS nhận được thông báo push, hệ thống kiểm tra xem có tiện ích mở rộng cho ứng dụng hay không. Nếu tiện ích mở rộng được đăng ký, iOS khởi chạy nó trong một tiến trình nền và chuyển nội dung thông báo qua phương thức didReceive(_:withContentHandler:). Tiện ích mở rộng xử lý nội dung và gọi contentHandler với nội dung đã sửa đổi. Nếu tiện ích mở rộng không hoàn thành trong 30 giây, hệ thống hiển thị thông báo gốc.
Service Extension cần thiết trong các kịch bản mà nội dung thông báo yêu cầu xử lý trên thiết bị. Truyền thông an toàn: máy chủ gửi payload đã mã hóa, tiện ích mở rộng giải mã cục bộ. Thông báo đa phương tiện phong phú: máy chủ gửi URL hình ảnh, tiện ích mở rộng tải xuống và đính kèm. Bản địa hóa động: tiện ích mở rộng thay thế văn bản bằng ngôn ngữ của thiết bị.
| Kịch bản | Không có tiện ích mở rộng | Có tiện ích mở rộng |
|---|---|---|
| Hình ảnh | Không được hỗ trợ | Được tải xuống và hiển thị |
| Mã hóa | Máy chủ giữ khóa | Giải mã trên thiết bị |
| Văn bản | Cố định trên máy chủ | Thay thế động |
| Xác thực | Không được kiểm tra | Hủy thông báo độc hại |
Notification Service Extension hoạt động trong môi trường giới hạn. Thời gian thực thi tối đa là 30 giây. Tiện ích mở rộng không có quyền truy cập vào bộ nhớ chính của ứng dụng (ngoại trừ App Group). Kích thước tiện ích mở rộng bị giới hạn ở 50 MB. Khi vượt quá thời gian, hệ thống gọi contentHandler với nội dung gốc và tất cả các thay đổi đều bị mất.
Trong Xcode, Notification Service Extension được thêm bằng cách tạo một target mới loại Notification Service Extension. Xcode tạo một lớp mẫu NotificationService kế thừa từ UNNotificationServiceExtension, với hai phương thức: didReceive và serviceExtensionTimeWillExpire.
Trong Xcode, chọn File → New → Target → Notification Service Extension. Nhập tên (ví dụ: PushNotificationService) và chọn ngôn ngữ Swift. Đảm bảo rằng target được thêm vào ứng dụng chính và có chữ ký chính xác. Sau khi tạo, lớp NotificationService.swift với triển khai cơ bản sẽ được tạo.
Info.plist của tiện ích mở rộng chứa khóa NSExtension với các khóa con NSExtensionPointIdentifier (com.apple.usernotifications.service) và NSExtensionPrincipalClass (tên bộ điều khiển của bạn). Ngoài ra, có thể chỉ định NSExtensionAttributes với các quy tắc kích hoạt tiện ích mở rộng. Xcode tạo các cài đặt này tự động.
Triển khai cơ bản ghi đè didReceive, sửa đổi nội dung thông báo và gọi contentHandler. Nếu quá trình xử lý mất quá nhiều thời gian, serviceExtensionTimeWillExpire được gọi, nơi bạn cần hoàn thành công việc với trạng thái hiện tại.
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
contentHandler = handler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "[Đã xử lý] \(bestAttemptContent.title)"
contentHandler?(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Một trong những ứng dụng chính của Service Extension là giải mã nội dung thông báo trên thiết bị. Máy chủ gửi một payload đã mã hóa và tiện ích mở rộng giải mã nó bằng khóa được lưu trữ trong Keychain hoặc App Group. Điều này đảm bảo nội dung không thể bị chặn trong quá trình truyền.
Máy chủ mã hóa nội dung thông báo bằng khóa đối xứng (AES-256). Khóa mã hóa được thỏa thuận trước giữa máy khách và máy chủ. Tiện ích mở rộng nhận chuỗi đã mã hóa trong trường data của payload, giải mã nó và thay thế vào các trường thông báo. Khóa người dùng có thể được lấy từ Keychain sau khi xác thực.
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
guard let content = request.content.mutableCopy()
as? UNMutableNotificationContent else {
handler(request.content)
return
}
guard let encryptedData = content.userInfo["encrypted_data"]
as? String else {
handler(content)
return
}
let decrypted = CryptoService.decrypt(encryptedData)
content.body = decrypted.body
content.title = decrypted.title
handler(content)
}
Khóa giải mã không được lưu trữ trong mã tiện ích mở rộng hoặc NSUserDefaults. Sử dụng iOS Keychain với quyền truy cập qua App Group để cả ứng dụng và tiện ích mở rộng đều có thể đọc khóa. Sử dụng Security.framework với thuật toán AES-256-GCM để tạo khóa trên máy khách.
Cách sử dụng phổ biến nhất của Notification Service Extension là tải xuống hình ảnh, GIF và video để hiển thị trong thông báo. Máy chủ gửi URL tệp phương tiện và tiện ích mở rộng tải xuống, lưu vào thư mục tạm thời và tạo UNNotificationAttachment.
Tiện ích mở rộng nhận URL hình ảnh từ trường payload của thông báo. Sử dụng URLSession, tiện ích mở rộng tải tệp xuống thư mục tạm thời. Sau khi tải xuống hoàn tất, UNNotificationAttachment với URL cục bộ được tạo. Tệp đính kèm được chuyển đến nội dung đã sửa đổi. iOS tự động hiển thị hình ảnh trong giao diện tiêu chuẩn hoặc trong Notification Content Extension.
private func downloadAndAttachMedia(
content: UNMutableNotificationContent,
mediaUrl: String,
handler: @escaping (UNNotificationContent) -> Void
) {
guard let url = URL(string: mediaUrl) else {
handler(content)
return
}
let task = URLSession.shared.downloadTask(with: url) { localUrl, _, error in
guard let localUrl = localUrl, error == nil else {
handler(content)
return
}
let attachment = try? UNNotificationAttachment(
identifier: "media",
url: localUrl,
options: nil
)
if let attachment = attachment {
content.attachments = [attachment]
}
handler(content)
}
task.resume()
}
iOS hỗ trợ các định dạng sau để hiển thị trong thông báo: JPEG, PNG, GIF (tĩnh), video MPEG-4 lên đến 50 MB. Đối với tệp âm thanh, MP3, AAC và ALAC được hỗ trợ. Quan trọng: tất cả các tệp phương tiện phải được tải xuống trong giới hạn 30 giây. Đối với tệp lớn, nên cắt phía máy chủ hoặc tải dần.
Quản lý giới hạn 30 giây là một nhiệm vụ quan trọng khi phát triển Notification Service Extension. Nếu tiện ích mở rộng không hoàn thành xử lý kịp thời, hệ thống gọi serviceExtensionTimeWillExpire và hiển thị nội dung gốc. Các kịch bản dự phòng phải được cung cấp cho mỗi loại xử lý.
Chia nhiệm vụ theo mức độ ưu tiên. Thực hiện các sửa đổi quan trọng (giải mã, bản địa hóa cơ bản) trước. Các cải tiến tùy chọn (tải xuống hình ảnh, làm phong phú văn bản) thực hiện sau. Sử dụng URLSession với thời gian chờ cho các yêu cầu mạng để tránh dành toàn bộ giới hạn cho một thao tác duy nhất.
Nếu tải xuống hình ảnh thất bại hoặc giải mã payload trả về lỗi, tiện ích mở rộng nên gọi contentHandler với nội dung gốc. Không bao giờ kết thúc tiện ích mở rộng mà không gọi contentHandler — điều này dẫn đến mất thông báo. Dự phòng an toàn phải luôn được bao gồm trong mã tiện ích mở rộng.
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
let content = (request.content.mutableCopy()
as? UNMutableNotificationContent) ?? request.content
// Nhiệm vụ quan trọng: giải mã
var decryptedContent = tryDecryptPayload(content)
// Nhiệm vụ tùy chọn: phương tiện
guard let mediaUrl = decryptedContent.userInfo["media_url"]
as? String else {
handler(decryptedContent)
return
}
downloadAndAttachMedia(
content: decryptedContent,
mediaUrl: mediaUrl,
handler: handler
)
}
Để kiểm tra Notification Service Extension, hãy sử dụng Xcode: chọn target tiện ích mở rộng, chạy trên trình mô phỏng và gửi thông báo push qua terminal hoặc Firebase Console. Ghi nhật ký từng giai đoạn xử lý bằng os_log — điều này giúp chẩn đoán các vấn đề về thời gian và tải xuống.
Câu hỏi thường gặp
Hệ thống gọi serviceExtensionTimeWillExpire, sau đó hiển thị thông báo gốc mà không thay đổi. Tất cả các tệp đã tải xuống và sửa đổi đều bị loại bỏ.
Có, nếu bạn gọi contentHandler với nội dung trống (UNNotificationContent với các trường trống), thông báo sẽ không được hiển thị. Điều này được sử dụng để lọc thông báo rác hoặc dữ liệu không chính xác.
Mã thông báo có thể được truyền trong userInfo của payload thông báo hoặc lấy từ Keychain qua App Group. Không khuyến nghị lưu trữ mã thông báo trong UserDefaults của tiện ích mở rộng.
Tiện ích mở rộng phải được ký bằng cùng chứng chỉ nhà phát triển với ứng dụng chính. Đối với sản xuất, cần có chứng chỉ sản xuất với khả năng Push Notifications được bật.
Kết nối thiết bị với Xcode, chọn target tiện ích mở rộng trong lược đồ chạy và gửi thông báo push qua Firebase Console. Điểm dừng trong tiện ích mở rộng hoạt động giống như trong ứng dụng chính.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm