There are a few things that can run before didFinishLaunchingWithOptions
. For example, Objective-C's load
method for NSObject
classes, UIApplicationDelegate
's willFinishLaunchingWithOptions
...
Even an innocent code addition before Logger.custom = FirebaseCustomLogger()
in didFinishLaunchingWithOptions
could end up calling the logger before it was properly set.
If you're confident that this won't happen in the future, adding nonisolated(unsafe)
may be fine.
Code that runs before didFinishLaunchingWithOptions
can trigger work that is executed in a different thread. For example, something as innocent as this:
func willFinishLaunchingWithOptions() {
Task {
// ...
Logger.custom.log("...") // <-- Which logger is this?
}
}
Written in the willFinishLaunchingWithOptions
, would race against the Logger.custom = ...
in didFinishLaunchingWithOptions
.
You may not have any code running before didFinishLaunchingWithOptions
now, but maybe you will in the future. This is very project dependent, so you'd know best.
Hmm. Dependency issues are tricky and very project dependent too. One idea that comes to mind is creating some basic object (for example BaseLogger
) somewhere accessible by both Core
and FirebaseCustomLogger
, with an API that allows configuring it (BaseLogger
) from the outside to reroute logs to Firebase. Something like this:
func applicationDidFinishLanching() {
Logger.custom.setupFirebaseRelay { log in
Analytics.logEvent(log)
}
}
With BaseLogger
handling the logic of relaying the messages received by Logger.custom.log()
to Firebase once that becomes available. Again, this is highly dependent on the kind of project you're writing, but I've noticed many projects end up adding multiple services to the logger instead of just one (for example: logging both to console and Firebase, or Firebase and Amplitude...). So you may find the need to create a similar logger object that is not Firebase-specific in the future.
But if all this sounds overkill to you, maybe a simple lock will do:
public extension Logger {
private static let _custom = OSAllocatedUnfairLock<any CustomLogger>(
initialState: EmptyCustomLogger()
)
static var custom: CustomLogger {
get {
_custom.withLock { logger in
return logger
}
}
set {
_custom.withLock { logger in
logger = newValue
}
}
}
}
This prevents Logger.custom
from being read from multiple threads at once, but it's a really fast operation so I don't think this could conceivably have any performance implications (unless you're calling Logger.custom
from multiple threads at once thousands of times per second, which sounds unlikely for a logger!).