App.swift 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import Foundation
  2. /// Backing storage for `logger`.
  3. ///
  4. /// ## Safety
  5. /// This is only ever mutated once, almost immediately after the app is launched and
  6. /// well before we do any concurrency shenanigans. Subsequent reads are always safe
  7. /// since `Logger` is `Sendable`.
  8. nonisolated(unsafe) private var _logger: Logger?
  9. /// The global logger.
  10. @_spi(Backends) public var logger: Logger {
  11. guard let _logger else {
  12. let logger = Logger(label: "TestLogger")
  13. logger.trace("logger used before initialization")
  14. _logger = logger
  15. return logger
  16. }
  17. return _logger
  18. }
  19. /// An application.
  20. @MainActor
  21. public protocol App {
  22. /// The backend used to render the app.
  23. associatedtype Backend: BaseAppBackend
  24. /// The type of scene representing the content of the app.
  25. associatedtype Body: Scene
  26. /// Metadata loaded at app start up.
  27. ///
  28. /// By default SwiftCrossUI attempts to load metadata inserted by Swift
  29. /// Bundler if present. Used by backends' default ``App/backend``
  30. /// implementations if not `nil`.
  31. static var metadata: AppMetadata? { get }
  32. /// The application's backend.
  33. var backend: Backend { get }
  34. /// The content of the app.
  35. @SceneBuilder var body: Body { get }
  36. /// Creates an instance of the app.
  37. ///
  38. /// This initializer is run before anything else, so you can perform early
  39. /// setup tasks in here, such as opening a database or preparing a
  40. /// dependency injection library.
  41. init()
  42. }
  43. /// Force refresh the entire scene graph. Used by hot reloading. If you need to do
  44. /// this in your own code then something has gone very wrong...
  45. @MainActor
  46. public var _forceRefresh: () -> Void = {}
  47. /// Metadata embedded by Swift Bundler, if present. Loaded at app start up.
  48. ///
  49. /// This will contain the app's metadata, if present, by the time ``App/init()``
  50. /// gets called.
  51. @MainActor
  52. private var swiftBundlerAppMetadata: AppMetadata?
  53. /// An error encountered when parsing Swift Bundler metadata.
  54. private enum SwiftBundlerMetadataError: LocalizedError {
  55. case noExecutableURL
  56. case failedToReadExecutable
  57. case emptyMetadata
  58. case jsonNotDictionary(String)
  59. case missingAppIdentifier
  60. case missingAppVersion
  61. case badMetadataPointer
  62. var errorDescription: String? {
  63. switch self {
  64. case .noExecutableURL:
  65. "no executable URL"
  66. case .failedToReadExecutable:
  67. "executable failed to read itself (to extract metadata)"
  68. case .emptyMetadata:
  69. "metadata found but was empty"
  70. case .jsonNotDictionary:
  71. "root metadata JSON value wasn't an object"
  72. case .missingAppIdentifier:
  73. "missing 'appIdentifier' (of type String)"
  74. case .missingAppVersion:
  75. "missing 'appVersion' (of type String)"
  76. case .badMetadataPointer:
  77. """
  78. bad metadata pointer returned by injected metadata function; \
  79. this causes segfaults on some systems so metadata parsing has \
  80. been skipped. update to a version of Swift Bundler newer than \
  81. commit 7b9c6a45fa5d0266985d45a3d12bc8d9fd729b84 to restore \
  82. metadata parsing
  83. """
  84. }
  85. }
  86. }
  87. extension App {
  88. /// Metadata loaded at app start up.
  89. ///
  90. /// This will contain the app's metadata, if present, by the time
  91. /// ``App/init()`` gets called.
  92. public static var metadata: AppMetadata? {
  93. swiftBundlerAppMetadata
  94. }
  95. /// The default log handler for apps which don't specify a custom one.
  96. ///
  97. /// This simply outputs logs to standard error.
  98. ///
  99. /// # See Also
  100. /// - <doc:Logging>
  101. /// Runs the application.
  102. public static func main() {
  103. Backend.earlySetup()
  104. extractMetadataAndInitializeLogging()
  105. let app = Self()
  106. let backend = app.backend
  107. let _app = _App(app, backend: backend)
  108. _forceRefresh = {
  109. backend.runInMainThread {
  110. _app.refreshSceneGraph()
  111. }
  112. }
  113. _app.run()
  114. }
  115. private static func extractMetadataAndInitializeLogging() {
  116. _logger = Logger(label: "SwiftCrossUI")
  117. }
  118. }
  119. // MARK: - Metadata extraction
  120. extension App {
  121. private static func extractSwiftBundlerMetadata() throws -> AppMetadata? {
  122. return nil
  123. }
  124. }