Skip to content

Commit

Permalink
feat: enable background mode for the example app
Browse files Browse the repository at this point in the history
  • Loading branch information
duyhungtnn committed Aug 31, 2023
1 parent 8df5ae3 commit 9a3934d
Show file tree
Hide file tree
Showing 20 changed files with 644 additions and 79 deletions.
234 changes: 230 additions & 4 deletions Bucketeer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions Bucketeer.xcodeproj/xcshareddata/xcschemes/ExampleSwiftUI.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "947A04ED2A9CF08500BE33F7"
BuildableName = "ExampleSwiftUI.app"
BlueprintName = "ExampleSwiftUI"
ReferencedContainer = "container:Bucketeer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "947A04ED2A9CF08500BE33F7"
BuildableName = "ExampleSwiftUI.app"
BlueprintName = "ExampleSwiftUI"
ReferencedContainer = "container:Bucketeer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "947A04ED2A9CF08500BE33F7"
BuildableName = "ExampleSwiftUI.app"
BlueprintName = "ExampleSwiftUI"
ReferencedContainer = "container:Bucketeer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
10 changes: 0 additions & 10 deletions Bucketeer/Sources/Internal/Event/EventInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,9 @@ final class EventInteractorImpl: EventInteractor {
let logger: Logger?
let featureTag: String

private let sendEventSemaphore = DispatchSemaphore(value: 1)
private let metadata: [String: String]
private var eventUpdateListener: EventUpdateListener?

deinit {
// If EventInteractor deinit before the sendEvents() finished could cause "bad instrucstion crash"
// Because APIClient didn't provides the way to cancel the ongoing request
// over-signaling does not introduce new problems
// https://stackoverflow.com/questions/70457141/safe-to-signal-semaphore-before-deinitialization-just-in-case
// verified it will be okay see `testSemaphoreOverSignalShouldNotCauseProblem` in EventInteractorTests.swift
sendEventSemaphore.signal()
}

init(
sdkVersion: String,
appVersion: String,
Expand Down
10 changes: 10 additions & 0 deletions Bucketeer/Sources/Internal/Scheduler/BackgroundTask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
#if canImport(BackgroundTasks)
import BackgroundTasks

@available(iOS 13.0, tvOS 13.0, *)
protocol BackgroundTask: ScheduledTask {
func getTaskIndentifier() -> String
func handle(_ task: BGTask)
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

class BackgroundTaskIndentifier {
static let fetchEvaluations = "io.bucketeer.background.fetch.evaluations"
static let flushEvents = "io.bucketeer.background.flush.events"
}
57 changes: 37 additions & 20 deletions Bucketeer/Sources/Internal/Scheduler/EvaluationBackgroundTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import BackgroundTasks

@available(iOS 13.0, tvOS 13.0, *)
final class EvaluationBackgroundTask {
static let taskId = "io.bucketeer.background.fetch.evaluations"

private weak var component: Component?
private let queue: DispatchQueue

Expand All @@ -15,40 +13,49 @@ final class EvaluationBackgroundTask {
self.queue = queue
}

func register() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.taskId, using: nil) { task in
self.handleAppRefresh(task: task as? BGAppRefreshTask)
}
}

func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: Self.taskId)
let request = BGProcessingTaskRequest(identifier: getTaskIndentifier())
request.requiresNetworkConnectivity = true
let interval: TimeInterval = TimeInterval(component?.config.backgroundPollingInterval ?? Constant.DEFAULT_BACKGROUND_POLLING_INTERVAL_MILLIS)
request.earliestBeginDate = Date(timeIntervalSinceNow: interval / 1000)

do {
try BGTaskScheduler.shared.submit(request)
component?.config.logger?.debug(message: "[EvaluationBackgroundTask] The background task is scheduled.")
} catch {
component?.config.logger?.error(error)
}
}

