Skip to content
This repository was archived by the owner on Sep 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

开发App常用工具和默认服务.


## 任务列表
- JSONValue 处理通用序列化和反序列化
## 常用组件 ServiceValues管理常用服务
- 实现ServiceStateType 纯Swift Redux


### 实现ServiceStateType 使用
### 实现DFStateType 使用

```swift
struct CounterState: ServiceStateType {
struct CounterState: DFStateType {
enum Action {
case increment
case decrement
Expand Down
213 changes: 213 additions & 0 deletions Sources/DFService/JSONValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import Foundation

public enum JSONValue: Codable, Hashable {
public static var jsonEncode = JSONEncoder()
public static var jsonDecoder = JSONDecoder()
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
case list([JSONValue])
case map([String: JSONValue])
case data(Data)
case null

// MARK: - Init from Any
public init?(obj: Any) {
switch obj {
case let value as String:
self = .string(value)
case let value as Bool:
self = .bool(value)
case let value as Int:
self = .int(value)
case let value as Double:
self = .double(value)

case let value as Data:
self = .data(value)
case let value as [Any]:
self = .list(value.compactMap { JSONValue(obj: $0) })
case let value as [String: Any]:
var object = [String: JSONValue]()
// map 根据key 排序
for (key, val) in value {
guard let jv = JSONValue(obj: val) else { return nil }
object[key] = jv
}
self = .map(object)
case let value as Encodable:
do {
let data = try JSONValue.jsonEncode.encode(value)
if let jsonObject = try? JSONDecoder().decode(JSONValue.self, from: data) {
self = jsonObject
} else {
self = .data(data)
}

} catch {
return nil
}
case _ as NSNull:
self = .null
default:
return nil
}
}

// MARK: - To Any
public func toAny() -> Any {
switch self {
case .string(let value): return value
case .int(let value): return value
case .double(let value): return value
case .bool(let value): return value
case .list(let array): return array.map { $0.toAny() }
case .map(let dict): return dict.mapValues { $0.toAny() }
case .data(let data): return data.base64EncodedString()
case .null: return NSNull()
}
}

// MARK: - Encode
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value): try container.encode(value)
case .bool(let value): try container.encode(value)
case .int(let value): try container.encode(value)
case .double(let value): try container.encode(value)
case .list(let value): try container.encode(value)
case .map(let value): try container.encode(value)
case .data(let value): try container.encode(value.base64EncodedString())
case .null: try container.encodeNil()
}
}

// MARK: - Decode
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Int.self) {
self = .int(value)
} else if let value = try? container.decode(Double.self) {
self = .double(value)
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(String.self) {
if let data = Data(base64Encoded: value), !data.isEmpty {
self = .data(data)
} else {
self = .string(value)
}
} else if let value = try? container.decode([JSONValue].self) {
self = .list(value)
} else if let value = try? container.decode([String: JSONValue].self) {
self = .map(value)
} else {
throw DecodingError.dataCorruptedError(
in: container, debugDescription: "Unknown JSONValue")
}
}

// MARK: - Path Access
public func value(at path: [String]) -> JSONValue? {
guard !path.isEmpty else { return self }
if case .map(let dict) = self {
guard let first = path.first, let next = dict[first] else { return nil }
return next.value(at: Array(path.dropFirst()))
} else if case .list(let array) = self {
guard let first = path.first, let index = Int(first), array.indices.contains(index)
else { return nil }
return array[index].value(at: Array(path.dropFirst()))
}
return nil
}

public func value(at path: String) -> JSONValue? {
return value(at: path.components(separatedBy: "."))
}

// MARK: - Get<T>
public func optional<T>(_ type: T.Type = T.self) -> T? {
return try? to()
}

public func to<T>(type: T.Type = T.self) throws -> T {
if let value = self as? T {
return value
}
let value = toAny()
if let obj: T = value as? T {
return obj
}
throw ServiceError.castError(from: value, to: T.Type.self)
}

// MARK: - Decode to Model
public func decode<T: Decodable>(_ type: T.Type) throws -> T {
if let value: T = optional() {
return value
} else if case .data(let data) = self {
return try Self.jsonDecoder.decode(T.self, from: data)
} else if case .string(let value) = self {
guard let data = value.data(using: .utf8) else {
throw NSError(
domain: "JSONValue", code: 1,
userInfo: [NSLocalizedDescriptionKey: "Invalid string encoding"])
}
return try Self.jsonDecoder.decode(T.self, from: data)
}
let obj = self.toAny()
let data = try JSONSerialization.data(withJSONObject: obj, options: [])
return try Self.jsonDecoder.decode(T.self, from: data)
}

// MARK: - Init from Model
public init<T: Encodable>(from model: T) throws {
let data = try Self.jsonEncode.encode(model)
let any = try JSONSerialization.jsonObject(with: data)
guard let json = JSONValue(obj: any) else {
throw NSError(
domain: "JSONValue", code: 1,
userInfo: [NSLocalizedDescriptionKey: "Cannot convert model to JSONValue"])
}
self = json
}
}

extension JSONValue: Equatable {}

extension JSONValue: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Any)...) {
var dict = [String: JSONValue]()
for (key, value) in elements {
dict[key] = JSONValue(obj: value)
}
self = .map(dict)
}
}
extension JSONValue: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Any...) {
self = .list(elements.compactMap { JSONValue(obj: $0) })
}
}

extension JSONValue: CustomStringConvertible {
public var description: String {
switch self {
case .string(let value): return "\"\(value)\""
case .int(let value): return "\(value)"
case .double(let value): return "\(value)"
case .bool(let value): return "\(value)"
case .list(let array):
return "[" + array.map { $0.description }.joined(separator: ", ") + "]"
case .map(let dict):
let items = dict.map { "\"\($0)\": \($1.description)" }.sorted().joined(separator: ", ")
return "{\(items)}"
case .data(let data): return "\"\(data.base64EncodedString())\""
case .null: return "null"
}
}
}
26 changes: 8 additions & 18 deletions Sources/DFService/ServiceError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
import Foundation

public enum ServiceError: Error {
public static var invalidParameters = 1000
/// API 错误,包含错误代码和消息
/// - Parameters:
/// - code: 错误代码
/// - message: 错误消息
/// - detail: 可选的调试消息,默认为 nil
case api(code: Int, message: String, detail: String? = nil)

/// 转换类型失败
case castError(from: Any, to: Any.Type)
/// 未实现
case notImplemented(String = #function)
/// 自定义错误
Expand All @@ -29,15 +32,13 @@ public enum ServiceError: Error {
}

/// 扩展 ServiceError 以提供错误描述
extension ServiceError: CustomStringConvertible, CustomDebugStringConvertible {
extension ServiceError: CustomStringConvertible {
public var errorCode: Int {
switch self {
case .api(let code, _, _):
return code
case .notImplemented:
return 0 // 未实现的错误没有特定的错误代码
case .custom(let error):
return (error as NSError).code // 使用 NSError 的代码
default:
return ServiceError.invalidParameters
}
}
public var description: String {
Expand All @@ -48,19 +49,8 @@ extension ServiceError: CustomStringConvertible, CustomDebugStringConvertible {
return "未实现: \(location)"
case .custom(let error):
return error.localizedDescription
}
}

public var debugDescription: String {

switch self {
case .api(let code, let message, let detail):
return
"ServiceError api - Code: \(code), Message: \(message), Detail: \(detail ?? "No detail")"
case .notImplemented(let location):
return "ServiceError - Location: \(location)"
case .custom(let error):
return "ServiceError - \(error.localizedDescription)"
case .castError:
return "参数错误"
}
}
}
10 changes: 5 additions & 5 deletions Sources/DFService/ServiceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ public final class ServiceManager {
public static var shared = ServiceManager()
public init() {}

private var modules: [String: ServiceModuleType] = [:]
private var modules: [String: DFModuleType] = [:]

@MainActor
public func runAll(phase: ServicePhase) {
Expand All @@ -13,20 +13,20 @@ public final class ServiceManager {
}

extension ServiceManager {
public func register<Module: ServiceModuleType>(_ module: Module) {
public func register<Module: DFModuleType>(_ module: Module) {
modules[Module.name] = module
}
public func register<Module: ServiceModuleType>(_ module: Module.Type = Module.self) {
public func register<Module: DFModuleType>(_ module: Module.Type = Module.self) {
let value = module.init(self)
register(value)
}
// 通过模块名称获取模块实例
public func getModule(named name: String) -> ServiceModuleType? {
public func getModule(named name: String) -> DFModuleType? {
return modules[name]
}

// 通过模块类型获取模块实例
public func getModule<T: ServiceModuleType>(byType type: T.Type) -> T? {
public func getModule<T: DFModuleType>(byType type: T.Type) -> T? {
return modules.values.first(where: { $0 is T }) as? T
}
public func sendEvent(_ event: ServiceEvent, to moduleName: String? = nil) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/DFService/ServiceStore.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation


public final class ServiceStore<State: ServiceStateType> {
public final class ServiceStore<State: DFStateType> {

public private(set) var state: State {
didSet {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public struct ServicePhase: Comparable, RawRepresentable {
}
}

public protocol ServiceModuleType: AnyObject {
public protocol DFModuleType: AnyObject {
init(_ manager: ServiceManager)
static var name: String { get }
// ren阶段
Expand All @@ -48,7 +48,7 @@ public protocol ServiceModuleType: AnyObject {
func handle(event: ServiceEvent)
}

extension ServiceModuleType {
extension DFModuleType {
public static var name: String {
return String(describing: self)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// A protocol that defines the required interface for representing the state of a service.
/// Types conforming to `ServiceStateType` should encapsulate all necessary information
/// Types conforming to `DFStateType` should encapsulate all necessary information
/// about the current state of a service, enabling state management and transitions.
public protocol ServiceStateType {
public protocol DFStateType {
associatedtype Action

typealias Reducer = (_ state: Self, _ action: Action) -> Self?
Expand Down
Loading