Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 56 additions & 33 deletions Sources/Segment/Plugins/Platforms/iOS/iOSLifecycleMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,34 @@ import Foundation
import UIKit

public protocol iOSLifecycle {
func applicationDidEnterBackground(application: UIApplication)
func applicationWillEnterForeground(application: UIApplication)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
func applicationDidBecomeActive(application: UIApplication)
func applicationWillResignActive(application: UIApplication)
func applicationDidReceiveMemoryWarning(application: UIApplication)
func applicationWillTerminate(application: UIApplication)
func applicationSignificantTimeChange(application: UIApplication)
func applicationBackgroundRefreshDidChange(application: UIApplication, refreshStatus: UIBackgroundRefreshStatus)
func applicationDidEnterBackground(application: UIApplication?)
func applicationWillEnterForeground(application: UIApplication?)
func application(_ application: UIApplication?, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
func applicationDidBecomeActive(application: UIApplication?)
func applicationWillResignActive(application: UIApplication?)
func applicationDidReceiveMemoryWarning(application: UIApplication?)
func applicationWillTerminate(application: UIApplication?)
func applicationSignificantTimeChange(application: UIApplication?)
func applicationBackgroundRefreshDidChange(application: UIApplication?, refreshStatus: UIBackgroundRefreshStatus)
}

public extension iOSLifecycle {
func applicationDidEnterBackground(application: UIApplication) { }
func applicationWillEnterForeground(application: UIApplication) { }
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { }
func applicationDidBecomeActive(application: UIApplication) { }
func applicationWillResignActive(application: UIApplication) { }
func applicationDidReceiveMemoryWarning(application: UIApplication) { }
func applicationWillTerminate(application: UIApplication) { }
func applicationSignificantTimeChange(application: UIApplication) { }
func applicationBackgroundRefreshDidChange(application: UIApplication, refreshStatus: UIBackgroundRefreshStatus) { }
func applicationDidEnterBackground(application: UIApplication?) { }
func applicationWillEnterForeground(application: UIApplication?) { }
func application(_ application: UIApplication?, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { }
func applicationDidBecomeActive(application: UIApplication?) { }
func applicationWillResignActive(application: UIApplication?) { }
func applicationDidReceiveMemoryWarning(application: UIApplication?) { }
func applicationWillTerminate(application: UIApplication?) { }
func applicationSignificantTimeChange(application: UIApplication?) { }
func applicationBackgroundRefreshDidChange(application: UIApplication?, refreshStatus: UIBackgroundRefreshStatus) { }
}

class iOSLifecycleMonitor: PlatformPlugin {
let type = PluginType.utility
var analytics: Analytics?

private var application: UIApplication
private var application: UIApplication? = nil
private var appNotifications: [NSNotification.Name] = [UIApplication.didEnterBackgroundNotification,
UIApplication.willEnterForegroundNotification,
UIApplication.didFinishLaunchingNotification,
Expand All @@ -50,7 +50,9 @@ class iOSLifecycleMonitor: PlatformPlugin {
UIApplication.backgroundRefreshStatusDidChangeNotification]

required init() {
application = UIApplication.shared
// App extensions can't use UIAppication.shared, so
// funnel it through something to check; Could be nil.
application = UIApplication.safeShared
setupListeners()
}

Expand Down Expand Up @@ -154,10 +156,15 @@ class iOSLifecycleMonitor: PlatformPlugin {
}

func backgroundRefreshDidChange(notification: NSNotification) {
analytics?.apply { (ext) in
if let validExt = ext as? iOSLifecycle {
validExt.applicationBackgroundRefreshDidChange(application: application,
refreshStatus: application.backgroundRefreshStatus)
// Not only would we not get this in an App Extension, but it would
// be useless since we couldn't provide the application object or
// the refreshStatus value.
if !isAppExtension, let application = UIApplication.safeShared {
analytics?.apply { (ext) in
if let validExt = ext as? iOSLifecycle {
validExt.applicationBackgroundRefreshDidChange(application: application,
refreshStatus: application.backgroundRefreshStatus)
}
}
}
}
Expand All @@ -166,11 +173,11 @@ class iOSLifecycleMonitor: PlatformPlugin {
// MARK: - Segment Destination Extension

extension SegmentDestination: iOSLifecycle {
public func applicationWillEnterForeground(application: UIApplication) {
public func applicationWillEnterForeground(application: UIApplication?) {
enterForeground()
}

public func applicationDidEnterBackground(application: UIApplication) {
public func applicationDidEnterBackground(application: UIApplication?) {
enterBackground()
}
}
Expand All @@ -180,15 +187,31 @@ extension SegmentDestination.UploadTaskInfo {
self.url = url
self.task = task

let taskIdentifier = UIApplication.shared.beginBackgroundTask { [self] in
self.task.suspend()
self.cleanup?()
if let application = UIApplication.safeShared {
let taskIdentifier = application.beginBackgroundTask { [self] in
self.task.suspend()
self.cleanup?()
}
self.taskID = taskIdentifier.rawValue

self.cleanup = { [self] in
application.endBackgroundTask(UIBackgroundTaskIdentifier(rawValue: self.taskID))
}
}
self.taskID = taskIdentifier.rawValue

self.cleanup = { [self] in
UIApplication.shared.endBackgroundTask(UIBackgroundTaskIdentifier(rawValue: self.taskID))
}
}

extension UIApplication {
static var safeShared: UIApplication? {
// UIApplication.shared is not available in app extensions.
if !isAppExtension {
// getting it like this allows us to avoid the compiler error that would
// be generated even though we're guarding against app extensions.
// there's no preprocessor macro or @available macro to help us here unfortunately
// so this is the best i could do.
return UIApplication.value(forKeyPath: "sharedApplication") as? UIApplication
}
return nil
}
}

Expand Down
7 changes: 7 additions & 0 deletions Sources/Segment/Utilities/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ internal var isUnitTesting: Bool = {
return value
}()

internal var isAppExtension: Bool = {
if Bundle.main.bundlePath.hasSuffix(".appex") {
return true
}
return false
}()

internal func exceptionFailure(_ message: String) {
if isUnitTesting {
assertionFailure(message)
Expand Down