สัมภาษณ์ iOS Push Notifications 2026: APNs, โทเคน และ troubleshooting

คู่มือเตรียมสัมภาษณ์ iOS อย่างครบถ้วนเกี่ยวกับ Push Notifications, APNs, การจัดการโทเคน และ troubleshooting พร้อมคำถามยอดนิยมและคำตอบโดยละเอียด

สถาปัตยกรรม APNs ของ iOS Push Notifications พร้อมโทเคนและ troubleshooting

Push Notifications ยังเป็นหัวข้อสำคัญในสัมภาษณ์ iOS การเข้าใจการทำงานของ APNs การจัดการ device token และการแก้ปัญหาทั่วไปแสดงถึงความเข้าใจอย่างลึกซึ้งในระบบนิเวศของ Apple คู่มือนี้รวบรวมคำถามสัมภาษณ์ที่พบบ่อยที่สุด

จุดสำคัญในการสัมภาษณ์

ผู้สัมภาษณ์มองหาผู้สมัครที่เข้าใจวงจรชีวิตทั้งหมด: ตั้งแต่การลงทะเบียนอุปกรณ์จนถึงการส่งการแจ้งเตือน รวมถึงการจัดการข้อผิดพลาดอย่างเหมาะสมในแต่ละขั้นตอน

สถาปัตยกรรม APNs: รากฐานของการแจ้งเตือนบน iOS

Apple Push Notification service (APNs) คือบริการกลางที่จัดการการส่ง push notification ไปยังอุปกรณ์ของ Apple การเข้าใจสถาปัตยกรรมของมันจำเป็นต่อการตอบคำถามสัมภาษณ์ได้อย่างมีประสิทธิภาพ

APNs ทำงานอย่างไร?

การสื่อสารเกี่ยวข้องกับสามผู้เล่นหลัก: แอป iOS, APNs และเซิร์ฟเวอร์ backend ขั้นตอนทั้งหมดเป็นดังนี้:

AppDelegate.swiftswift
import UIKit
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Request notification authorization
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound]
        ) { granted, error in
            guard granted else { return }
            // Register with APNs
            DispatchQueue.main.async {
                application.registerForRemoteNotifications()
            }
        }
        return true
    }
}

การลงทะเบียน APNs เกิดขึ้นในสองขั้น: ขออนุญาตจากผู้ใช้ จากนั้นเรียก registerForRemoteNotifications()

การจัดการ device token

Device token เป็นตัวระบุเฉพาะที่ APNs สร้างขึ้นเพื่อใช้กับอุปกรณ์เครื่องใดเครื่องหนึ่ง โทเคนนี้สามารถเปลี่ยนแปลงได้ และควรถูกส่งไปยังเซิร์ฟเวอร์ backend ทุกครั้งที่เปิดแอป

AppDelegate.swiftswift
extension AppDelegate {

    // Callback when APNs provides the token
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        // Convert token to hexadecimal string
        let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device Token: \(tokenString)")

        // Send to backend server
        sendTokenToServer(tokenString)
    }

    // Callback on registration failure
    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("Failed to register: \(error.localizedDescription)")
    }

    private func sendTokenToServer(_ token: String) {
        // Implement HTTP request to backend
    }
}

โทเคนถูกส่งกลับมาในรูปของ Data และต้องถูกแปลงเป็นสตริงเลขฐานสิบหกก่อนส่งไปยังเซิร์ฟเวอร์

คำถามสัมภาษณ์ APNs ที่พบบ่อย

สัมภาษณ์ iOS มักมีทั้งคำถามทฤษฎีและปฏิบัติเกี่ยวกับ APNs ต่อไปนี้คือคำถามที่พบบ่อยที่สุดพร้อมคำตอบโดยละเอียด

คำถามที่ 1: ความแตกต่างระหว่าง APNs sandbox กับ production คืออะไร?

สภาพแวดล้อมของ APNs

APNs มีสองสภาพแวดล้อมแยกกันโดยมี endpoint ต่างกัน โทเคนที่สร้างในสภาพแวดล้อมหนึ่งจะใช้ในอีกสภาพแวดล้อมไม่ได้

สภาพแวดล้อมEndpointการใช้งาน
Sandboxapi.sandbox.push.apple.comDebug, TestFlight
Productionapi.push.apple.comApp Store

คำถามที่ 2: จัดการกับโทเคนหมดอายุอย่างไร?

Device token อาจเปลี่ยนได้ด้วยหลายเหตุผล: การคืนค่าระบบ การติดตั้งบนอุปกรณ์ใหม่ หรือการรีเฟรชเป็นระยะโดย APNs เซิร์ฟเวอร์ต้องจัดการกับการตอบกลับข้อผิดพลาดของ APNs อย่างเหมาะสม

NotificationService.swiftswift
enum APNsError: Int {
    case badDeviceToken = 400
    case unregistered = 410
    case payloadTooLarge = 413
    case tooManyRequests = 429
    case internalServerError = 500

