356 lines
11 KiB
Swift
356 lines
11 KiB
Swift
|
|
//
|
|||
|
|
// Common+Ext.swift
|
|||
|
|
// Crush
|
|||
|
|
//
|
|||
|
|
// Created by Leon on 2025/7/11.
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
import Foundation
|
|||
|
|
|
|||
|
|
/// 打印
|
|||
|
|
//func dlog<T>(_ message: T, file: StaticString = #file, method: String = #function, line: Int = #line) {
|
|||
|
|
// #if DEBUG
|
|||
|
|
// let fileName = (file.description as NSString).lastPathComponent
|
|||
|
|
// print("🔹\(fileName) \(method)[\(line)]:\n\(message)\n")
|
|||
|
|
// #endif
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
func dlog<T>(_ message: T, file: StaticString = #file, method: String = #function, line: Int = #line) {
|
|||
|
|
#if DEBUG
|
|||
|
|
let fileName = (file.description as NSString).lastPathComponent
|
|||
|
|
print("🔹\(fileName) \(method)[\(line)]:\n\(message)\n")
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct EmptyModel: Codable {}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// MARK: - DecodableValue
|
|||
|
|
enum DecodableValue: Decodable, Encodable {
|
|||
|
|
case string(String)
|
|||
|
|
case int(Int)
|
|||
|
|
case double(Double)
|
|||
|
|
case bool(Bool)
|
|||
|
|
case array([DecodableValue])
|
|||
|
|
case dictionary([String: DecodableValue])
|
|||
|
|
case null
|
|||
|
|
|
|||
|
|
init(from decoder: Decoder) throws {
|
|||
|
|
let container = try decoder.singleValueContainer()
|
|||
|
|
|
|||
|
|
if container.decodeNil() {
|
|||
|
|
self = .null
|
|||
|
|
} else if let b = try? container.decode(Bool.self) {
|
|||
|
|
self = .bool(b)
|
|||
|
|
} else if let i = try? container.decode(Int.self) {
|
|||
|
|
self = .int(i)
|
|||
|
|
} else if let d = try? container.decode(Double.self) {
|
|||
|
|
self = .double(d)
|
|||
|
|
} else if let s = try? container.decode(String.self) {
|
|||
|
|
self = .string(s)
|
|||
|
|
} else if let a = try? container.decode([DecodableValue].self) {
|
|||
|
|
self = .array(a)
|
|||
|
|
} else if let dict = try? container.decode([String: DecodableValue].self) {
|
|||
|
|
self = .dictionary(dict)
|
|||
|
|
} else {
|
|||
|
|
throw DecodingError.dataCorruptedError(
|
|||
|
|
in: container,
|
|||
|
|
debugDescription: "Unsupported value"
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// MARK: - Encodable
|
|||
|
|
func encode(to encoder: Encoder) throws {
|
|||
|
|
var container = encoder.singleValueContainer()
|
|||
|
|
|
|||
|
|
switch self {
|
|||
|
|
case .null:
|
|||
|
|
try container.encodeNil()
|
|||
|
|
case .bool(let b):
|
|||
|
|
try container.encode(b)
|
|||
|
|
case .int(let i):
|
|||
|
|
try container.encode(i)
|
|||
|
|
case .double(let d):
|
|||
|
|
try container.encode(d)
|
|||
|
|
case .string(let s):
|
|||
|
|
try container.encode(s)
|
|||
|
|
case .array(let a):
|
|||
|
|
try container.encode(a)
|
|||
|
|
case .dictionary(let d):
|
|||
|
|
try container.encode(d)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension Encodable {
|
|||
|
|
func toNonNilDictionary() -> [String: Any] {
|
|||
|
|
return Self._toDictionary(value: self)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static func _toDictionary(value: Any) -> [String: Any] {
|
|||
|
|
var result: [String: Any] = [:]
|
|||
|
|
let mirror = Mirror(reflecting: value)
|
|||
|
|
|
|||
|
|
for child in mirror.children {
|
|||
|
|
guard let key = child.label else { continue }
|
|||
|
|
|
|||
|
|
let rawValue = child.value
|
|||
|
|
let valueMirror = Mirror(reflecting: rawValue)
|
|||
|
|
|
|||
|
|
// 处理 Optional 类型
|
|||
|
|
if valueMirror.displayStyle == .optional {
|
|||
|
|
if let some = valueMirror.children.first?.value {
|
|||
|
|
let processedValue = processValue(some)
|
|||
|
|
if !isNil(processedValue) {
|
|||
|
|
result[key] = processedValue
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理值
|
|||
|
|
let processedValue = processValue(rawValue)
|
|||
|
|
if !isNil(processedValue) {
|
|||
|
|
result[key] = processedValue
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static func processValue(_ value: Any) -> Any {
|
|||
|
|
if let rawRepresentable = value as? (any RawRepresentable) {
|
|||
|
|
return rawRepresentable.rawValue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理特定类型(如 Sex 枚举)
|
|||
|
|
if let sex = value as? Sex {
|
|||
|
|
return sex.rawValue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理 Encodable 类型(嵌套结构体或类)
|
|||
|
|
if let encodableValue = value as? Encodable {
|
|||
|
|
let mirror = Mirror(reflecting: encodableValue)
|
|||
|
|
|
|||
|
|
// 处理数组或集合
|
|||
|
|
if mirror.displayStyle == .collection {
|
|||
|
|
return mirror.children.compactMap { child in
|
|||
|
|
let processed = processValue(child.value)
|
|||
|
|
return isNil(processed) ? nil : processed
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理嵌套结构体或类
|
|||
|
|
if mirror.displayStyle == .class || mirror.displayStyle == .struct || mirror.displayStyle == .set {
|
|||
|
|
return _toDictionary(value: encodableValue)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 辅助函数:检查值是否为 nil
|
|||
|
|
private static func isNil(_ value: Any) -> Bool {
|
|||
|
|
let mirror = Mirror(reflecting: value)
|
|||
|
|
return mirror.displayStyle == .optional && mirror.children.isEmpty
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// func toNonNilDictionary() -> [String: Any] {
|
|||
|
|
// return Self._toDictionary(value: self)
|
|||
|
|
// }
|
|||
|
|
// private static func _toDictionary(value: Any) -> [String: Any] {
|
|||
|
|
// var result: [String: Any] = [:]
|
|||
|
|
// let mirror = Mirror(reflecting: value)
|
|||
|
|
//
|
|||
|
|
// for child in mirror.children {
|
|||
|
|
// guard let key = child.label else { continue }
|
|||
|
|
//
|
|||
|
|
// let rawValue = child.value
|
|||
|
|
// let valueMirror = Mirror(reflecting: rawValue)
|
|||
|
|
//
|
|||
|
|
// // Optional 类型处理
|
|||
|
|
// if valueMirror.displayStyle == .optional {
|
|||
|
|
// if let some = valueMirror.children.first?.value {
|
|||
|
|
// result[key] = processValue(some)
|
|||
|
|
// }
|
|||
|
|
// } else {
|
|||
|
|
// result[key] = processValue(rawValue)
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
// return result
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
// private static func processValue(_ value: Any) -> Any {
|
|||
|
|
// let mirror = Mirror(reflecting: value)
|
|||
|
|
//
|
|||
|
|
// // 递归处理 Optional
|
|||
|
|
// if mirror.displayStyle == .optional {
|
|||
|
|
// if let some = mirror.children.first?.value {
|
|||
|
|
// return processValue(some) // 递归处理解包后的值
|
|||
|
|
// } else {
|
|||
|
|
// return ""// NSNull() // 或者直接 return "" 视你的需求
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
// if let sex = value as? Sex {
|
|||
|
|
// return sex.rawValue
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
// if let encodableValue = value as? Encodable {
|
|||
|
|
// let mirror = Mirror(reflecting: encodableValue)
|
|||
|
|
//
|
|||
|
|
// if mirror.displayStyle == .class || mirror.displayStyle == .struct || mirror.displayStyle == .set {
|
|||
|
|
// return _toDictionary(value: encodableValue)
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
// return value
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// MARK: - part 2
|
|||
|
|
|
|||
|
|
/// 将结构体中指定 key 的字段(如果非 nil)导出为字典
|
|||
|
|
func toPartialDictionary(keys includedKeys: Set<String> = []) -> [String: Any] {
|
|||
|
|
return Self._toDictionary(value: self, onlyIncludeKeys: includedKeys)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static func _toDictionary(value: Any, onlyIncludeKeys: Set<String>? = nil) -> [String: Any] {
|
|||
|
|
var result: [String: Any] = [:]
|
|||
|
|
let mirror = Mirror(reflecting: value)
|
|||
|
|
|
|||
|
|
for child in mirror.children {
|
|||
|
|
guard let key = child.label else { continue }
|
|||
|
|
|
|||
|
|
if let allowedKeys = onlyIncludeKeys, !allowedKeys.contains(key) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let rawValue = child.value
|
|||
|
|
let valueMirror = Mirror(reflecting: rawValue)
|
|||
|
|
|
|||
|
|
if valueMirror.displayStyle == .optional {
|
|||
|
|
if let some = valueMirror.children.first?.value {
|
|||
|
|
result[key] = processValue(some, onlyIncludeKeys: nil)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
result[key] = processValue(rawValue, onlyIncludeKeys: nil)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 支持将Sex输出为int
|
|||
|
|
private static func processValue(_ value: Any, onlyIncludeKeys: Set<String>?) -> Any {
|
|||
|
|
let mirror = Mirror(reflecting: value)
|
|||
|
|
|
|||
|
|
if mirror.displayStyle == .struct {
|
|||
|
|
return _toDictionary(value: value, onlyIncludeKeys: onlyIncludeKeys)
|
|||
|
|
} else if let rawRepresentable = value as? any RawRepresentable {
|
|||
|
|
return rawRepresentable.rawValue
|
|||
|
|
} else {
|
|||
|
|
return value
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
extension Array where Element: Equatable {
|
|||
|
|
mutating func removeObj(_ object: Element) {
|
|||
|
|
if let index = firstIndex(of: object) {
|
|||
|
|
remove(at: index)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - 通用扩展
|
|||
|
|
|
|||
|
|
extension Optional {
|
|||
|
|
/// 是否为 nil
|
|||
|
|
var isNil: Bool { self == nil }
|
|||
|
|
|
|||
|
|
/// 是否有值
|
|||
|
|
var isSome: Bool { self != nil }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension Optional where Wrapped == Int {
|
|||
|
|
/// 是否大于 0
|
|||
|
|
var isPositive: Bool { (self ?? 0) > 0 }
|
|||
|
|
|
|||
|
|
/// 是否为负数
|
|||
|
|
var isNegative: Bool { (self ?? 0) < 0 }
|
|||
|
|
|
|||
|
|
/// 是否为 nil 或 0
|
|||
|
|
var isNilOrZero: Bool { self == nil || self == 0 }
|
|||
|
|
|
|||
|
|
/// 转字符串(nil → "")
|
|||
|
|
var stringValue: String { self.map { "\($0)" } ?? "" }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension Optional where Wrapped == String {
|
|||
|
|
/// 是否为空串或 nil
|
|||
|
|
var isNilOrEmpty: Bool { self?.isEmpty ?? true }
|
|||
|
|
|
|||
|
|
/// 去除空格后的安全字符串
|
|||
|
|
var trimmed: String { self?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" }
|
|||
|
|
|
|||
|
|
/// 转 Int(失败或 nil 返回 nil)
|
|||
|
|
var intValue: Int? { self.flatMap { Int($0) } }
|
|||
|
|
|
|||
|
|
/// 转 Double(失败或 nil 返回 nil)
|
|||
|
|
var doubleValue: Double? { self.flatMap { Double($0) } }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension Optional where Wrapped == Bool {
|
|||
|
|
/// 默认 false
|
|||
|
|
var boolValue: Bool { self ?? false }
|
|||
|
|
|
|||
|
|
/// 默认 true
|
|||
|
|
var trueOrNil: Bool { self ?? true }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension Double {
|
|||
|
|
func formatted(decimal: Int = 2, asPercent: Bool = false, usesGroupingSeparator: Bool = true) -> String {
|
|||
|
|
// let formatter = NumberFormatter()
|
|||
|
|
// formatter.minimumFractionDigits = decimal
|
|||
|
|
// formatter.maximumFractionDigits = decimal
|
|||
|
|
// formatter.numberStyle = .decimal
|
|||
|
|
// return formatter.string(from: NSNumber(value: self)) ?? "\(self)"
|
|||
|
|
|
|||
|
|
let value = asPercent ? self * 100 : self
|
|||
|
|
|
|||
|
|
let formatter = NumberFormatter()
|
|||
|
|
formatter.minimumFractionDigits = decimal
|
|||
|
|
formatter.maximumFractionDigits = decimal
|
|||
|
|
formatter.usesGroupingSeparator = usesGroupingSeparator
|
|||
|
|
formatter.numberStyle = .decimal
|
|||
|
|
|
|||
|
|
let result = formatter.string(from: NSNumber(value: value)) ?? "\(value)"
|
|||
|
|
return asPercent ? result + "%" : result
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension CGFloat {
|
|||
|
|
func formatted(decimal: Int = 2, asPercent: Bool = false, usesGroupingSeparator: Bool = true) -> String {
|
|||
|
|
let value = asPercent ? self * 100 : self
|
|||
|
|
|
|||
|
|
let formatter = NumberFormatter()
|
|||
|
|
formatter.minimumFractionDigits = decimal
|
|||
|
|
formatter.maximumFractionDigits = decimal
|
|||
|
|
formatter.usesGroupingSeparator = usesGroupingSeparator
|
|||
|
|
formatter.numberStyle = .decimal
|
|||
|
|
|
|||
|
|
let result = formatter.string(from: NSNumber(value: value)) ?? "\(value)"
|
|||
|
|
return asPercent ? result + "%" : result
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extension Dictionary {
|
|||
|
|
func merged(with other: Dictionary, preferNew: Bool = true) -> Dictionary {
|
|||
|
|
return self.merging(other) { old, new in preferNew ? new : old }
|
|||
|
|
}
|
|||
|
|
}
|