From 9df42635b6bf5202e7413d48a14c07536a112459 Mon Sep 17 00:00:00 2001 From: KWANG HYOUN KIM Date: Sat, 26 Jan 2019 17:32:26 +0900 Subject: [PATCH] Updated: 1.3.0 feature. --- APNSUtil.podspec | 2 +- APNSUtil/Classes/APNSManager.swift | 83 ++++++++++++--------------- APNSUtilAppExtension.podspec | 2 +- Example/APNSUtil/APNSPayload.swift | 18 +++--- Example/APNSUtil/AppDelegate.swift | 11 ++-- Example/APNSUtil/ViewController.swift | 9 ++- README.md | 70 +++++++++++++++------- 7 files changed, 110 insertions(+), 85 deletions(-) diff --git a/APNSUtil.podspec b/APNSUtil.podspec index 19cc05f..5dacaf2 100644 --- a/APNSUtil.podspec +++ b/APNSUtil.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'APNSUtil' - s.version = '1.2.0' + s.version = '1.3.0' s.summary = 'APNSUtil is makes code simple using apple push notification service.' s.description = 'APNSUtil is makes code simple using apple push notification service.' s.homepage = 'https://github.com/pisces/APNSUtil' diff --git a/APNSUtil/Classes/APNSManager.swift b/APNSUtil/Classes/APNSManager.swift index 9ac721b..074ef79 100644 --- a/APNSUtil/Classes/APNSManager.swift +++ b/APNSUtil/Classes/APNSManager.swift @@ -13,7 +13,7 @@ public class APNSManager { // MARK: - Constants - public typealias Processing = (RemoteNotificationElement) -> Void + public typealias SubscribeClosure = (RemoteNotificationElement) -> Void public static let shared = APNSManager() private let kAuthorizationStatusDetermined: String = "kAuthorizationStatusDetermined" @@ -22,7 +22,7 @@ public class APNSManager { private var isInitialized: Bool = false private var types: UIUserNotificationType = [.sound, .alert, .badge] - private var processingClosureMap = [Int: Processing]() + private var subscribeClosureMap = [Int: SubscribeClosure]() private var elements = [RemoteNotificationElement]() private(set) var isAuthorizationStatusDetermined: Bool { get { @@ -37,42 +37,33 @@ public class APNSManager { // MARK: - Public methods @discardableResult - public func begin() -> APNSManager { + public func begin() -> Self { isInitialized = true dequeue() return self } - public func didFinishLaunching(withOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?, as type: T.Type) { + public func didFinishLaunching(withOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) { let remote = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] let local = launchOptions?[UIApplicationLaunchOptionsKey.localNotification] as? [AnyHashable: Any] guard let userInfo = remote ?? local else {return} - didReceive(userInfo: userInfo, as: type, isInactive: true) + didReceive(userInfo: userInfo, isInactive: true) } - public func didReceive(userInfo: [AnyHashable : Any], as: T.Type, isInactive: Bool) { - do { - let data = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) - let model = try JSONDecoder().decode(`as`, from: data) - enqueue(RemoteNotificationElement(isInactive: isInactive, model: model)).dequeue() - } catch { - } + public func didReceive(userInfo: [AnyHashable : Any], isInactive: Bool) { + enqueue(.init(isInactive: isInactive, userInfo: userInfo)).dequeue() } - public func processing(_ subscribable: Subscribable, _ closure: @escaping Processing) -> APNSManager { - guard processingClosureMap[subscribable.hash] == nil else {return self} - processingClosureMap[subscribable.hash] = closure - return self - } - public func register() -> APNSManager { + public func register() -> Self { #if !APP_EXTENSIONS if #available(iOS 10.0, *) { + let types = self.types let options: () -> UNAuthorizationOptions = { var rawValue: UInt = 0 - if self.types.rawValue & UIUserNotificationType.alert.rawValue == UIUserNotificationType.alert.rawValue { + if types.rawValue & UIUserNotificationType.alert.rawValue == UIUserNotificationType.alert.rawValue { rawValue |= UNAuthorizationOptions.alert.rawValue } - if self.types.rawValue & UIUserNotificationType.sound.rawValue == UIUserNotificationType.sound.rawValue { + if types.rawValue & UIUserNotificationType.sound.rawValue == UIUserNotificationType.sound.rawValue { rawValue |= UNAuthorizationOptions.sound.rawValue } - if self.types.rawValue & UIUserNotificationType.badge.rawValue == UIUserNotificationType.badge.rawValue { + if types.rawValue & UIUserNotificationType.badge.rawValue == UIUserNotificationType.badge.rawValue { rawValue |= UNAuthorizationOptions.badge.rawValue } return UNAuthorizationOptions(rawValue: rawValue) @@ -81,11 +72,7 @@ public class APNSManager { let center = UNUserNotificationCenter.current() center.delegate = UIApplication.shared.delegate as? UNUserNotificationCenterDelegate center.requestAuthorization(options: options()) { (granted, error) in - if let error = error { - print("Push registration failed") - print("ERROR: \(error.localizedDescription) - \(error.localizedDescription)") - return - } + guard error == nil else {return} DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() @@ -101,12 +88,17 @@ public class APNSManager { public func registerDeviceToken(_ deviceToken: Data) { APNSInstance.shared.setAPNSToken(deviceToken) } - public func setTypes(_ types: UIUserNotificationType) -> APNSManager { + public func setTypes(_ types: UIUserNotificationType) -> Self { self.types = types return self } + public func subscribe(_ target: T, _ closure: @escaping SubscribeClosure) -> Self { + guard subscribeClosureMap[target.hashValue] == nil else {return self} + subscribeClosureMap[target.hashValue] = closure + return self + } public func unregister() { - processingClosureMap.removeAll() + subscribeClosureMap.removeAll() #if !APP_EXTENSIONS UIApplication.shared.unregisterForRemoteNotifications() #endif @@ -114,43 +106,40 @@ public class APNSManager { elements.removeAll() isAuthorizationStatusDetermined = true } - public func unsubscribe(_ subscribable: Subscribable) { - processingClosureMap.removeValue(forKey: subscribable.hash) + public func unsubscribe(_ target: T) { + subscribeClosureMap.removeValue(forKey: target.hashValue) } // MARK: - Private methods private func dequeue() { guard isInitialized, elements.count > 0 else {return} - processingClosureMap.forEach { $0.value(elements.first!) } - elements.remove(at: 0) + let element = elements.removeFirst() + subscribeClosureMap.forEach { $0.value(element) } dequeue() } @discardableResult - private func enqueue(_ element: RemoteNotificationElement) -> APNSManager { + private func enqueue(_ element: RemoteNotificationElement) -> Self { elements.append(element) return self } } public struct RemoteNotificationElement { - public typealias T = Decodable + public let isInactive: Bool + public let userInfo: [AnyHashable : Any] - public private(set) var isInactive: Bool = false - private var model: T! - - public init(isInactive: Bool, model: T) { + public init(isInactive: Bool, userInfo: [AnyHashable : Any]) { self.isInactive = isInactive - self.model = model + self.userInfo = userInfo } - public func payload() -> E { - return model as! E + public func payload() -> T? { + do { + let data = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) + return try JSONDecoder().decode(T.self, from: data) + } catch { + return nil + } } } - -public protocol Subscribable { - var hash: Int {get} -} - -extension NSObject: Subscribable {} diff --git a/APNSUtilAppExtension.podspec b/APNSUtilAppExtension.podspec index 71b64f3..df0c4a0 100644 --- a/APNSUtilAppExtension.podspec +++ b/APNSUtilAppExtension.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'APNSUtilAppExtension' - s.version = '1.2.0' + s.version = '1.3.0' s.summary = 'APNSUtil is makes code simple using apple push notification service.' s.description = 'APNSUtil is makes code simple using apple push notification service.' s.homepage = 'https://github.com/pisces/APNSUtil' diff --git a/Example/APNSUtil/APNSPayload.swift b/Example/APNSUtil/APNSPayload.swift index 1b14938..fcd02b7 100644 --- a/Example/APNSUtil/APNSPayload.swift +++ b/Example/APNSUtil/APNSPayload.swift @@ -8,12 +8,16 @@ import APNSUtil -extension RemoteNotificationElement { - typealias T = APNSPayload -} - struct APNSPayload: Decodable { - var msg: String? - var id: String? + let aps: APS? + + struct APS: Decodable { + let sound: String? + let alert: Alert? + } + + struct Alert: Decodable { + let body: String? + let title: String? + } } - diff --git a/Example/APNSUtil/AppDelegate.swift b/Example/APNSUtil/AppDelegate.swift index 2275ba8..6750703 100644 --- a/Example/APNSUtil/AppDelegate.swift +++ b/Example/APNSUtil/AppDelegate.swift @@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - APNSManager.shared.didFinishLaunching(withOptions: launchOptions, as: APNSPayload.self) + APNSManager.shared.didFinishLaunching(withOptions: launchOptions) return true } @@ -24,10 +24,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { APNSManager.shared.registerDeviceToken(deviceToken) - // TODO: write code to update devicetoken with your api server + // <>(APNSInstance.shared.tokenString) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - // TODO: write code to update devicetoken with your api server } // MARK: - Push Notification for iOS 9 @@ -36,18 +35,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { application.registerForRemoteNotifications() } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { - APNSManager.shared.didReceive(userInfo: userInfo, as: APNSPayload.self, isInactive: application.applicationState == .inactive) + APNSManager.shared.didReceive(userInfo: userInfo, isInactive: application.applicationState == .inactive) } // MARK: - Push Notification for iOS 10 or higher @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - APNSManager.shared.didReceive(userInfo: notification.request.content.userInfo, as: APNSPayload.self, isInactive: false) + APNSManager.shared.didReceive(userInfo: notification.request.content.userInfo, isInactive: false) } @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - APNSManager.shared.didReceive(userInfo: response.notification.request.content.userInfo, as: APNSPayload.self, isInactive: true) + APNSManager.shared.didReceive(userInfo: response.notification.request.content.userInfo, isInactive: true) } } diff --git a/Example/APNSUtil/ViewController.swift b/Example/APNSUtil/ViewController.swift index 1fd9473..3ed7c75 100644 --- a/Example/APNSUtil/ViewController.swift +++ b/Example/APNSUtil/ViewController.swift @@ -16,8 +16,13 @@ class ViewController: UIViewController { APNSManager.shared .setTypes([.sound, .alert, .badge]) // setting user notification types .register() // registering to use apns - .processing(self) { // processing received apns payload - let payload: APNSPayload = $0.payload() // your custom payload with generic + .subscribe(self) { // subscribe receiving apns payload + // your custom payload with generic + guard let payload: APNSPayload = $0.payload() else { + return + } + + print("subscribe", $0.isInactive, $0.userInfo, payload) if $0.isInactive { // TODO: write code to present viewController on inactive diff --git a/README.md b/README.md index 456ee06..52c380e 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,21 @@ class ViewController: UIViewController { APNSManager.shared .setTypes([.sound, .alert, .badge]) // setting user notification types - .register() // registering to use apns - .processing(self) { // processing received apns payload - let payload: APNSPayload = $0.payload() // your custom payload with generic + .register() // register to use apns + .subscribe(self) { // subscribe to receive apns payload + // your payload model with generic + guard let payload: APNSPayload = $0.payload() else { + return + } + + print("subscribe", $0.isInactive, $0.userInfo, payload) if $0.isInactive { // TODO: write code to present viewController on inactive } else { // TODO: write code to show toast message on active } - }.begin() // begin receiving apns payload + }.begin() // begin to receive apns payload } } ``` @@ -61,7 +66,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Logic for handling push notifications at initial startup - APNSManager.shared.didFinishLaunching(withOptions: launchOptions, as: APNSPayload.self) + APNSManager.shared.didFinishLaunching(withOptions: launchOptions) return true } @@ -70,6 +75,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { APNSManager.shared.registerDeviceToken(deviceToken) // <>(APNSInstance.shared.tokenString) + // ex) myApiRepository.registerDeviceToken(APNSInstance.shared.tokenString) } // MARK: - Push Notification for iOS 9 @@ -78,40 +84,34 @@ class AppDelegate: UIResponder, UIApplicationDelegate { application.registerForRemoteNotifications() } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { - APNSManager.shared.didReceive(userInfo: userInfo, as: APNSPayload.self, isInactive: application.applicationState == .inactive) + APNSManager.shared.didReceive(userInfo: userInfo, isInactive: application.applicationState == .inactive) } // MARK: - Push Notification for iOS 10 or higher @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - APNSManager.shared.didReceive(userInfo: notification.request.content.userInfo, as: APNSPayload.self, isInactive: false) + APNSManager.shared.didReceive(userInfo: notification.request.content.userInfo, isInactive: false) } @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - APNSManager.shared.didReceive(userInfo: response.notification.request.content.userInfo, as: APNSPayload.self, isInactive: true) + APNSManager.shared.didReceive(userInfo: response.notification.request.content.userInfo, isInactive: true) } } ``` ### Implement your payload model ```swift -import APNSUtil - -extension RemoteNotificationElement { - typealias T = APNSPayload -} - struct APNSPayload: Decodable { let aps: APS? - - // write properties for your payload. - + + // Add properties here you need + struct APS: Decodable { let sound: String? let alert: Alert? } - + struct Alert: Decodable { let body: String? let title: String? @@ -119,6 +119,34 @@ struct APNSPayload: Decodable { } ``` +### Using with your payload model as generic + +```swift + APNSManager.shared + .setTypes([.sound, .alert, .badge]) + .register() + .subscribe(self) { + guard let payload: APNSPayload = $0.payload() else { + return + } + + // write here to process payload model + }.begin() +``` + +### Using with raw userInfo + +```swift + APNSManager.shared + .setTypes([.sound, .alert, .badge]) + .register() + .subscribe(self) { + let userInfo = $0.userInfo + + // write here to process userInfo + }.begin() +``` + ## Installation ### CocoaPods @@ -139,12 +167,12 @@ platform :ios, '9.0' # Default target '' do - pod 'APNSUtil', '~> 1.2.0' + pod 'APNSUtil', '~> 1.3.0' end # for AppExtension target '' do - pod 'APNSUtil/AppExtension', '~> 1.2.0' + pod 'APNSUtil/AppExtension', '~> 1.3.0' end ``` @@ -168,7 +196,7 @@ $ brew install carthage To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: ```ogdl -github "pisces/APNSUtil" ~> 1.2.0 +github "pisces/APNSUtil" ~> 1.3.0 ``` Run `carthage update` to build the framework and drag the built `APNSUtil.framework` into your Xcode project.