Skip to content

Commit

Permalink
Merge pull request #4 from crontab/v1.5
Browse files Browse the repository at this point in the history
Added the Zip module; internal clean up and optimization; folder reorg
  • Loading branch information
crontab authored Jul 6, 2024
2 parents ced2440 + 6fd3d66 commit 78ba915
Show file tree
Hide file tree
Showing 27 changed files with 200 additions and 84 deletions.
16 changes: 0 additions & 16 deletions AsyncMux.xcworkspace/contents.xcworkspacedata

This file was deleted.

4 changes: 4 additions & 0 deletions AsyncMux/AsyncMux.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
3620DEE42C392F2000584FFC /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3620DEE32C392F2000584FFC /* Zip.swift */; };
362CC58E29746BE900059933 /* AsyncMux.h in Headers */ = {isa = PBXBuildFile; fileRef = 362CC58D29746BE900059933 /* AsyncMux.h */; settings = {ATTRIBUTES = (Public, ); }; };
3635172D29746D2700F9B045 /* AsyncError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3635172529746D2600F9B045 /* AsyncError.swift */; };
3635172F29746D2700F9B045 /* AsyncUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3635172729746D2600F9B045 /* AsyncUtils.swift */; };
Expand All @@ -18,6 +19,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
3620DEE32C392F2000584FFC /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
362CC58A29746BE900059933 /* AsyncMux.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncMux.framework; sourceTree = BUILT_PRODUCTS_DIR; };
362CC58D29746BE900059933 /* AsyncMux.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncMux.h; sourceTree = "<group>"; };
3635172529746D2600F9B045 /* AsyncError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -67,6 +69,7 @@
3635172929746D2600F9B045 /* MuxCacher.swift */,
3635172529746D2600F9B045 /* AsyncError.swift */,
3635172729746D2600F9B045 /* AsyncUtils.swift */,
3620DEE32C392F2000584FFC /* Zip.swift */,
362CC58D29746BE900059933 /* AsyncMux.h */,
);
path = Sources;
Expand Down Expand Up @@ -159,6 +162,7 @@
36CD20042C340DFA000C31FF /* MultiplexerMap.swift in Sources */,
3635173029746D2700F9B045 /* AsyncMedia.swift in Sources */,
36E20F2F298215640022D151 /* Multiplexer.swift in Sources */,
3620DEE42C392F2000584FFC /* Zip.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
26 changes: 18 additions & 8 deletions AsyncMux/Sources/Multiplexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ public final class Multiplexer<T: Codable & Sendable>: MuxRepositoryProtocol {

public typealias OnFetch = @Sendable () async throws -> T

public let cacheKey: String
public let cacheKey: String?

/// Instantiates a `Multiplexer<T>` object with a given `onFetch` block.
/// - parameter cacheKey (optional): a string to be used as a file name for the disk cache. If omitted, an automatic name is generated based on `T`'s description. NOTE: if you have several multiplexers whose `T` is the same, you *should* define unique non-conflicting `cacheKey` parameters for each.
/// - parameter onFetch: an async throwing block that should retrieve an object presumably in an asynchronous manner.
nonisolated
public init(cacheKey: String? = nil, onFetch: @escaping OnFetch) {
self.cacheKey = cacheKey ?? String(describing: T.self)
self.cacheKey = cacheKey
self.onFetch = onFetch
}

Expand All @@ -58,7 +58,9 @@ public final class Multiplexer<T: Codable & Sendable>: MuxRepositoryProtocol {
/// Writes the previously cached object to disk.
public func save() {
if isDirty, let storedValue {
MuxCacher.save(storedValue, domain: muxRootDomain, key: cacheKey)
if let cacheKey {
MuxCacher.save(storedValue, domain: muxRootDomain, key: cacheKey)
}
isDirty = false
}
}
Expand All @@ -70,7 +72,9 @@ public final class Multiplexer<T: Codable & Sendable>: MuxRepositoryProtocol {

/// Clears the memory and disk caches. Will trigger a full fetch on the next `request()` call.
public func clear() {
MuxCacher.delete(domain: muxRootDomain, key: cacheKey)
if let cacheKey {
MuxCacher.delete(domain: muxRootDomain, key: cacheKey)
}
clearMemory()
}

Expand All @@ -86,12 +90,12 @@ public final class Multiplexer<T: Codable & Sendable>: MuxRepositoryProtocol {
private var task: Task<T, Error>?
private var completionTime: TimeInterval = 0

internal func request(domain: String, key: LosslessStringConvertible) async throws -> T {
internal func request(domain: String?, key: LosslessStringConvertible?) async throws -> T {
if !refreshFlag, !isExpired {
if let storedValue {
return storedValue
}
else if let cachedValue = MuxCacher.load(domain: domain, key: key, type: T.self) {
else if let key, let domain, let cachedValue = MuxCacher.load(domain: domain, key: key, type: T.self) {
storedValue = cachedValue
return cachedValue
}
Expand All @@ -105,8 +109,14 @@ public final class Multiplexer<T: Codable & Sendable>: MuxRepositoryProtocol {
return try await onFetch()
}
catch {
if error.isSilencable, let cachedValue = storedValue ?? MuxCacher.load(domain: domain, key: key, type: T.self) {
return cachedValue
if error.isSilencable {
if let storedValue {
return storedValue
}
else if let key, let domain, let cachedValue = MuxCacher.load(domain: domain, key: key, type: T.self) {
storedValue = cachedValue
return cachedValue
}
}
throw error
}
Expand Down
16 changes: 11 additions & 5 deletions AsyncMux/Sources/MultiplexerMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public final class MultiplexerMap<K: MuxKey, T: Codable & Sendable>: MuxReposito

public typealias OnFetch = @Sendable (K) async throws -> T

public let cacheKey: String
public let cacheKey: String?

/// Instantiates a `MultiplexerMap<T>` object with a given `onFetch` block.
/// - parameter cacheKey (optional): a string to be used as a file name for the disk cache. If omitted, an automatic name is generated based on `T`'s description. NOTE: if you have several multiplexers whose `T` is the same, you *should* define unique non-conflicting `cacheKey` parameters for each.
/// - parameter onFetch: an async throwing block that should retrieve an object by a given key presumably in an asynchronous manner.
nonisolated
public init(cacheKey: String? = nil, onFetch: @escaping OnFetch) {
self.cacheKey = cacheKey ?? String(describing: T.self)
self.cacheKey = cacheKey
self.onFetch = onFetch
}

Expand Down Expand Up @@ -72,7 +72,9 @@ public final class MultiplexerMap<K: MuxKey, T: Codable & Sendable>: MuxReposito
public func save() {
muxMap.forEach { key, mux in
if mux.isDirty, let storedValue = mux.storedValue {
MuxCacher.save(storedValue, domain: cacheKey, key: String(key))
if let cacheKey {
MuxCacher.save(storedValue, domain: cacheKey, key: String(key))
}
mux.isDirty = false
}
}
Expand All @@ -88,13 +90,17 @@ public final class MultiplexerMap<K: MuxKey, T: Codable & Sendable>: MuxReposito

/// Clears the memory and disk caches for an object with a given `key`. Will trigger a full fetch on the next `request(key:)` call.
public func clear(key: K) {
MuxCacher.delete(domain: cacheKey, key: String(key))
if let cacheKey {
MuxCacher.delete(domain: cacheKey, key: String(key))
}
clearMemory(key: key)
}

/// Clears the memory and disk caches all objects in this `MultiplexerMap`. Will trigger a full fetch on the next `request(key:)` call.
public func clear() {
MuxCacher.deleteDomain(cacheKey)
if let cacheKey {
MuxCacher.deleteDomain(cacheKey)
}
clearMemory()
}

Expand Down
2 changes: 1 addition & 1 deletion AsyncMux/Sources/MuxCacher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation


public class MuxCacher {
final public class MuxCacher {

public static func load<T: Decodable>(domain: String, key: LosslessStringConvertible, type: T.Type) -> T? {
return try? JSONDecoder().decode(type, from: Data(contentsOf: cacheFileURL(domain: domain, key: key, create: false)))
Expand Down
15 changes: 10 additions & 5 deletions AsyncMux/Sources/MuxRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public protocol MuxRepositoryProtocol: Sendable {
func save() async
func clearMemory() async
func clear() async
var cacheKey: String { get }
var cacheKey: String? { get }
}


Expand Down Expand Up @@ -45,13 +45,18 @@ public actor MuxRepository {
}

func register(mux: MuxRepositoryProtocol) {
let id = mux.cacheKey
precondition(repo[id] == nil, "MuxRepository: duplicate registration (Cache key: \(id))")
repo[id] = mux
guard let key = mux.cacheKey else {
preconditionFailure("MuxRepository: cacheKey is required for \(String(describing: mux))")
}
precondition(repo[key] == nil, "MuxRepository: duplicate registration (Cache key: \(key))")
repo[key] = mux
}

func unregister(mux: MuxRepositoryProtocol) {
repo.removeValue(forKey: mux.cacheKey)
guard let key = mux.cacheKey else {
preconditionFailure("MuxRepository: cacheKey is required for \(String(describing: mux))")
}
repo.removeValue(forKey: key)
}

private var repo: [String: MuxRepositoryProtocol] = [:]
Expand Down
99 changes: 99 additions & 0 deletions AsyncMux/Sources/Zip.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// Zip.swift
// AsyncMuxDemo
//
// Created by Hovik Melikyan on 06.07.24.
//

import Foundation


// This is an experimental module not yet included in the documentation.


/// `Zip<T>` allows to combine two or more parallel asynchronous actions into one and receive the results from all of them at once, when they become available. The result of the execution is returned as an array of T.
public struct Zip<T: Sendable>: Sendable {

public typealias Action = @Sendable () async throws -> T

private var actions: [Action]

public init(actions: [Action] = []) {
self.actions = actions
}

/// Adds an asynchronous action to be executed in parallel with others. The action is not executed until `result` is called on this Zip object.
public mutating func add(_ action: @escaping Action) {
actions.append(action)
}

/// Simultaneously executes the actions added earlier using `add()` and returns the result as an array of T when all results become available.
public var result: [T] {
get async throws {
guard !actions.isEmpty else {
return []
}
return try await withThrowingTaskGroup(of: T.self) { group in
for action in actions {
group.addTask(operation: action)
}
var results: [T] = []
for try await result in group {
results.append(result)
}
return results
}
}
}
}

/// Executes asynchronous actions `a` and `b` and returns their results as a tuple (A, B) when both become available.
public func zip<A, B>(
_ a: @Sendable () async throws -> A,
_ b: @Sendable () async throws -> B) async throws -> (A, B) {
async let a = try await a()
async let b = try await b()
return try await (a, b)
}


/// Executes asynchronous actions `a`, `b` and `c` and returns their results as a tuple (A, B, C) when all of them become available.
public func zip<A, B, C>(
_ a: @Sendable () async throws -> A,
_ b: @Sendable () async throws -> B,
_ c: @Sendable () async throws -> C) async throws -> (A, B, C) {
async let a = try await a()
async let b = try await b()
async let c = try await c()
return try await (a, b, c)
}


/// Executes asynchronous actions `a`, `b`, `c`, and `d` and returns their results as a tuple (A, B, C, D) when all of them become available.
public func zip<A, B, C, D>(
_ a: @Sendable () async throws -> A,
_ b: @Sendable () async throws -> B,
_ c: @Sendable () async throws -> C,
_ d: @Sendable () async throws -> D) async throws -> (A, B, C, D) {
async let a = try await a()
async let b = try await b()
async let c = try await c()
async let d = try await d()
return try await (a, b, c, d)
}


/// Executes asynchronous actions `a`, `b`, `c`, `d`, and `e` and returns their results as a tuple (A, B, C, D, E) when all of them become available.
public func zip<A, B, C, D, E>(
_ a: @Sendable () async throws -> A,
_ b: @Sendable () async throws -> B,
_ c: @Sendable () async throws -> C,
_ d: @Sendable () async throws -> D,
_ e: @Sendable () async throws -> E) async throws -> (A, B, C, D, E) {
async let a = try await a()
async let b = try await b()
async let c = try await c()
async let d = try await d()
async let e = try await e()
return try await (a, b, c, d, e)
}
16 changes: 16 additions & 0 deletions AsyncMuxDemo/AsyncMux.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
Loading

0 comments on commit 78ba915

Please # to comment.