diff --git a/Demo/Demo/DemoApp.swift b/Demo/Demo/DemoApp.swift index ccc33a7..b1b12ba 100644 --- a/Demo/Demo/DemoApp.swift +++ b/Demo/Demo/DemoApp.swift @@ -14,7 +14,7 @@ struct DemoApp: App { appService.bootstrap(.eager) } @ObservedObject - var appService = ServiceValues[AppService.self] + var appService = Application.shared @SceneBuilder public var body: some Scene { WindowGroup { @@ -22,11 +22,11 @@ struct DemoApp: App { .chain { view in if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { view.task { - appService.bootstrap(.root) + appService.bootstrap(.splash) } } else { view.onAppear { - appService.bootstrap(.root) + appService.bootstrap(.splash) } } } diff --git a/Demo/Demo/Persistence.swift b/Demo/Demo/Persistence.swift index 1ed82df..13f37d7 100644 --- a/Demo/Demo/Persistence.swift +++ b/Demo/Demo/Persistence.swift @@ -9,7 +9,7 @@ import CoreData import DFService struct PersistenceController { static var runtime: PersistenceController { - if ServiceValues[RuntimeService.self].isPreview { + if Application.shared[RuntimeService.self].isPreview { return Self.preview } else { return Self.shared diff --git a/Demo/Demo/providers/RouteServiceProvider.swift b/Demo/Demo/providers/RouteServiceProvider.swift index a109cb2..fb89624 100644 --- a/Demo/Demo/providers/RouteServiceProvider.swift +++ b/Demo/Demo/providers/RouteServiceProvider.swift @@ -2,11 +2,12 @@ import DFService import SwiftUI class RouteServiceProvider: ServiceProvider { - override func performAsyncStartup() async { + override func performAsyncStartup() async throws { // app.rootView = AnyView(buildRoot()) + throw CommonError.unImplemented() } override var when: ServiceProvider.ProviderWhen { - return .window + return .eager } diff --git a/Sources/DFService/ServiceValues.swift b/Sources/DFService/Application.swift similarity index 51% rename from Sources/DFService/ServiceValues.swift rename to Sources/DFService/Application.swift index ffcd842..a712d45 100644 --- a/Sources/DFService/ServiceValues.swift +++ b/Sources/DFService/Application.swift @@ -1,27 +1,18 @@ -public struct ServiceValues { +import SwiftUI + +public class Application { public static let version = "0.1.0" - - public static var shared = ServiceValues() + fileprivate static var _instance: Application? + public var loadProviders: [DFService.ServiceProvider] = [] + @Published + public var rootView: AnyView = AnyView(RouterView()) private var storage: [FactoryKey: Any] = [:] private var serviceNames: [ServiceName: FactoryKey] = [:] - - public init() {} - + public required init() {} } -extension ServiceValues { - public static subscript(service: Service.Type, tag: String = "") - -> Service.Value - { - get { - return Self.shared[service, tag] - } - set { - Self.shared[service, tag] = newValue - } - } - public subscript(service: Service.Type, tag: String = "") -> Service.Value - { +extension Application: DFApplication { + public subscript(service: Service.Type, tag: String = "") -> Service.Value where Service : DFServiceKey { get { let key = FactoryKey(type: service, tag: tag) if let value = storage[key] as? Service.Value { @@ -34,7 +25,29 @@ extension ServiceValues { storage[key] = newValue } } - public subscript(_ name: ServiceName, tag: String = "") -> Service.Type? { + + public var providerType: [DFService.ServiceProvider.Type] { + return [] + } + + public static var shared: Application { + if let value = _instance { + return value + } + let value = Application() + _instance = value + do { + let provider = try Bundle.main.app.loadClass(ofType: ServiceProvider.self, named: "AppServiceProvider") + value.provider(provider) + } catch { + + } + return value + } +} + +extension Application { + public subscript(_ name: ServiceName, tag: String = "") -> Service.Type? { set { guard newValue != nil else { serviceNames[name] = nil @@ -63,11 +76,19 @@ extension ServiceValues { } } -#if canImport(SwiftUI) -import SwiftUI +extension Application: ObservableObject {} + +struct AppEnvironmentKey: EnvironmentKey { + static var defaultValue = Application.shared +} + extension EnvironmentValues { - public var serviceValues: ServiceValues { - return ServiceValues.shared + public var application: Application { + get { + return self[AppEnvironmentKey.self] + } + set { + self[AppEnvironmentKey.self] = newValue + } } } -#endif diff --git a/Sources/DFService/DFError.swift b/Sources/DFService/CommonError.swift similarity index 90% rename from Sources/DFService/DFError.swift rename to Sources/DFService/CommonError.swift index c2da9c0..b5fa0b1 100644 --- a/Sources/DFService/DFError.swift +++ b/Sources/DFService/CommonError.swift @@ -1,6 +1,6 @@ import Foundation /// 常用错误定义 -public enum DFError: Error { +public enum CommonError: Error { /// 业务错误 case biz(code: Int, msg: String) /// 未实现 diff --git a/Sources/DFService/contract/DFApiCall.swift b/Sources/DFService/contract/DFApiCall.swift index ef459d5..9ad1eeb 100644 --- a/Sources/DFService/contract/DFApiCall.swift +++ b/Sources/DFService/contract/DFApiCall.swift @@ -1,6 +1,7 @@ public class ApiCallConext { let method: String let param: Codable + public var options = [String: Any]() public init(method: String, param: Codable) { self.method = method diff --git a/Sources/DFService/contract/DFApiKey.swift b/Sources/DFService/contract/DFApiKey.swift deleted file mode 100644 index 7d58090..0000000 --- a/Sources/DFService/contract/DFApiKey.swift +++ /dev/null @@ -1,26 +0,0 @@ -import SwiftUI -public protocol DFApiService: EnvironmentKey { - associatedtype Value - static var defaultValue: Self.Value { get } -} - -extension DFApplication { - public subscript(service: Service.Type) -> Service.Value { - get { - return serviceValues[service] - } - set { - ServiceValues.shared[service] = newValue - } - } -} - -protocol LogHandle { - func log() -} - -struct Mock: LogHandle { - func log() { - - } -} diff --git a/Sources/DFService/contract/DFApplication.swift b/Sources/DFService/contract/DFApplication.swift index 416cf8d..68999bc 100644 --- a/Sources/DFService/contract/DFApplication.swift +++ b/Sources/DFService/contract/DFApplication.swift @@ -6,39 +6,6 @@ public protocol DFApplication: AnyObject { var providerType: [ServiceProvider.Type] { get } /// 已注册的服务提供者 var loadProviders: [ServiceProvider] { get set } - var rootView: AnyView { get set } - var serviceValues: ServiceValues { get } -} - - -public extension DFApplication { - var serviceValues: ServiceValues { - return .shared - } - - @discardableResult - func provider(_ serviceType: T.Type = T.self) -> T { - - let loadProvider = loadProviders.first { loaded in - return loaded.name == serviceType.name - } - if let provider = loadProvider as? T { - return provider - } - let provider = serviceType.init(self) - loadProviders.append(provider) - provider.register() - self[LogService.self].debug("注册服务\(provider)") - return provider - } - - - func bootstrap(_ when: ServiceProvider.ProviderWhen) { - provider(BootstrapServiceProvider.self).bootstrap(when) - } - /// 重置启动,执行provider Shutdown - func reset(_ when: ServiceProvider.ProviderWhen) { - provider(BootstrapServiceProvider.self).bootstrap(when) - } + subscript(service: Service.Type, tag: String) -> Service.Value { get set } } diff --git a/Sources/DFService/contract/DFHandler.swift b/Sources/DFService/contract/DFHandler.swift new file mode 100644 index 0000000..b9283c3 --- /dev/null +++ b/Sources/DFService/contract/DFHandler.swift @@ -0,0 +1,32 @@ +public protocol DFHandler { + associatedtype Input + associatedtype Output = Void + + func handleSync(_ input: Input) throws -> Output + func handle(_ input: Input) async throws-> Output +} + +public extension DFHandler { + func handle(_ input: Input) async throws -> Output { + return try self.handleSync(input) + } +} + + +public struct AnyHandler: DFHandler { + public func handleSync(_ input: Input) throws -> Output { + return try _handlerSync(input) + } + public func handle(_ input: Input) async throws -> Output { + return try await _handler(input) + } + + private let _handlerSync:(Input) throws -> Output + private let _handler:(Input) async throws -> Output + public init(_ handler: T) where T.Input == Input, T.Output == Output { + self._handler = handler.handle(_:) + self._handlerSync = handler.handleSync(_:) + } +} + + diff --git a/Sources/DFService/contract/DFServiceKey.swift b/Sources/DFService/contract/DFServiceKey.swift new file mode 100644 index 0000000..df07b34 --- /dev/null +++ b/Sources/DFService/contract/DFServiceKey.swift @@ -0,0 +1,5 @@ +import SwiftUI +public protocol DFServiceKey { + associatedtype Value + static var defaultValue: Self.Value { get } +} diff --git a/Sources/DFService/core/ServiceName.swift b/Sources/DFService/core/ServiceName.swift index f681786..e21910c 100644 --- a/Sources/DFService/core/ServiceName.swift +++ b/Sources/DFService/core/ServiceName.swift @@ -18,8 +18,8 @@ extension ServiceName: DFApiCall { @discardableResult public func callAsFunction(_ context: ApiCallConext) async throws -> Any { - guard let value = ServiceValues.shared.findBy(self) else { - throw DFError.unImplemented() + guard let value = Application.shared.findBy(self) else { + throw CommonError.unImplemented() } return try await value.callAsFunction(context) } diff --git a/Sources/DFService/core/ServiceProvider.swift b/Sources/DFService/core/ServiceProvider.swift index a3710ce..782310c 100644 --- a/Sources/DFService/core/ServiceProvider.swift +++ b/Sources/DFService/core/ServiceProvider.swift @@ -1,27 +1,27 @@ /// 应用内服务提供者 /// 在应用不同时机启动服务 open class ServiceProvider { - public let app: DFApplication + public let app: Application /// 服务是否启动 public internal(set) var isBooted: Bool = false - public required init(_ app: DFApplication) { + public required init(_ app: Application) { self.app = app } /// 注册服务 open func register() {} /// 模拟异步启动逻辑 - open func performAsyncStartup() async { + open func performAsyncStartup() async throws { // 重写此方法以实现实际的异步启动逻辑 -// try? await Task.sleep(nanoseconds: 1_000_000_000)// 模拟1秒的异步启动 +// try await Task.sleep(nanoseconds: 1_000_000_000)// 模拟1秒的异步启动 } /// 模拟异步关闭逻辑 - open func performAsyncShutdown() async { + open func performAsyncShutdown() async throws { // 重写此方法以实现实际的异步关闭逻辑 // try await Task.sleep(nanoseconds: 1_000_000_000) // 模拟1秒的异步关闭 } open var when: ProviderWhen { - return .root + return .splash } var name: String { @@ -58,8 +58,8 @@ extension ServiceProvider { public static let eager = ProviderWhen(rawValue: 0) /// 窗口创建后启动 public static let window = ProviderWhen(rawValue: 4) - /// 主窗口后启动 - public static let root = ProviderWhen(rawValue: 8) + /// 主窗口创建的第一个页面 + public static let splash = ProviderWhen(rawValue: 8) public static func + (lhs: ProviderWhen, rhs: Int) -> ProviderWhen { return ProviderWhen(rawValue: lhs.rawValue + rhs) diff --git a/Sources/DFService/extension/app.bundle.swift b/Sources/DFService/extension/app.bundle.swift index 463aabf..72689d9 100644 --- a/Sources/DFService/extension/app.bundle.swift +++ b/Sources/DFService/extension/app.bundle.swift @@ -35,11 +35,11 @@ extension Space where Base == Bundle { name = namespace.replacingOccurrences(of: " ", with: "_") + "." + name } guard name.components(separatedBy: ".").count > 1 else { - throw DFError.notFound("module") + throw CommonError.notFound("module") } - guard let loadedClass = base.classNamed(name) else { throw DFError.notFound(type) } + guard let loadedClass = base.classNamed(name) else { throw CommonError.notFound(type) } guard let castedClass = loadedClass as? T.Type else { - throw DFError.convert(from: String(describing: loadedClass), to: name) + throw CommonError.convert(from: String(describing: loadedClass), to: name) } return castedClass diff --git a/Sources/DFService/service/AppService.swift b/Sources/DFService/service/AppService.swift index d0c9a39..ad1a1d9 100644 --- a/Sources/DFService/service/AppService.swift +++ b/Sources/DFService/service/AppService.swift @@ -1,26 +1,3 @@ -public struct AppService: DFApiService { - public static var defaultValue: Value { - return Value.shared - } -} - -extension AppService { - open class Value: DFApplication, ObservableObject { - - internal static var shared = Value() - public init() {} - public var providerType: [ServiceProvider.Type] { - do { - let provider = try Bundle.main.app.loadClass(ofType: ServiceProvider.self, named: "AppServiceProvider") - return [provider] - } catch { - return [] - } - } - - public var loadProviders: [ServiceProvider] = [] - - @Published - public var rootView: AnyView = AnyView(RouterView()) - } +public struct AppService: DFServiceKey { + public static var defaultValue = Application.shared } diff --git a/Sources/DFService/service/LogService.swift b/Sources/DFService/service/LogService.swift index 4f81e7e..be4c79b 100644 --- a/Sources/DFService/service/LogService.swift +++ b/Sources/DFService/service/LogService.swift @@ -48,7 +48,7 @@ public extension DFLogHandle { } -public struct LogService: DFApiService { +public struct LogService: DFServiceKey { public static var defaultValue: DFLogHandle { return MockApiServiceImpl() } diff --git a/Sources/DFService/service/RouteService.swift b/Sources/DFService/service/RouteService.swift index 5ef8c0c..78f9af0 100644 --- a/Sources/DFService/service/RouteService.swift +++ b/Sources/DFService/service/RouteService.swift @@ -1,4 +1,4 @@ -public struct RouteService: DFApiService { +public struct RouteService: DFServiceKey { public static var defaultValue: Router { return Value.shared } diff --git a/Sources/DFService/service/RuntimeService.swift b/Sources/DFService/service/RuntimeService.swift index 3b299da..60e3e18 100644 --- a/Sources/DFService/service/RuntimeService.swift +++ b/Sources/DFService/service/RuntimeService.swift @@ -1,4 +1,4 @@ -public struct RuntimeService: DFApiService { +public struct RuntimeService: DFServiceKey { public static var defaultValue: Value = Value() public struct Value {} diff --git a/Sources/DFService/service/impl/MockApiServiceImpl.swift b/Sources/DFService/service/impl/MockApiServiceImpl.swift index 48baa47..46b8eb4 100644 --- a/Sources/DFService/service/impl/MockApiServiceImpl.swift +++ b/Sources/DFService/service/impl/MockApiServiceImpl.swift @@ -2,9 +2,10 @@ struct MockApiServiceImpl { } + extension MockApiServiceImpl: DFApiCall { func callAsFunction(_ context: ApiCallConext) async throws -> Any { - ServiceValues[LogService.self].debug("api 未实现 \(context.method):\(context.param)") + Application.shared[LogService.self].debug("api 未实现 \(context.method):\(context.param)") return () } } diff --git a/Sources/DFService/service/provider/BootstrapServiceProvider.swift b/Sources/DFService/service/provider/BootstrapServiceProvider.swift index e68ab1e..8cfb039 100644 --- a/Sources/DFService/service/provider/BootstrapServiceProvider.swift +++ b/Sources/DFService/service/provider/BootstrapServiceProvider.swift @@ -1,12 +1,12 @@ import Foundation actor Bootstrap { - private weak var app: DFApplication? + private weak var app: Application? private var current: ServiceProvider.ProviderWhen = .eager private var target: ServiceProvider.ProviderWhen = .eager private var isRunning = false - init(app: DFApplication) { + init(app: Application) { self.app = app } @@ -34,6 +34,9 @@ actor Bootstrap { return } self.isRunning = true + defer { + self.isRunning = false + } for provider in currentProviders where !provider.isBooted { let start_time = CFAbsoluteTimeGetCurrent() var success = false @@ -42,19 +45,23 @@ actor Bootstrap { let cost_time = (end_time - start_time) * 1000 app[LogService.self].debug("\(provider.name) 启动\(success)用时: \(cost_time)毫秒") } - await provider.performAsyncStartup() - success = true - provider.isBooted = true - + do { + try await provider.performAsyncStartup() + provider.isBooted = true + success = true + } catch { + app[LogService.self].warning("\(provider.name) 启动失败\(error)") + return + } } - self.isRunning = false + await bootIfNeed() } } class BootstrapServiceProvider: ServiceProvider { let bootstrap: Bootstrap - required init(_ app: DFApplication) { + required init(_ app: Application) { bootstrap = Bootstrap(app: app) super.init(app) } @@ -77,3 +84,32 @@ class BootstrapServiceProvider: ServiceProvider { return .eager } } + + + +public extension Application { + @discardableResult + func provider(_ serviceType: T.Type = T.self) -> T { + + let loadProvider = loadProviders.first { loaded in + return loaded.name == serviceType.name + } + if let provider = loadProvider as? T { + return provider + } + let provider = serviceType.init(self) + loadProviders.append(provider) + provider.register() + return provider + } + + + func bootstrap(_ when: ServiceProvider.ProviderWhen) { + provider(BootstrapServiceProvider.self).bootstrap(when) + } + /// 重置启动,执行provider Shutdown + func reset(_ when: ServiceProvider.ProviderWhen) { + provider(BootstrapServiceProvider.self).bootstrap(when) + } + +} diff --git a/Sources/DFService/service/router/Router.swift b/Sources/DFService/service/router/Router.swift index b34922d..2f6db89 100644 --- a/Sources/DFService/service/router/Router.swift +++ b/Sources/DFService/service/router/Router.swift @@ -70,14 +70,3 @@ public extension Router { return AnyView(builder(request)) } } - - -extension Router { - -} - -extension EnvironmentValues { - var pageRouter: Router { - return serviceValues[RouteService.self] - } -} diff --git a/Tests/DFServiceTests/DFServiceTests.swift b/Tests/DFServiceTests/DFServiceTests.swift index 5f32c93..d830da9 100644 --- a/Tests/DFServiceTests/DFServiceTests.swift +++ b/Tests/DFServiceTests/DFServiceTests.swift @@ -6,14 +6,14 @@ final class DFServiceTests: XCTestCase { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct // results. - XCTAssertEqual(ServiceValues.version, "0.1.0") - XCTAssertEqual(ServiceValues[RuntimeService.self].isRunningInTests, true) + XCTAssertEqual(Application.version, "0.1.0") + XCTAssertEqual(Application.shared[RuntimeService.self].isRunningInTests, true) } func testApiCalll() async throws { - ServiceValues.shared[.logger] = LogService.self + Application.shared[.logger] = LogService.self try await ServiceName.logger(ApiCallConext(method: "debug", param: "你好呀")) } }