Skip to content

Commit

Permalink
fix(iOS): KV, Context fixes (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
theproducer committed Apr 16, 2024
1 parent 18f72b6 commit 142e96d
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 95 deletions.
8 changes: 8 additions & 0 deletions .changeset/late-peas-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@capacitor/background-runner": minor
"ios-engine": minor
---

(iOS) KV: calling `get` on an non-existent key returns null instead of empty object
(iOS) Fixed an issue within Runner that could potentially cause EXC_BAD_ACCESS crashes

2 changes: 1 addition & 1 deletion apps/example-app/ios/App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 023978344a0d202e5020371cd007ff62c16044ac

COCOAPODS: 1.13.0
COCOAPODS: 1.15.2
122 changes: 54 additions & 68 deletions packages/capacitor-plugin/ios/Plugin/BackgroundRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import JavaScriptCore

public class BackgroundRunner {
public static let shared = BackgroundRunner()

private var config: RunnerConfig?
public var config: RunnerConfig?

private var runner = Runner()

public init() {
Expand All @@ -17,93 +17,51 @@ public class BackgroundRunner {
print("could not initialize BackgroundRunner: \(error)")
}
}

public func registerBackgroundTask() {
public func scheduleBackgroundTasks() throws {
guard let config = config else {
print("no runner to register")
return
throw BackgroundRunnerPluginError.noRunnerConfig
}

if config.event == nil {
print("cannot register background task without a event to call")
return
}

BGTaskScheduler.shared.register(forTaskWithIdentifier: config.label, using: nil) { task in
do {
task.expirationHandler = {
print("task timed out")
}

guard let event = config.event else {
throw BackgroundRunnerPluginError.invalidRunnerConfig(reason: "runner event is missing or invalid")
}

_ = try BackgroundRunner.shared.execute(config: config, event: event)

task.setTaskCompleted(success: true)
} catch {
print("background task error: \(error)")
task.setTaskCompleted(success: false)
}
}
}

public func scheduleBackgroundTasks() {
guard let config = config else {

if !config.autoSchedule {
return
}

guard let interval = config.interval else {
print("cannot register background task without a configured interval")
return
throw BackgroundRunnerPluginError.invalidRunnerConfig(reason: "cannot register background task without a configured interval")
}

let request = BGAppRefreshTaskRequest(identifier: config.label)
request.earliestBeginDate = Date(timeIntervalSinceNow: Double(interval) * 60)

do {
print("Scheduling \(config.label)")
print("Scheduling \(config.label)")

try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}

public func getConfig() -> RunnerConfig? {
return config
try BGTaskScheduler.shared.submit(request)
}

public func dispatchEvent(event: String, inputArgs: [String: Any]?, callbackId: String? = nil) throws {
if let config = config {
let waitGroup = DispatchGroup()
waitGroup.enter()

var err: Error?
public func registerBackgroundTask() throws {
guard let config = config else {
throw BackgroundRunnerPluginError.noRunnerConfig
}

// swiftlint:disable:next unowned_variable_capture
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
_ = try self.execute(config: config, event: event, inputArgs: inputArgs, callbackId: callbackId)
} catch {
err = error
print("[\(config.label)]: \(error)")
BGTaskScheduler.shared.register(forTaskWithIdentifier: config.label, using: nil) { task in
do {
task.expirationHandler = {
print("task timed out")
}

waitGroup.leave()
}
_ = try BackgroundRunner.shared.execute(config: config)

waitGroup.wait()

if let err = err {
throw err
task.setTaskCompleted(success: true)
} catch {
print("background task error: \(error)")
task.setTaskCompleted(success: false)
}
}
}

// swiftlint:disable:next cyclomatic_complexity function_body_length
public func execute(config: RunnerConfig, event: String, inputArgs: [String: Any]? = nil, callbackId: String? = nil) throws -> [String: Any]? {
public func execute(config: RunnerConfig, inputArgs: [String: Any]? = nil, callbackId: String? = nil) throws -> [String: Any]? {
do {
let context = try initContext(config: config, callbackId: callbackId)

Expand Down Expand Up @@ -170,9 +128,10 @@ public class BackgroundRunner {

waitGroup.enter()

try context.dispatchEvent(event: event, details: args)
try context.dispatchEvent(event: config.event, details: args)

waitGroup.wait()


if let rejection = rejectionErr {
throw EngineError.jsException(details: rejection.message)
Expand All @@ -185,6 +144,33 @@ public class BackgroundRunner {
}
}

public func dispatchEvent(event: String, inputArgs: [String: Any]?, callbackId: String? = nil) throws {
if let config = config {
let waitGroup = DispatchGroup()
waitGroup.enter()

var err: Error?

// swiftlint:disable:next unowned_variable_capture
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
_ = try self.execute(config: config, inputArgs: inputArgs, callbackId: callbackId)
} catch {
err = error
print("[\(config.label)]: \(error)")
}

waitGroup.leave()
}

waitGroup.wait()

if let err = err {
throw err
}
}
}

private func loadRunnerConfig() throws -> RunnerConfig? {
guard let capacitorConfigFileURL = Bundle.main.url(forResource: "capacitor.config.json", withExtension: nil) else {
throw BackgroundRunnerPluginError.runnerError(reason: "capacitor.config.json file not found")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

public enum BackgroundRunnerPluginError: Error, Equatable {
case noRunnerConfig
case invalidRunnerConfig(reason: String)
case invalidArgument(reason: String)
case runnerError(reason: String)
Expand Down
29 changes: 22 additions & 7 deletions packages/capacitor-plugin/ios/Plugin/BackgroundRunnerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class BackgroundRunnerPlugin: CAPPlugin {
name: UIApplication.didEnterBackgroundNotification,
object: nil
)

initWatchConnectivity()
}

Expand Down Expand Up @@ -54,22 +55,23 @@ public class BackgroundRunnerPlugin: CAPPlugin {

@objc func dispatchEvent(_ call: CAPPluginCall) {
do {
guard let runnerEvent = call.getString("event") else {
guard let runnerEvent = call.getString("event"), !runnerEvent.isEmpty else {
throw BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid")
}

let details = call.getObject("details", JSObject())

guard let config = impl.getConfig() else {
throw BackgroundRunnerPluginError.runnerError(reason: "no runner config loaded")
guard var config = impl.config else {
throw BackgroundRunnerPluginError.noRunnerConfig
}

config.event = runnerEvent

// swiftlint:disable:next unowned_variable_capture
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
let result = try self.impl.execute(
config: config,
event: runnerEvent,
inputArgs: details as [String: Any],
callbackId: call.callbackId
)
Expand All @@ -94,14 +96,27 @@ public class BackgroundRunnerPlugin: CAPPlugin {
}

@objc private func didEnterBackground() {
impl.scheduleBackgroundTasks()
do {
try impl.scheduleBackgroundTasks()
} catch {
print("could not schedule background task: \(error)")
}
}

public static func registerBackgroundTask() {
BackgroundRunner.shared.registerBackgroundTask()
do {
try BackgroundRunner.shared.registerBackgroundTask()
} catch {
print("could not register background task: \(error)")
}
}

public static func dispatchEvent(event: String, eventArgs: [AnyHashable: Any], completionHandler: ((Result<Bool, Error>) -> Void)) {
if event.isEmpty {
completionHandler(.failure(BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid")))
return
}

var args: [String: Any] = [:]

eventArgs.forEach { (key: AnyHashable, value: Any) in
Expand Down Expand Up @@ -147,7 +162,7 @@ public class BackgroundRunnerPlugin: CAPPlugin {
return
}

guard let config = impl.getConfig() else {
guard let config = impl.config else {
return
}

Expand Down
6 changes: 4 additions & 2 deletions packages/capacitor-plugin/ios/Plugin/CapacitorAPI/KV.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ class CapacitorKVStore: NSObject, CapacitorKVStoreExports {
}

static func get(_ key: String) -> JSValue {
guard let value = UserDefaults.standard.string(forKey: key) else {
return JSValue(nullIn: JSContext.current())
}

var valueWrapper: [String: String] = [:]

let value = UserDefaults.standard.string(forKey: key)
valueWrapper["value"] = value

return JSValue(object: valueWrapper, in: JSContext.current())
Expand Down
4 changes: 2 additions & 2 deletions packages/capacitor-plugin/ios/Plugin/RunnerConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public struct RunnerConfig {
let label: String
let src: String
var autoSchedule: Bool
let event: String?
var event: String
let repeats: Bool?
let enableWatchConnectivity: Bool

Expand All @@ -20,7 +20,7 @@ public struct RunnerConfig {
throw BackgroundRunnerPluginError.invalidRunnerConfig(reason: "runner source file path is missing or invalid")
}

let event = jsObject["event"] as? String
let event = jsObject["event"] as? String ?? ""
let repeats = jsObject["repeat"] as? Bool
let interval = jsObject["interval"] as? Int
let autoStart = jsObject["autoStart"] as? Bool
Expand Down
17 changes: 2 additions & 15 deletions packages/ios-engine/Sources/RunnerEngine/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,17 @@ import JavaScriptCore

public class Runner {
var machine: JSVirtualMachine
var contexts: [String: Context]

private let runLoop: RunLoop
private var thread: Thread?

public init() {
machine = JSVirtualMachine()
contexts = [:]
runLoop = RunLoop.current
}

func createContext(name: String) throws -> Context {
let context = try Context(vm: machine, contextName: name, runLoop: runLoop)

if contexts.keys.contains(name) {
throw EngineError.runnerError(details: "context with name \(name) already exists")
}

contexts[name] = context

return context
}

func destroyContext(name: String) {
contexts.removeValue(forKey: name)
return try Context(vm: machine, contextName: name, runLoop: runLoop)
}

func start() {
Expand All @@ -54,6 +40,7 @@ public class Runner {
}

thread.cancel()

while !thread.isFinished {
Thread.sleep(forTimeInterval: 0.05)
}
Expand Down

0 comments on commit 142e96d

Please sign in to comment.