Skip to content

Commit 0dbdd7d

Browse files
bsneedBrandon Sneed
andauthored
Fixes issue w/ UIApplication.shared not being supported in iOS App Extensions (#37)
* Fixes issue w/ UIApplication.shared not being supported in iOS App Extensions * Removed unnecessary check. * Clarified commentary around safeShared. Co-authored-by: Brandon Sneed <[email protected]>
1 parent a8e7c72 commit 0dbdd7d

File tree

2 files changed

+67
-32
lines changed

2 files changed

+67
-32
lines changed

Sources/Segment/Plugins/Platforms/iOS/iOSLifecycleMonitor.swift

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,34 @@ import Foundation
1111
import UIKit
1212

1313
public protocol iOSLifecycle {
14-
func applicationDidEnterBackground(application: UIApplication)
15-
func applicationWillEnterForeground(application: UIApplication)
16-
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
17-
func applicationDidBecomeActive(application: UIApplication)
18-
func applicationWillResignActive(application: UIApplication)
19-
func applicationDidReceiveMemoryWarning(application: UIApplication)
20-
func applicationWillTerminate(application: UIApplication)
21-
func applicationSignificantTimeChange(application: UIApplication)
22-
func applicationBackgroundRefreshDidChange(application: UIApplication, refreshStatus: UIBackgroundRefreshStatus)
14+
func applicationDidEnterBackground(application: UIApplication?)
15+
func applicationWillEnterForeground(application: UIApplication?)
16+
func application(_ application: UIApplication?, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
17+
func applicationDidBecomeActive(application: UIApplication?)
18+
func applicationWillResignActive(application: UIApplication?)
19+
func applicationDidReceiveMemoryWarning(application: UIApplication?)
20+
func applicationWillTerminate(application: UIApplication?)
21+
func applicationSignificantTimeChange(application: UIApplication?)
22+
func applicationBackgroundRefreshDidChange(application: UIApplication?, refreshStatus: UIBackgroundRefreshStatus)
2323
}
2424

2525
public extension iOSLifecycle {
26-
func applicationDidEnterBackground(application: UIApplication) { }
27-
func applicationWillEnterForeground(application: UIApplication) { }
28-
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { }
29-
func applicationDidBecomeActive(application: UIApplication) { }
30-
func applicationWillResignActive(application: UIApplication) { }
31-
func applicationDidReceiveMemoryWarning(application: UIApplication) { }
32-
func applicationWillTerminate(application: UIApplication) { }
33-
func applicationSignificantTimeChange(application: UIApplication) { }
34-
func applicationBackgroundRefreshDidChange(application: UIApplication, refreshStatus: UIBackgroundRefreshStatus) { }
26+
func applicationDidEnterBackground(application: UIApplication?) { }
27+
func applicationWillEnterForeground(application: UIApplication?) { }
28+
func application(_ application: UIApplication?, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { }
29+
func applicationDidBecomeActive(application: UIApplication?) { }
30+
func applicationWillResignActive(application: UIApplication?) { }
31+
func applicationDidReceiveMemoryWarning(application: UIApplication?) { }
32+
func applicationWillTerminate(application: UIApplication?) { }
33+
func applicationSignificantTimeChange(application: UIApplication?) { }
34+
func applicationBackgroundRefreshDidChange(application: UIApplication?, refreshStatus: UIBackgroundRefreshStatus) { }
3535
}
3636

3737
class iOSLifecycleMonitor: PlatformPlugin {
3838
let type = PluginType.utility
3939
var analytics: Analytics?
4040

41-
private var application: UIApplication
41+
private var application: UIApplication? = nil
4242
private var appNotifications: [NSNotification.Name] = [UIApplication.didEnterBackgroundNotification,
4343
UIApplication.willEnterForegroundNotification,
4444
UIApplication.didFinishLaunchingNotification,
@@ -50,7 +50,9 @@ class iOSLifecycleMonitor: PlatformPlugin {
5050
UIApplication.backgroundRefreshStatusDidChangeNotification]
5151

5252
required init() {
53-
application = UIApplication.shared
53+
// App extensions can't use UIAppication.shared, so
54+
// funnel it through something to check; Could be nil.
55+
application = UIApplication.safeShared
5456
setupListeners()
5557
}
5658

@@ -154,10 +156,15 @@ class iOSLifecycleMonitor: PlatformPlugin {
154156
}
155157

156158
func backgroundRefreshDidChange(notification: NSNotification) {
157-
analytics?.apply { (ext) in
158-
if let validExt = ext as? iOSLifecycle {
159-
validExt.applicationBackgroundRefreshDidChange(application: application,
160-
refreshStatus: application.backgroundRefreshStatus)
159+
// Not only would we not get this in an App Extension, but it would
160+
// be useless since we couldn't provide the application object or
161+
// the refreshStatus value.
162+
if !isAppExtension, let application = UIApplication.safeShared {
163+
analytics?.apply { (ext) in
164+
if let validExt = ext as? iOSLifecycle {
165+
validExt.applicationBackgroundRefreshDidChange(application: application,
166+
refreshStatus: application.backgroundRefreshStatus)
167+
}
161168
}
162169
}
163170
}
@@ -166,11 +173,11 @@ class iOSLifecycleMonitor: PlatformPlugin {
166173
// MARK: - Segment Destination Extension
167174

168175
extension SegmentDestination: iOSLifecycle {
169-
public func applicationWillEnterForeground(application: UIApplication) {
176+
public func applicationWillEnterForeground(application: UIApplication?) {
170177
enterForeground()
171178
}
172179

173-
public func applicationDidEnterBackground(application: UIApplication) {
180+
public func applicationDidEnterBackground(application: UIApplication?) {
174181
enterBackground()
175182
}
176183
}
@@ -180,15 +187,36 @@ extension SegmentDestination.UploadTaskInfo {
180187
self.url = url
181188
self.task = task
182189

183-
let taskIdentifier = UIApplication.shared.beginBackgroundTask { [self] in
184-
self.task.suspend()
185-
self.cleanup?()
190+
if let application = UIApplication.safeShared {
191+
let taskIdentifier = application.beginBackgroundTask { [self] in
192+
self.task.suspend()
193+
self.cleanup?()
194+
}
195+
self.taskID = taskIdentifier.rawValue
196+
197+
self.cleanup = { [self] in
198+
application.endBackgroundTask(UIBackgroundTaskIdentifier(rawValue: self.taskID))
199+
}
186200
}
187-
self.taskID = taskIdentifier.rawValue
201+
}
202+
}
203+
204+
extension UIApplication {
205+
static var safeShared: UIApplication? {
206+
// UIApplication.shared is not available in app extensions so try to get
207+
// it in a way that's safe for both.
188208

189-
self.cleanup = { [self] in
190-
UIApplication.shared.endBackgroundTask(UIBackgroundTaskIdentifier(rawValue: self.taskID))
209+
// if we are NOT an app extension, we need to get UIApplication.shared
210+
if !isAppExtension {
211+
// getting it like this allows us to avoid the compiler error that would
212+
// be generated even though we're guarding against app extensions.
213+
// there's no preprocessor macro or @available macro to help us here unfortunately
214+
// so this is the best i could do.
215+
return UIApplication.value(forKeyPath: "sharedApplication") as? UIApplication
191216
}
217+
// if we ARE an app extension, send back nil since we have no way to get the
218+
// application instance.
219+
return nil
192220
}
193221
}
194222

Sources/Segment/Utilities/Utils.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ internal var isUnitTesting: Bool = {
1313
return value
1414
}()
1515

16+
internal var isAppExtension: Bool = {
17+
if Bundle.main.bundlePath.hasSuffix(".appex") {
18+
return true
19+
}
20+
return false
21+
}()
22+
1623
internal func exceptionFailure(_ message: String) {
1724
if isUnitTesting {
1825
assertionFailure(message)

0 commit comments

Comments
 (0)