func handleAppRefresh(task: BGAppRefreshTask?) {
private func handleAppRefresh(_ task: BGTask) {
component?.config.logger?.debug(message: "[EvaluationBackgroundTask] handleAppRefresh")
// Schedule a new refresh task.
scheduleAppRefresh()

guard let component = self.component else { return }
BKTClient.fetchEvaluationsSync(
component: component,
dispatchQueue: queue,
timeoutMillis: nil,
completion: { error in
task?.setTaskCompleted(success: error == nil)
queue.async { [weak self] in
if let taskQueue = self?.queue {
BKTClient.fetchEvaluationsSync(
component: component,
dispatchQueue: taskQueue,
timeoutMillis: nil,
completion: { error in
task.setTaskCompleted(success: error == nil)
if let error {
self?.component?.config.logger?.error(error)
} else {
self?.component?.config.logger?.debug(message: "[EventBackgroundTask] success")
}
}
)
}
)
}
// Provide the background task with an expiration handler that cancels the operation.
task?.expirationHandler = { [weak self] in
self?.component?.config.logger?.warn(message: "The background task is expired.")
task.expirationHandler = { [weak self] in
self?.component?.config.logger?.debug(message: "[EvaluationBackgroundTask] The background task is expired.")
// Must set task completed, if we don't do this OS will throttle and limit our background task request
// https://developer.apple.com/videos/play/wwdc2022/10142/
task.setTaskCompleted(success: false)
}
}
}
Expand All @@ -60,8 +67,18 @@ extension EvaluationBackgroundTask: ScheduledTask {
}

func stop() {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Self.taskId)
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: getTaskIndentifier())
}
}

@available(iOS 13.0, tvOS 13.0, *)
extension EvaluationBackgroundTask: BackgroundTask {
func getTaskIndentifier() -> String {
return BackgroundTaskIndentifier.fetchEvaluations
}

func handle(_ task: BGTask) {
handleAppRefresh(task)
}
}
#endif
38 changes: 24 additions & 14 deletions Bucketeer/Sources/Internal/Scheduler/EventBackgroundTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import BackgroundTasks

@available(iOS 13.0, tvOS 13.0, *)
final class EventBackgroundTask {
static let taskId = "io.bucketeer.background.flush.events"

private weak var component: Component?
private let queue: DispatchQueue

Expand All @@ -15,40 +13,42 @@ final class EventBackgroundTask {
self.queue = queue
}

func register() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.taskId, using: nil) { task in
self.handleAppRefresh(task: task as? BGAppRefreshTask)
}
}

func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: Self.taskId)
let request = BGProcessingTaskRequest(identifier: getTaskIndentifier())
request.requiresNetworkConnectivity = true
let interval: TimeInterval = TimeInterval(component?.config.eventsFlushInterval ?? Constant.DEFAULT_FLUSH_INTERVAL_MILLIS)
request.earliestBeginDate = Date(timeIntervalSinceNow: interval / 1000)

do {
try BGTaskScheduler.shared.submit(request)
component?.config.logger?.debug(message: "[EventBackgroundTask] The background task is scheduled.")
} catch {
component?.config.logger?.error(error)
}
}

func handleAppRefresh(task: BGAppRefreshTask?) {
private func handleAppRefresh(_ task: BGTask) {
component?.config.logger?.debug(message: "[EventBackgroundTask] handleAppRefresh")
// Schedule a new refresh task.
scheduleAppRefresh()

guard let component = self.component else { return }
queue.async {
BKTClient.flushSync(component: component) { [weak self] error in
task?.setTaskCompleted(success: error == nil)
task.setTaskCompleted(success: error == nil)
if let error {
self?.component?.config.logger?.error(error)
} else {
self?.component?.config.logger?.debug(message: "[EventBackgroundTask] success")
}
}
}
// Provide the background task with an expiration handler that cancels the operation.
task?.expirationHandler = { [weak self] in
self?.component?.config.logger?.warn(message: "The background task is expired.")
task.expirationHandler = { [weak self] in
self?.component?.config.logger?.debug(message: "[EventBackgroundTask] The background task is expired.")
// Must set task completed, if we don't do this OS will throttle and limit our background task request
// https://developer.apple.com/videos/play/wwdc2022/10142/
task.setTaskCompleted(success: false)
}
}
}
Expand All @@ -60,8 +60,18 @@ extension EventBackgroundTask: ScheduledTask {
}

func stop() {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Self.taskId)
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: getTaskIndentifier())
}
}

@available(iOS 13.0, tvOS 13.0, *)
extension EventBackgroundTask: BackgroundTask {
func getTaskIndentifier() -> String {
return BackgroundTaskIndentifier.flushEvents
}

func handle(_ task: BGTask) {
handleAppRefresh(task)
}
}
#endif
Loading

0 comments on commit 9a3934d

Please sign in to comment.