A Swift foundational toolkit for Apple platforms, covering networking, resilient decoding, concurrency safety, and practical development utilities.
pod 'RYKit', :git => 'https://github.com/mithyer/RYKit.git', :tag => '2.1.0'.package(url: "https://github.com/mithyer/RYKit.git", from: "2.1.0")A feature-rich Swift utility library providing common foundational modules for iOS, macOS, and tvOS applications.
- Current Version: 2.1.0
- Supported Platforms: iOS 13.0+, macOS 10.15+, tvOS 13.0+
- Swift Version: 5.0+
RYKit is a Swift foundational toolkit for Apple platforms, focused on providing stable, reusable building blocks for networking, data decoding, concurrency safety, and common utility scenarios.
Core capabilities at a glance:
- HTTP request abstraction: Encapsulates common request flows, response parsing, request strategies, and business error handling to reduce repetitive networking code.
- STOMP real-time messaging: Provides subscription management, reconnect handling, and message distribution for WebSocket-based real-time communication.
- Codable resilience tools: Uses property wrappers such as
@Default,@PreferValue, and@IgnoreValueto make model decoding more fault-tolerant and easier to maintain. - Thread safety primitives: Includes locks and thread-safe property wrappers for protecting shared state in concurrent code.
- Practical utilities and data structures: Offers common extensions, linked lists, queues, timeout task helpers, and version comparison utilities for everyday development.
Additional built-in modules include logging, network reachability monitoring, and associated object helpers. Detailed examples and module-specific usage are provided in the sections below.
Add RYKit to your Package.swift dependencies:
.dependencies([
.package(url: "https://github.com/mithyer/RYKit.git", from: "2.1.0")
])Then choose the product that fits your use case:
.target(
name: "YourTarget",
dependencies: [
.product(name: "RYKit", package: "RYKit")
]
)Available products:
RYKit: Aggregated product that re-exportsRYKitCore,RYKitNetworkHttp, andRYKitNetworkStompRYKitCore: Core utilities and foundational typesRYKitNetworkHttp: HTTP request moduleRYKitNetworkStomp: STOMP messaging module
Import examples:
import RYKit // Recommended: aggregated import
// or
import RYKitCore
import RYKitNetworkHttp
import RYKitNetworkStompAdd to your Podfile:
# Install all modules
pod 'RYKit'
# Or install only needed submodules
pod 'RYKit/Core'
pod 'RYKit/NetworkHttp'
pod 'RYKit/NetworkStomp'Then run:
pod installlet request = HttpRequest(
session: .shared,
queue: .main,
baseURL: "https://api.example.com",
method: .POST,
path: "/users",
params: .dic(["name": "John"]),
contentType: .applicationJson,
requestStrategy: .cancelIfRequesting,
baseHeaders: ["Authorization": "Bearer token"],
businessCodeValidator: nil,
handlers: handlers
)
request.response(User.self) { result in
switch result {
case .success(let user): print("User: \(user)")
case .failure(let error): print("Error: \(error.localizedDescription)")
}
}let manager = StompManager<YourChannel>(userToken: "user123")
let subscription = StompSubInfo(destination: "/topic/messages", identifier: "msg_subscriber", headers: nil)
let holder = manager.subscribe(
dataType: Message.self,
subscription: subscription,
receiveMessageStrategy: .all
) { message, headers, raw in
print("Received message: \(message)")
}// Configure the minimum level emitted by the process-wide logger.
setLogLevel(.warn)
// Filtered messages are not evaluated when below the configured threshold.
log("cache miss", level: .debug)
log_warn("request retrying")
log_err("request failed", errKey: "request-failure")The log entry point supports verbose, debug, info, warn, and error levels. The configured level is a minimum threshold: messages below it are filtered before evaluation. DEBUG builds default to .debug; non-DEBUG builds default to .error.
The default logger buffers accepted messages through LogRecorder and writes them according to its buffer-size and flush policy under Documents/RYKitLogs. The default logger does not expose a public flush hook. In Release builds, the default .error threshold means eligible errors may create or append to log files when the recorder flushes. Use setActiveLogger to provide a custom LoggerProtocol implementation when persistence should be handled elsewhere; custom adapters are also filtered by the configured threshold.
Choose the public product that best fits your integration needs:
RYKit: Umbrella product that re-exportsRYKitCore,RYKitNetworkHttp, andRYKitNetworkStompRYKitCore: Foundational utilities for decoding resilience, concurrency safety, lightweight storage, async helpers, logging, and common reusable building blocks.Associatable: Attach associated objects to existing instances.Async: Provide lightweight async execution helpers.Codable: Improve resilient decoding and value conversion.Collections: Offer linked lists, queues, weak maps, and weak sets.Combine: Add practical Combine storage, callback throttling, and debouncing helpers.Extensions: Add convenience helpers for common Foundation and Swift types.KV: Simplify lightweight key-value storage and buffering.Lock: Protect shared state in concurrent code.Log: Persist and inspect runtime logs.Reachability: Monitor shared network reachability state.TimeoutTask: Manage delayed and timeout-based task execution.
RYKitNetworkHttp: HTTP request abstraction and response handlingRYKitNetworkStomp: STOMP-based real-time messaging over WebSocket
In CocoaPods, the public subspecs are
Core,NetworkHttp, andNetworkStomp. In Swift Package Manager, the corresponding public products areRYKitCore,RYKitNetworkHttp, andRYKitNetworkStomp.
final class Session: NSObject, Associatable {}
let session = Session()
session.setAssociated("traceId", value: "req-001")
let traceId: String? = session.associated("traceId")let executor = AsyncSerialExecutor()
try await executor.run {
// run serial side effects
}struct Profile: Codable {
@Default.Empty<String> var name: String
@Default.Zero<Int> var age: Int
}struct Payload: Codable {
@PreferValue var score: Int?
@FromStringValue var amount: Double?
}let queue = Queue<Int>()
queue.enqueue(1)
queue.enqueue(2)
let first = queue.dequeue()let map = WeakMap<String, NSObject>()
let retainedObject = NSObject() // keep a strong owner outside the weak map
map.insert(key: "item", retainedObject)
let cached = map["item"]var cancellables = Set<AnyCancellable>()
Just("value")
.sink { value in print(value) }
.store(in: &cancellables)let debounce = DebounceCallback(interval: .milliseconds(300))
debounce.send {
// execute once after quiet period
}let defaults = UserDefaults.WithKeyExtended { "app.\($0)" }
defaults.set("dark", forKey: "theme")
let theme = defaults.string(forKey: "theme")let kv = TinyKV(dbName: "app", tableName: "cache")
try await kv.set(value: "Alice", for: .string("user.name"))
let name: String = try await kv.getValue(for: .string("user.name"))let buffered = TinyBufferedKV(dbName: "app", tableName: "buffer")
try await buffered.set(value: 42, for: .string("counter"))
try await buffered.flush()For value encryption, create an encryptor and inject it through Config. Passing nil keeps values unencrypted.
let encryptor = try TinyKVAESGCMEncryptor.loadOrCreateFromKeychain(
service: "com.example.app.tinykv"
)
let secureKV = TinyKV(
dbName: "app",
tableName: "secure-cache",
config: TinyKV.Config(valueEncryptor: encryptor)
)
try await secureKV.set(value: "Alice", for: .string("user.name"))
let bufferedConfig = TinyBufferedKV.Config(valueEncryptor: encryptor)
let secureBufferedKV = TinyBufferedKV(
dbName: "app",
tableName: "secure-buffer",
config: bufferedConfig
)AES-GCM encrypts persisted values only. SQLite keys remain available for TinyKVQueryKey queries, while the encryptor instance and its key must be retained by the application.
class Store {
@ThreadSafe var count: Int = 0
}
let store = Store()
store.$count.lock { $0 += 1 }let lock = ReadWriteLock()
var value = 0
let current = lock.read { value }
lock.write { value = current + 1 }let token = GlobalReachability.shared.listen { status in
print("network:", status)
}let task = OnceTimeoutTask<String, Error>(
flag: "load-profile",
executionTimeoutInterval: .seconds(3),
stopTimeoutInterval: .seconds(1),
execute: { complete in
complete(.success("ok"))
},
stopWhenExecuting: { stopped in
stopped()
}
)stopWhenExecuting is only consulted when the task is already executing.
let queue = OnceTimeoutTaskQueue<String, Error>(
executeQueue: .main,
defaultPreemptionStrategy: .waitCurrentCompletion
)
let cancellable = queue.taskDidFinish.sink { event in
print(event.flag, event.doneType)
}
queue.addTask(task, priority: 10)let persistentTask = OnceTimeoutTask<String, Error>(
flag: "persistent",
executionTimeoutInterval: nil,
stopTimeoutInterval: nil,
execute: { _ in
// Complete later, or keep running.
}
)
queue.addTask(persistentTask, priority: 1)MIT License
Ray - GitHub
RYKit 是一个面向 Apple 平台的 Swift 基础能力工具库,重点覆盖网络通信、数据解码容错、并发安全以及常见开发工具场景,帮助你减少重复造轮子。
- 当前版本: 2.1.0
- 支持平台: iOS 13.0+, macOS 10.15+, tvOS 13.0+
- Swift 版本: 5.0+
核心能力包括:
- HTTP 请求封装:统一常见请求流程、响应解析、请求策略与业务错误处理,减少重复网络层代码。
- STOMP 实时通信:提供订阅管理、重连处理和消息分发能力,适合基于 WebSocket 的实时消息场景。
- Codable 容错工具:通过
@Default、@PreferValue、@IgnoreValue等属性包装器,让模型解码更稳健、更易维护。 - 线程安全原语:内置锁与线程安全属性包装器,便于在并发代码中保护共享状态。
- 常用扩展与数据结构:提供常见扩展、链表、队列、超时任务工具和版本比较等高频基础能力。
此外,库中还内置了日志记录、网络可达性监听和关联对象等实用模块。更详细的示例和模块说明请继续查看下方章节。
在你的 Podfile 中添加:
# 安装所有模块
pod 'RYKit'
# 或者只安装需要的子模块
pod 'RYKit/Core'
pod 'RYKit/NetworkHttp'
pod 'RYKit/NetworkStomp'然后运行:
pod installlet request = HttpRequest(
session: .shared,
queue: .main,
baseURL: "https://api.example.com",
method: .POST,
path: "/users",
params: .dic(["name": "John"]),
contentType: .applicationJson,
requestStrategy: .cancelIfRequesting,
baseHeaders: ["Authorization": "Bearer token"],
businessCodeValidator: nil,
handlers: handlers
)
request.response(User.self) { result in
switch result {
case .success(let user): print("User: \(user)")
case .failure(let error): print("Error: \(error.localizedDescription)")
}
}let manager = StompManager<YourChannel>(userToken: "user123")
let subscription = StompSubInfo(destination: "/topic/messages", identifier: "msg_subscriber", headers: nil)
let holder = manager.subscribe(
dataType: Message.self,
subscription: subscription,
receiveMessageStrategy: .all
) { message, headers, raw in
print("收到消息: \(message)")
}setLogLevel(.warn)
log("cache miss", level: .debug)
log_warn("request retrying")
log_err("request failed", errKey: "request-failure")log 支持 verbose、debug、info、warn 和 error 五个等级。配置的等级是最低阈值,低于阈值的消息会在求值前被过滤。DEBUG 构建默认使用 .debug,非 DEBUG 构建默认使用 .error。
默认 logger 会通过 LogRecorder 缓冲已接受的消息,并按照缓冲大小和 flush 策略写入 Documents/RYKitLogs。默认 logger 不提供公共 flush 接口。Release 构建默认只接受 error,符合条件的错误会在 recorder flush 时创建或追加日志文件。可以通过 setActiveLogger 提供自定义 LoggerProtocol 实现;自定义 adapter 同样会受到全局等级阈值过滤。
根据接入方式选择最适合你的对外产品:
RYKit:聚合产品,统一导出RYKitCore、RYKitNetworkHttp和RYKitNetworkStompRYKitCore:基础工具能力,重点覆盖解码容错、并发安全、轻量存储、异步辅助、日志以及常用可复用组件。Associatable:为现有实例附加关联对象能力。Async:提供轻量级异步执行辅助工具。Codable:提升解码容错与数值转换能力。Collections:提供链表、队列、弱引用映射和弱引用集合等数据结构。Combine:补充实用的 Combine 存储、回调节流与防抖辅助能力。Extensions:为常见 Foundation 和 Swift 类型补充便捷扩展能力。KV:简化轻量级键值存储及缓冲写入。Lock:用于在并发代码中保护共享状态。Log:用于持久化和查看运行日志。Reachability:用于监控共享网络可达性状态。TimeoutTask:用于管理延时任务和超时任务。
RYKitNetworkHttp:HTTP 请求封装与响应处理RYKitNetworkStomp:基于 WebSocket 的 STOMP 实时消息能力
在 CocoaPods 中,对外暴露的 subspec 为
Core、NetworkHttp和NetworkStomp;在 Swift Package Manager 中,对应的公开产品为RYKitCore、RYKitNetworkHttp和RYKitNetworkStomp。
final class Session: NSObject, Associatable {}
let session = Session()
session.setAssociated("traceId", value: "req-001")
let traceId: String? = session.associated("traceId")let executor = AsyncSerialExecutor()
try await executor.run {
// 串行执行副作用任务
}struct Profile: Codable {
@Default.Empty<String> var name: String
@Default.Zero<Int> var age: Int
}struct Payload: Codable {
@PreferValue var score: Int?
@FromStringValue var amount: Double?
}let queue = Queue<Int>()
queue.enqueue(1)
queue.enqueue(2)
let first = queue.dequeue()let map = WeakMap<String, NSObject>()
let object = NSObject()
map.insert(key: "item", object)
let cached = map["item"]var cancellables = Set<AnyCancellable>()
Just("value")
.sink { value in print(value) }
.store(in: &cancellables)let debounce = DebounceCallback(interval: .milliseconds(300))
debounce.send {
// 静默窗口后触发一次
}let prefs = UserDefaults.WithKeyExtended { "app.\($0)" }
prefs.set("dark", forKey: "theme")
let theme = prefs.string(forKey: "theme")let kv = TinyKV(dbName: "app", tableName: "cache")
try await kv.set(value: "Alice", for: .string("user.name"))
let name: String = try await kv.getValue(for: .string("user.name"))let buffered = TinyBufferedKV(dbName: "app", tableName: "buffer")
try await buffered.set(value: 42, for: .string("counter"))
try await buffered.flush()如需加密 value,可以创建加密器并通过 Config 注入;传入 nil 表示不加密。
let encryptor = try TinyKVAESGCMEncryptor.loadOrCreateFromKeychain(
service: "com.example.app.tinykv"
)
let secureKV = TinyKV(
dbName: "app",
tableName: "secure-cache",
config: TinyKV.Config(valueEncryptor: encryptor)
)
try await secureKV.set(value: "Alice", for: .string("user.name"))
let bufferedConfig = TinyBufferedKV.Config(valueEncryptor: encryptor)
let secureBufferedKV = TinyBufferedKV(
dbName: "app",
tableName: "secure-buffer",
config: bufferedConfig
)AES-GCM 只加密持久化的 value,SQLite 中的 key 仍然可用于 TinyKVQueryKey 查询。应用需要自行持有加密器实例及其密钥。
class Store {
@ThreadSafe var count: Int = 0
}
let store = Store()
store.$count.lock { $0 += 1 }var value = 0
let lock = ReadWriteLock()
lock.write { value += 1 }let token = GlobalReachability.shared.listen { status in
print("network:", status)
}let task = OnceTimeoutTask<String, Error>(
flag: "load-profile",
executionTimeoutInterval: .seconds(3),
stopTimeoutInterval: .seconds(1),
execute: { complete in
complete(.success("ok"))
},
stopWhenExecuting: { stopped in
stopped()
}
)let queue = OnceTimeoutTaskQueue<String, Error>(
executeQueue: .main,
defaultPreemptionStrategy: .waitCurrentCompletion
)
let cancellable = queue.taskDidFinish.sink { event in
print(event.flag, event.doneType)
}
queue.addTask(task, priority: 10)let persistentTask = OnceTimeoutTask<String, Error>(
flag: "persistent",
executionTimeoutInterval: nil,
stopTimeoutInterval: nil,
execute: { _ in
// Complete later, or keep running.
}
)
queue.addTask(persistentTask, priority: 1)MIT License
Ray - GitHub