    var shouldRemoveToken: Bool {
        // Remove token only if device is no longer registered
        return self == .unregistered || self == .badDeviceToken
    }
}

struct APNsResponse {
    let statusCode: Int
    let deviceToken: String

    func handleError() {
        guard let error = APNsError(rawValue: statusCode) else { return }

        if error.shouldRemoveToken {
            // Remove token from database
            TokenRepository.shared.remove(deviceToken)
        }
    }
}

การจัดการข้อผิดพลาด APNs ฝั่งเซิร์ฟเวอร์มีความสำคัญอย่างยิ่งต่อการรักษาฐานข้อมูลโทเคนให้สะอาดและหลีกเลี่ยงคำขอที่ไม่จำเป็น

คำถามที่ 3: ใช้งาน silent notification อย่างไร?

Silent notification ช่วยปลุกแอปให้ทำงานพื้นหลังเพื่อดำเนินงานโดยไม่แสดงการแจ้งเตือนใด ๆ ต่อผู้ใช้

AppDelegate.swiftswift
func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
    // Check if this is a silent notification
    guard let aps = userInfo["aps"] as? [String: Any],
          aps["content-available"] as? Int == 1 else {
        completionHandler(.noData)
        return
    }

    // Perform background task
    performBackgroundTask { result in
        switch result {
        case .success:
            completionHandler(.newData)
        case .failure:
            completionHandler(.failed)
        }
    }
}

Payload JSON ของ silent notification ต้องมี "content-available": 1 ภายในออบเจ็กต์ aps

พร้อมที่จะพิชิตการสัมภาษณ์ iOS แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Notification Service Extension: การปรับแต่งขั้นสูง

Notification Service Extension อนุญาตให้แก้ไขเนื้อหาของการแจ้งเตือนก่อนแสดงผล ฟีเจอร์นี้มักถูกถามในการสัมภาษณ์

สร้าง Service Extension

NotificationService.swift (in Extension target)swift
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent

        guard let bestAttemptContent = bestAttemptContent else {
            contentHandler(request.content)
            return
        }

        // Modify content
        if let imageURLString = bestAttemptContent.userInfo["image-url"] as? String,
           let imageURL = URL(string: imageURLString) {
            downloadImage(from: imageURL) { attachment in
                if let attachment = attachment {
                    bestAttemptContent.attachments = [attachment]
                }
                contentHandler(bestAttemptContent)
            }
        } else {
            contentHandler(bestAttemptContent)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        // Called when time limit (30 seconds) is exceeded
        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

    private func downloadImage(
        from url: URL,
        completion: @escaping (UNNotificationAttachment?) -> Void
    ) {
        URLSession.shared.downloadTask(with: url) { localURL, _, error in
            guard let localURL = localURL, error == nil else {
                completion(nil)
                return
            }

            let tempDirectory = FileManager.default.temporaryDirectory
            let fileName = url.lastPathComponent
            let destinationURL = tempDirectory.appendingPathComponent(fileName)

            try? FileManager.default.moveItem(at: localURL, to: destinationURL)

            let attachment = try? UNNotificationAttachment(
                identifier: fileName,
                url: destinationURL,
                options: nil
            )
            completion(attachment)
        }.resume()
    }
}

Extension มีเวลา 30 วินาทีในการแก้ไขเนื้อหา หากเกินขีดจำกัดนี้จะมีการเรียกเมธอด serviceExtensionTimeWillExpire()

Troubleshooting Push Notifications

การดีบัก push notification เป็นหัวข้อที่พบซ้ำในการสัมภาษณ์ ผู้สมัครควรรู้จักเครื่องมือและเทคนิคในการวินิจฉัย

ตรวจสอบสถานะการลงทะเบียน

DebugHelper.swiftswift
struct PushNotificationDebugger {

    static func checkNotificationStatus() async {
        let center = UNUserNotificationCenter.current()
        let settings = await center.notificationSettings()

        print("=== Push Notification Status ===")
        print("Authorization: \(settings.authorizationStatus.description)")
        print("Alert: \(settings.alertSetting.description)")
        print("Badge: \(settings.badgeSetting.description)")
        print("Sound: \(settings.soundSetting.description)")
        print("Notification Center: \(settings.notificationCenterSetting.description)")
    }
}

extension UNAuthorizationStatus {
    var description: String {
        switch self {
        case .notDetermined: return "Not Determined"
        case .denied: return "Denied"
        case .authorized: return "Authorized"
        case .provisional: return "Provisional"
        case .ephemeral: return "Ephemeral"
        @unknown default: return "Unknown"
        }
    }
}

ฟังก์ชัน debug นี้ช่วยตรวจสอบสถานะสิทธิ์ของการแจ้งเตือนได้อย่างรวดเร็ว

ข้อผิดพลาดที่พบบ่อยและวิธีแก้

ข้อผิดพลาดที่ควรรู้

ผู้สัมภาษณ์มักถามเกี่ยวกับข้อผิดพลาดที่พบบ่อยและแนวทางแก้ไข ต่อไปนี้คือสิ่งสำคัญที่สุดที่ควรจดจำ

ข้อผิดพลาดสาเหตุวิธีแก้
โทเคนไม่ถูกต้องสภาพแวดล้อมผิด (sandbox/prod)ตรวจสอบ provisioning profile
ไม่ได้รับการแจ้งเตือนเปิดโหมดประหยัดพลังงานทดสอบขณะแบตเต็ม
Extension ไม่ถูกเรียกPayload ไม่มี mutable-contentเพิ่ม "mutable-content": 1
Background fetch ล้มเหลวผู้ใช้ปิดแอปเองแจ้งผู้ใช้เกี่ยวกับข้อจำกัด

ทดสอบ APNs โดยตรง

สำหรับการทดสอบการแจ้งเตือนระหว่างพัฒนา เครื่องมือ curl ช่วยให้ส่งคำขอไปที่ APNs ได้โดยตรง:

TestPayload.swiftswift
struct APNsTestPayload {

    static let silentNotification = """
    {
        "aps": {
            "content-available": 1
        },
        "custom-data": "test"
    }
    """

    static let richNotification = """
    {
        "aps": {
            "alert": {
                "title": "New message",
                "body": "Message content"
            },
            "mutable-content": 1,
            "sound": "default"
        },
        "image-url": "https://example.com/image.jpg"
    }
    """
}

Payload ทดสอบเหล่านี้ช่วยตรวจสอบพฤติกรรมของแอปกับการแจ้งเตือนหลายประเภท

แนวปฏิบัติที่ดีในระดับ production

คำถามเกี่ยวกับแนวปฏิบัติที่ดีช่วยประเมินประสบการณ์ของผู้สมัครกับแอป production

การจัดการข้อผิดพลาดของเครือข่าย

PushTokenManager.swiftswift
actor PushTokenManager {

    private var pendingToken: String?
    private var retryCount = 0
    private let maxRetries = 3

    func registerToken(_ token: String) async {
        pendingToken = token
        await sendTokenWithRetry()
    }

    private func sendTokenWithRetry() async {
        guard let token = pendingToken else { return }

        do {
            try await APIClient.shared.registerPushToken(token)
            pendingToken = nil
            retryCount = 0
        } catch {
            retryCount += 1
            if retryCount < maxRetries {
                // Retry with exponential backoff
                let delay = UInt64(pow(2.0, Double(retryCount))) * 1_000_000_000
                try? await Task.sleep(nanoseconds: delay)
                await sendTokenWithRetry()
            }
        }
    }
}

การใช้ actor ช่วยให้การจัดการโทเคนปลอดภัยต่อการทำงานพร้อมกันในสภาพแวดล้อมแบบขนาน

การเก็บโทเคนแบบโลคัล

TokenStorage.swiftswift
struct TokenStorage {

    private static let tokenKey = "com.app.pushToken"

    static func save(_ token: String) {
        UserDefaults.standard.set(token, forKey: tokenKey)
    }

    static func retrieve() -> String? {
        UserDefaults.standard.string(forKey: tokenKey)
    }

    static func hasTokenChanged(_ newToken: String) -> Bool {
        guard let savedToken = retrieve() else { return true }
        return savedToken != newToken
    }
}

การบันทึกโทเคนแบบโลคัลช่วยลดการเรียกเครือข่ายที่ไม่จำเป็นเมื่อโทเคนยังไม่เปลี่ยน

สรุป

ความเชี่ยวชาญใน iOS Push Notifications แสดงถึงความเข้าใจอย่างลึกซึ้งในระบบนิเวศของ Apple และการสื่อสารระหว่างไคลเอนต์-เซิร์ฟเวอร์ จุดสำคัญที่ควรจำสำหรับการสัมภาษณ์:

✅ สถาปัตยกรรม APNs: เข้าใจกระบวนการตั้งแต่ลงทะเบียนจนถึงส่งการแจ้งเตือน

✅ Device token: วงจรชีวิตและการจัดการเมื่อโทเคนเปลี่ยน

✅ Notification Service Extension: ปรับแต่งเนื้อหาภายในกรอบ 30 วินาที

✅ Troubleshooting: รู้จักข้อผิดพลาดทั่วไปและวิธีแก้

✅ Silent notifications: content-available สำหรับ background fetch

✅ แนวปฏิบัติที่ดี: ตรรกะ retry การเก็บโทเคนแบบโลคัล การจัดการข้อผิดพลาด

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน iOS เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 29 เมษายน 2569

แท็ก

#ios
#push-notifications
#apns
#swift
#interview

แชร์

บทความที่เกี่ยวข้อง