🎭 Swift async/await & Actor-powered effectful state-management framework.

Overview

🎭 Actomaton

Swift 5.5

🧑‍🎤 Actor + 🤖 Automaton = 🎭 Actomaton

Actomaton is Swift async/await & Actor-powered effectful state-management framework inspired by Elm and swift-composable-architecture.

This repository consists of 2 frameworks:

  1. Actomaton: Actor-based effect-handling state-machine at its core. Linux ready.
  2. ActomatonStore: SwiftUI & Combine support

These frameworks depend on swift-case-paths as Functional Prism library, which is a powerful tool to construct an App-level Mega-Reducer from each screen's Reducers.

This framework is a successor of the following projects:

Demo App

1. Actomaton (Core)

Example 1-1. Simple Counter

enum State {
    var count: Int = 0
}

enum Action {
    case increment
    case decrement
}

typealias Enviornment = Void

let reducer: Reducer<Action, State, Environment>
reducer = Reducer { action, state, environment in
    switch action {
    case .increment:
        state.count += 1
        return Effect.empty
    case .decrement:
        state.count -= 1
        return Effect.empty
    }
}

let actomaton = Actomaton<Action, State>(
    state: State(),
    reducer: reducer
)

@main
enum Main {
    static func main() async {
        assertEqual(await actomaton.state.count, 0)

        await actomaton.send(.increment)
        assertEqual(await actomaton.state.count, 1)

        await actomaton.send(.increment)
        assertEqual(await actomaton.state.count, 2)

        await actomaton.send(.decrement)
        assertEqual(await actomaton.state.count, 1)

        await actomaton.send(.decrement)
        assertEqual(await actomaton.state.count, 0)
    }
}

If you want to do some logging (side-effect), add Effect in Reducer as follows:

reducer = Reducer { action, state, environment in
    switch action {
    case .increment:
        state.count += 1
        return Effect.fireAndForget {
            print("increment")
        }
    case .decrement:
        state.count -= 1
        return Effect.fireAndForget {
            print("decrement and sleep...")
            await Task.sleep(...) // NOTE: We can use `await`!
            print("I'm awake!")
        }
    }
}

NOTE: There are 5 ways of creating Effect in Actomaton:

  1. No side-effects, but next action only
    • Effect.nextAction
  2. Single async without next action
    • Effect.fireAndForget(id:run:)
  3. Single async with next action
    • Effect.init(id:run:)
  4. Multiple asyncs (i.e. AsyncSequence) with next actions
    • Effect.init(id:sequence:)
  5. Manual cancellation
    • Effect.cancel(id:) / .cancel(ids:)

Example 1-2. Login-Logout (and ForceLogout)

login-diagram

enum State {
    case loggedOut, loggingIn, loggedIn, loggingOut
}

enum Action {
    case login, loginOK, logout, logoutOK
    case forceLogout
}

// NOTE:
// Use same `EffectID` so that if previous effect is still running,
// next effect with same `EffectID` will automatically cancel the previous one.
//
// Note that `EffectID` is also useful for manual cancellation via `Effect.cancel`.
struct LoginFlowEffectID: EffectIDProtocol {}

struct Environment {
    let loginEffect: (userId: String) -> Effect<Action>
    let logoutEffect: Effect<Action>
}

let environment = Environment(
    loginEffect: { userId in
        Effect(id: LoginFlowEffectID()) {
            let loginRequest = ...
            let data = try? await URLSession.shared.data(for: loginRequest)
            if Task.isCancelled { return nil }
            ...
            return Action.loginOK // next action
        }
    },
    logoutEffect: {
        Effect(id: LoginFlowEffectID()) {
            let logoutRequest = ...
            let data = try? await URLSession.shared.data(for: logoutRequest)
            if Task.isCancelled { return nil }
            ...
            return Action.logoutOK // next action
        }
    }
)

let reducer = Reducer { action, state, environment in
    switch (action, state) {
    case (.login, .loggedOut):
        state = .loggingIn
        return environment.login(state.userId)

    case (.loginOK, .loggingIn):
        state = .loggedIn
        return .empty

    case (.logout, .loggedIn),
        (.forceLogout, .loggingIn),
        (.forceLogout, .loggedIn):
        state = .loggingOut
        return environment.logout()

    case (.logoutOK, .loggingOut):
        state = .loggedOut
        return .empty

    default:
        return Effect.fireAndForget {
            print("State transition failed...")
        }
    }
}

let actomaton = Actomaton<Action, State>(
    state: .loggedOut,
    reducer: reducer,
    environment: environment
)

@main
enum Main {
    static func test_login_logout() async {
        var t: Task<(), Never>?

        assertEqual(await actomaton.state, .loggedOut)

        t = await actomaton.send(.login)
        assertEqual(await actomaton.state, .loggingIn)

        await t?.value // wait for previous effect
        assertEqual(await actomaton.state, .loggedIn)

        t = await actomaton.send(.logout)
        assertEqual(await actomaton.state, .loggingOut)

        await t?.value // wait for previous effect
        assertEqual(await actomaton.state, .loggedOut)

        XCTAssertFalse(isLoginCancelled)
    }

    static func test_login_forceLogout() async throws {
        var t: Task<(), Never>?

        assertEqual(await actomaton.state, .loggedOut)

        await actomaton.send(.login)
        assertEqual(await actomaton.state, .loggingIn)

        // Wait for a while and interrupt by `forceLogout`.
        // Login's effect will be automatically cancelled because of same `EffectID.
        await Task.sleep(/* 1 ms */)
        t = await actomaton.send(.forceLogout)

        assertEqual(await actomaton.state, .loggingOut)

        await t?.value // wait for previous effect
        assertEqual(await actomaton.state, .loggedOut)

    }
}

Here we see the notions of EffectID, Environment, and let task: Task<(), Never> = actomaton.send(...)

  • EffectID is for both manual & automatic cancellation of previous running effects. In this example, forceLogout will cancel login's networking effect.
  • Environment is useful for injecting effects to be called inside Reducer so that they become replaceable. Environment is known as Dependency Injection (using Reader monad).
  • (Optional) Task<(), Never> returned from actomaton.send(action) is another fancy way of dealing with "all the effects triggered by action". We can call await task.value to wait for all of them to be completed, or task.cancel() to cancel all. Note that Actomaton already manages such tasks for us internally, so we normally don't need to handle them by ourselves (use this as a last resort!).

Example 1-3. Timer (using AsyncSequence)

typealias State = Int

enum Action {
    case start, tick, stop
}

struct TimerID: EffectIDProtocol {}

struct Environment {
    let timerEffect: Effect<Action>
}

let environment = Environment(
    timerEffect: { userId in
        Effect(id: TimerID(), sequence: AsyncStream<()> { continuation in
            let task = Task {
                while true {
                    await Task.sleep(/* 1 sec */)
                    continuation.yield(())
                }
            }

            continuation.onTermination = { @Sendable _ in
                task.cancel()
            }
        }
    }
)

let reducer = Reducer { action, state, environment in
    switch action {
    case .start:
        return environment.timerEffect
    case .tick:
        state += 1
        return .empty
    case .stop:
        return Effect.cancel(id: TimerID())
    }
}

let actomaton = Actomaton<Action, State>(
    state: 0,
    reducer: reducer,
    environment: environment
)

@main
enum Main {
    static func test_timer() async {
        assertEqual(await actomaton.state, 0)

        await actomaton.send(.start)

        assertEqual(await actomaton.state, 0)

        await Task.sleep(/* 1 sec */)
        assertEqual(await actomaton.state, 1)

        await Task.sleep(/* 1 sec */)
        assertEqual(await actomaton.state, 2)

        await Task.sleep(/* 1 sec */)
        assertEqual(await actomaton.state, 3)

        await actomaton.send(.stop)

        await Task.sleep(/* long enough */)
        assertEqual(await actomaton.state, 3,
                    "Should not increment because timer is stopped.")
    }
}

In this example, Effect(id:sequence:) is used for timer effect, which yields Action.tick multiple times.

Example 1-4. Reducer composition

Actomaton-Gallery provides a good example of how Reducers can be combined together into one big Reducer using Reducer.combine.

In this example, swift-case-paths is used as a counterpart of WritableKeyPath, so if we use both, we can easily construct Mega-Reducer without a hussle.

(NOTE: CasePath is useful when dealing with enums, e.g. enum Action and enum Current in this example)

enum Root {} // just a namespace

extension Root {
    enum Action {
        case changeCurrent(State.Current?)

        case counter(Counter.Action)
        case stopwatch(Stopwatch.Action)
        case stateDiagram(StateDiagram.Action)
        case todo(Todo.Action)
        case github(GitHub.Action)
    }

    struct State: Equatable {
        var current: Current?

        // Current screen (NOTE: enum, so only 1 screen will appear)
        enum Current: Equatable {
            case counter(Counter.State)
            case stopwatch(Stopwatch.State)
            case stateDiagram(StateDiagram.State)
            case todo(Todo.State)
            case github(GitHub.State)
        }
    }

    // NOTE: `contramap` is also called `pullback` in swift-composable-architecture.
    static var reducer: Reducer<Action, State, Environment> {
        Reducer.combine(
            Counter.reducer
                .contramap(action: /Action.counter)
                .contramap(state: /State.Current.counter)
                .contramap(state: \State.current)
                .contramap(environment: { _ in () }),

            Todo.reducer
                .contramap(action: /Action.todo)
                .contramap(state: /State.Current.todo)
                .contramap(state: \State.current)
                .contramap(environment: { _ in () }),

            StateDiagram.reducer
                .contramap(action: /Action.stateDiagram)
                .contramap(state: /State.Current.stateDiagram)
                .contramap(state: \State.current)
                .contramap(environment: { _ in () }),

            Stopwatch.reducer
                .contramap(action: /Action.stopwatch)
                .contramap(state: /State.Current.stopwatch)
                .contramap(state: \State.current)
                .contramap(environment: { $0.stopwatch }),

            GitHub.reducer
                .contramap(action: /Action.github)
                .contramap(state: /State.Current.github)
                .contramap(state: \State.current)
                .contramap(environment: { $0.github }),

            GitHub.reducer
                .contramap(action: /Action.github)
                .contramap(state: /State.Current.github)
                .contramap(state: \State.current)
                .contramap(environment: { $0.github })
        )
    }
}

To learn more about CasePath, visit the official site and tutorials:

2. ActomatonStore (SwiftUI)

Store (from ActomatonStore.framework) provides a thin wrapper of Actomaton to work seamlessly in SwiftUI world. It will be stored as a @StateObject of the App's view.

For example, from Actomaton-Gallery:

struct AppView: View {
    @StateObject
    private var store: Store<Root.Action, Root.State> = .init(
        state: Root.State(...),
        reducer: Root.reducer,
        environment: Root.Environment(...)
    )

    init() {}

    var body: some View {
        // IMPORTANT: Pass `Store.Proxy` to children.
        RootView(store: self.store.proxy)
    }
}

struct RootView: View {
    // IMPORTANT: `Store.Proxy`, not `Store` itself.
    private let store: Store<Root.Action, Root.State>.Proxy

    init(store: Store<Root.Action, Root.State>.Proxy) {
        self.store = store
    }

    var body: some View {
        return VStack {
            NavigationView {
                List {
                    navigationLink(example: allExamples[0])
                    navigationLink(example: allExamples[1])
                    navigationLink(example: allExamples[2])
                    navigationLink(example: allExamples[3])
                    navigationLink(example: allExamples[4])
                }
                .navigationBarTitle(Text("🎭 Actomaton Gallery 🖼️"), displayMode: .large)
            }
        }
    }

    private func navigationLink(example: Example) -> some View {
        NavigationLink(
            destination: example.exampleView(store: self.store)
                .navigationBarTitle(
                    "\(example.exampleTitle)",
                    displayMode: .inline
                ),
            isActive: self.store.current
                .stateBinding(onChange: Root.Action.changeCurrent)
                .transform(
                    get: { $0?.example.exampleTitle == example.exampleTitle },
                    set: { _, isPresenting in
                        isPresenting ? example.exampleInitialState : nil
                    }
                )
        ) {
            HStack(alignment: .firstTextBaseline) {
                example.exampleIcon
                    .frame(width: 44)
                Text(example.exampleTitle)
            }
            .font(.body)
            .padding(5)
        }
    }
}

Here, the most important part is that the topmost container view of the app will hold Store as a single source of truth, and for the child views, Store.Proxy will be passed instead, so that we don't duplicate multiple Stores but Binding and Store.proxy.send (sending message to topmost Store) functionalities are still available.

This (and overall) architecture is inherited from Harvest which has a different Store structure compared to swift-composable-architecture.

See following information for more details:

Store.Proxy.stateBinding

To interact with SwiftUI's State-binding-based event handling, ActomatonStore provides Store.Proxy.stateBinding hook methods so that direct state changes can be converted into action dispatches (indirection) This indirection allows us to build more sophisticated UI architecture including better testing.

References

License

MIT

Comments
  • Unsafe flags were added and after that I cannot use Actomaton as a dependency

    Unsafe flags were added and after that I cannot use Actomaton as a dependency

    Hi.

    I played with Actomaton version 0.3.1 and I was happy with it. Now I tried to upgrade to 0.6.0 and I get the following compilation error: The package product 'ActomatonStore' cannot be used as a dependency of this target because it uses unsafe build flags.

    The commit which introduced the change is this: Add Sendable conformances, -warn-concurrency & -enable-actor-data-race-checks compiler options

    To me this unsafe flags error seems confusing, like for the others. If that unsafe flags are banned from the dependencies, what should we do? Is that absolutely necessary to add or there is a workaround setting into the project which depend on unsafe flagged packages?

    opened by tiborkiss 4
  • Breaking type-check when using effect binary operation multi times

    Breaking type-check when using effect binary operation multi times

    Description

    i’m facing problem about some project which is using actomaton did breaking type-check.

    no problem using binary operation + 1~3 times. but sometimes use over 3 times, it's problem occurred.

    i hope some advice about it 🙏

    Sample code

    • https://github.com/yochidros/ActomatonSample/blob/main/Sources/ActomatonSample/ActomatonSample.swift

    It happens type-check error. Compiler error message:

    The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
    
    スクリーンショット 2022-09-06 14 44 29

    Workaround: I do separated some effects as local variables. it's work fine.

    ex.

     let effect1 = Effect { 
          // do some side effect
         Action.a
     } + Effect {
        // do some side effect
        return nil
     }
     
     let effect2 = Effect { 
          // do some side effect
         Action.b
      } + Effect {
        // do some side effect
        return nil
     }
     
    // work fine
     return effect1 + effect2
    
    opened by yochidros 2
  • [Store] Fix: Add `func send` task capture `[weak self]`

    [Store] Fix: Add `func send` task capture `[weak self]`

    This PR, Fix tests ActomatonStore.deinitTests to pass from failure.

    スクリーンショット 2022-06-15 9 51 56

    it's ActomatonStore fixed to add weak self capture because to when do deinit then not deallocation Store instance.

    opened by yochidros 1
  • [Actomaton] Use `Task.detached` for executing effects

    [Actomaton] Use `Task.detached` for executing effects

    This PR improves effect handling in Actomaton and MainActomaton, by internally using Task.detached so that running-effects don't necessary have to follow the same Actomaton's executor which allows separate execution path from state updates.

    opened by inamiy 0
  • [Test] Add ActomatonUITests

    [Test] Add ActomatonUITests

    Related:

    • https://github.com/Actomaton/Actomaton/pull/62
    • #66

    This PR adds ActomatonUITests which is copied from original ActomatonStoreTests introduced in https://github.com/Actomaton/Actomaton/pull/62 .

    NOTE: The codebase is exactly the same as before.

    opened by inamiy 0
  • [UI] Add `StoreConfiguration` for Store's better `LogFormat` support

    [UI] Add `StoreConfiguration` for Store's better `LogFormat` support

    Continued from:

    • #69

    Related:

    • #66

    This PR adds StoreConfiguration that allows better Action & State debug-logging including the observation of direct-state-binding changes, which wasn't possible before.

    By introducing this PR, ActomatonUI will now depend on ActomatonDebugging.

    enhancement 
    opened by inamiy 0
  • [Debugging] Refactor ActomatonDebugging, adding `LogFormat` & `Reducer.log(format:)`

    [Debugging] Refactor ActomatonDebugging, adding `LogFormat` & `Reducer.log(format:)`

    This PR adds LogFormat & Reducer.log(format:) in ActomatonDebugging, which are often useful to manually disable by programmatically setting nil instead of forcefully opt-in/out by #if DEBUG compiler flag.

    Also, file names and API names are changed from "DebugLogFormat" to "LogFormat".

    enhancement 
    opened by inamiy 0
  • [Doc] Add ActomatonUI.docc

    [Doc] Add ActomatonUI.docc

    Documentation for:

    • #66

    This PR adds ActomatonUI.docc that is revised from existing ActomatonStore.docc.

    Please see ActomatonUI doc: https://actomaton.github.io/Actomaton/documentation/actomatonui

    documentation 
    opened by inamiy 0
  • Introduce ActomatonUI

    Introduce ActomatonUI

    Successor of:

    • #64

    Integration example:

    • https://github.com/Actomaton/Actomaton-Gallery/pull/42

    Overview

    This PR adds a new ActomatonUI module that inherits the core functionality from ActomatonStore which also allows SwiftUI-rendering optimization using ViewStore.

    The original idea of ViewStore is from swift-composable-architecture with compatible API names (e.g. ViewStore) and slight internal differences.

    Background

    Current Actomaton-Gallery demo app uses ActomatonStore as a SwiftUI-bridge of Actomaton.

    It has a very simple (and naive) rendering structure: i.e. putting @StateObject in a root SwiftUI.View and let child views only own its (sub) Binding<State>s:

    1. Store (ObservableObject) lives at topmost view, and will be observed by @StateObject
    2. Store.Proxy lives in each subviews, and is capable of providing Binding<State>

    so that only a single observation point (@StateObject var store: Store) was necessary at the topmost view.

    This approach is a very traditional Elm-like rendering approach that always does let wholeView = render(wholeState) (not let partialView = render(partialState) and manually summing up), and let rendering system take care of the complicated "partial" diffing part.

    As of iOS 15, this approach worked fairly well including high-CPU-usage SwiftUI screens, e.g. LineCollisionExample that runs at 60 frames per seconds + a lot of user-dragging events + physics calculation, to make views updated per every tick. This means, SwiftUI's diff-rendering is actually already quite performant although the ActomatonStore strategy always re-renders from the topmost view.

    However, this naive approach had some other drawbacks too:

    1. Explicit animation (withAnimation) didn't work as expected in the screens where NavigationView wraps
    2. A new iOS 16 NavigationStack causes the re-rendering not working correctly

    Yes, The SwiftUI Navigation™️ issue!

    While there had been some workarounds applied to NavigationView in every iOS releasing year which barely worked until iOS 15, but now, new iOS 16 (as of Beta 3) NavigationStack will break it in even more hopeless way that single-@StateObject-at-top (and providing child stores to subviews) approach is unlikely become possible.

    Since the basic SwiftUI design and (implicit) guideline is to "use ViewModel to wrap state per screen (OOP-style)", unfortunately, Elm-style also needs to somehow adopt this rule so that safe and faster rendering can become possible.

    Luckily, the refactoring could be easily done by just swapping the above strategy into:

    1. Store (non-ObservableObject) lives at topmost view, and narrows down as sub-stores to be passed to its child views
    2. ViewStore (ObservableObject) lives in each subviews as a partial SwiftUI states to be observed by @ObservabedObject, and is capable of providing Binding<State>

    which literally has the same mechanism as swift-composable-architecture which already has solved in the very beginning.

    Thus, this PR now applies its basic strategy with following the same naming, including WithViewStore as an efficient partial view re-rendering component.

    The cons of ViewStore approach is that WithViewStore will now require in many places throughout the app (rather than just placing single @StateObject at root), which often requires state-extracting optimization to narrow down Store's scope to only hold the very necessary subsets for minimal changes (which is actually quite difficult process whenever one considers about creating the container view that contains some child view, and let only child rendering run optimally).

    As mentioned above, this cumbersome issue is due to SwiftUI's ViewModel-first design and Navigation issue that easily breaks per every iOS release. As ViewStore is a translation from Elm (sub-)store into ViewModel (per each screen) which is a safe and decent approach, I decided to take this pill for safer future implementation without bothering SwiftUI changes too much.

    enhancement 
    opened by inamiy 0
  • [Store] Use `MainActomaton`

    [Store] Use `MainActomaton`

    Successor of:

    • #64

    Overview

    This PR replaces Store's usage of actor Actomaton with @MainActor class MainActomaton which is introduced in #64 for:

    1. Simplifying APIs, removing #40 StoreConfiguration
    2. Enhanced state updates to be applied to SwiftUI views
      • Existing approach using non-main-thread actor Actomaton may sometimes cause old state updates when:
        1. setting StoreConfiguration.updatesStateImmediately = true and
        2. state updates occur in high frequency.

    Issue 2b. has been observed when performing Actomaton-Gallery's LineCollisionExample which runs in 60 frames per seconds (by timer) + additional user-object-dragging, that led to too high update frequency. While Store could update states on main-thread immediately by updatesStateImmediately = true, non-main-thread Actomaton may send older state back to Store due to slow operation, so this caused the malformed state update onto UI.

    enhancement 
    opened by inamiy 0
  • Introduce `MainActomaton`

    Introduce `MainActomaton`

    This PR introduces @MainActor class MainActomaton where the original code is derived from actor Actomaton for better SwiftUI animation handling.

    By adding this new class:

    1. Actor-syncing between MainActor and Actomaton will no longer become necessary
    2. #40 will no longer become necessary

    The overall codebase will become more simple and developer-friendly.

    Important Notice

    This PR is not integrated in ActomatonStore module yet, which will be done in separate PR. Also, ActomatonStore will be deprecated in future in favor of forth-coming ActomatonUI module.

    Comment in source code

    MainActomaton.swift is derived from Actomaton.swift with replacing to @MainActor, fixing unnecessary async-awaits and adding some workarounds.

    Code must be in sync as much as possible.

    This code duplication is unfortunately required since Swift (as of 5.7) doesn't support custom actor executor yet. https://forums.swift.org/t/support-custom-executors-in-swift-concurrency/44425

    The main goal of this code duplication is to allow MainActomaton to work on SwiftUI and its explicit animations on main-thread (via withAnimation) more seamlessly without hopping around between MainActor and (non-Main) Actomaton.

    Note that there is a funny hack to pretend (non-main) actor to use MainActor's exectutor, but this approach still doesn't solve SwiftUI's explicit animation issue, since launching a new Task is still required (compiler can't distinguish its unnecessity) thus causing to run on event-loop's next-tick. https://gist.github.com/inamiy/6ab294f7cc47d1f79b892740578a712a

    enhancement 
    opened by inamiy 0
  • 7guis showcase

    7guis showcase

    I wonder whether this could be used as the underlying "data framework" for implementing 7guis using any GUI toolking of your choice (probably one of those used in Apple ecosystems)?

    opened by dumblob 3
Releases(0.7.0)
  • 0.7.0(Jul 23, 2022)

    For Swift 5.6, Xcode 13.4.1 (and Swift 5.7, Xcode 14 Beta 3).

    Milestone: https://github.com/inamiy/Actomaton/milestone/12?closed=1

    Important changes

    • Introduce ActomatonUI by @inamiy in https://github.com/Actomaton/Actomaton/pull/66

    ActomatonUI module is introduced in place of ActomatonStore for better SwiftUI rendering, especially for forthcoming iOS 16 NavigationStack. For more details, check https://github.com/Actomaton/Actomaton/pull/66 .

    What's Changed

    • Introduce MainActomaton by @inamiy in https://github.com/Actomaton/Actomaton/pull/64
    • [Store] Use MainActomaton by @inamiy in https://github.com/Actomaton/Actomaton/pull/65
    • Introduce ActomatonUI by @inamiy in https://github.com/Actomaton/Actomaton/pull/66
    • [UI] Improve HostingViewController by @inamiy in https://github.com/Actomaton/Actomaton/pull/67
    • [Debugging] Refactor ActomatonDebugging, adding LogFormat & Reducer.log(format:) by @inamiy in https://github.com/Actomaton/Actomaton/pull/69
    • [UI] Add StoreConfiguration for Store's better LogFormat support by @inamiy in https://github.com/Actomaton/Actomaton/pull/70
    • [Doc] Add ActomatonUI.docc by @inamiy in https://github.com/Actomaton/Actomaton/pull/68
    • [Test] Add ActomatonUITests by @inamiy in https://github.com/Actomaton/Actomaton/pull/71

    Full Changelog: https://github.com/Actomaton/Actomaton/compare/0.6.2...0.7.0

    Source code(tar.gz)
    Source code(zip)
  • 0.6.2(Jun 24, 2022)

    For Swift 5.6, Xcode 13.3.1.

    Milestone: https://github.com/inamiy/Actomaton/milestone/11?closed=1

    What's Changed

    • [Store] fix access level for viewdidload of hostingviewcontroller by @RyogaBarbie in https://github.com/Actomaton/Actomaton/pull/63

    New Contributors

    • @RyogaBarbie made their first contribution in https://github.com/Actomaton/Actomaton/pull/63

    Full Changelog: https://github.com/Actomaton/Actomaton/compare/0.6.1...0.6.2

    Source code(tar.gz)
    Source code(zip)
  • 0.6.1(Jun 15, 2022)

    For Swift 5.6, Xcode 13.3.1.

    Milestone: https://github.com/inamiy/Actomaton/milestone/10?closed=1

    Important bug fix

    • [Store] Fix: Add func send task capture [weak self] by @yochidros in https://github.com/Actomaton/Actomaton/pull/62

    What's Changed

    • [Actomaton] Add Reducer.map(id:) & Reducer.map(queue:) by @inamiy in https://github.com/Actomaton/Actomaton/pull/54
    • [Test] Fix EffectQueueDelayTests.test_DelayedNewest2EffectQueue for Swift 5.7 slow cancellation propagation by @inamiy in https://github.com/Actomaton/Actomaton/pull/55
    • Update README.md on how to install by @inamiy in https://github.com/Actomaton/Actomaton/pull/57
    • [Test] Improve DeinitTests by @inamiy in https://github.com/Actomaton/Actomaton/pull/58
    • [Test] Fix DeinitTests by @inamiy in https://github.com/Actomaton/Actomaton/pull/59
    • [Test] Migrate Fixture to TestFixture module by @inamiy in https://github.com/Actomaton/Actomaton/pull/60

    New Contributors

    • @yochidros made their first contribution in https://github.com/Actomaton/Actomaton/pull/62

    Full Changelog: https://github.com/Actomaton/Actomaton/compare/0.6.0...0.6.1

    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(May 16, 2022)

    For Swift 5.6, Xcode 13.3.1.

    Milestone: https://github.com/inamiy/Actomaton/milestone/9?closed=1

    What's Changed

    Feature

    • [Actomaton] Add EffectQueueDelay by @inamiy in https://github.com/inamiy/Actomaton/pull/49

    Intrastructure

    • [CI] Use Xcode 13.3.1 (Swift 5.6) by @inamiy in https://github.com/inamiy/Actomaton/pull/51
    • Update README.md on EffectQueue by @inamiy in https://github.com/inamiy/Actomaton/pull/50
    • [Makefile] Add make docs by @inamiy in https://github.com/inamiy/Actomaton/pull/52
    • [Docs] Update Actomaton.docc & README.md by @inamiy in https://github.com/inamiy/Actomaton/pull/53

    Full Changelog: https://github.com/inamiy/Actomaton/compare/0.5.3...0.6.0

    Source code(tar.gz)
    Source code(zip)
  • 0.5.3(May 14, 2022)

    For Swift 5.6, Xcode 13.3.1.

    Milestone: https://github.com/inamiy/Actomaton/milestone/8?closed=1

    What's Changed

    • [Store] Add Store.Proxy.noEnvironment by @inamiy in https://github.com/inamiy/Actomaton/pull/47
    • [Actomaton] Fix .runOldest(n, .discardNew) auto-cancellation handling by @inamiy in https://github.com/inamiy/Actomaton/pull/48

    Full Changelog: https://github.com/inamiy/Actomaton/compare/0.5.2...0.5.3

    Source code(tar.gz)
    Source code(zip)
  • 0.5.2(May 8, 2022)

  • 0.5.1(May 2, 2022)

  • 0.5.0(Apr 24, 2022)

  • 0.3.1(Jan 23, 2022)

  • 0.3.0(Jan 20, 2022)

  • 0.2.1(Dec 27, 2021)

  • 0.2.0(Dec 16, 2021)

Owner
Yasuhiro Inami
Functional Programmer at @delyjp / KURASHIRU / クラシル. Interests: Swift / Haskell / PureScript / Elm / Rust / TypeScript / Category Theory
Yasuhiro Inami
An actor model library for swift.

Aojet Aojet is an actor model implemetion for swift. Features Asynchronous, non-blocking and highly performant message-driven programming model Safe a

null 37 Apr 7, 2022
**`withCheckedContinuation`'s body will run on background thread in case of starting from main-actor.**

ConcurrencyContinuationReproduce Differences of Concurrency behaviors between Xcode 14.0 and 14.1 Xcode 14.0 iOS 13+: Runs on main (inherited same con

Hiroshi Kimura 4 Dec 20, 2022
Hydra ⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

Lightweight full-featured Promises, Async & Await Library in Swift What's this? Hydra is full-featured lightweight library which allows you to write b

Daniele Margutti 2k Dec 24, 2022
⏳ Collection of Swift 5.5 async/await utility functions.

⏳ FunAsync Collection of Swift 5.5 async/await utility functions. Throw <-> Result conversion asyncThrowsToAsyncResult asyncResultToAsyncThrows More C

Yasuhiro Inami 23 Oct 14, 2022
A lightweight swift network layer with Combine, Async-Await, and a traditional completion block.

CombineNetwork A simple light-weight network library to make network requesting simpler. It supports newer techonology such as async/await as well as

Dushant Singh 4 Jan 3, 2022
Hydra: Lightweight full-featured Promises, Async-Await Library in Swift

Async Functions for ECMAScript The introduction of Promises and Generators in EC

Ecma TC39 1.6k Oct 17, 2022
AsyncLocationKit - Async/await CoreLocation With Swift

AsyncLocationKit Wrapper for Apple CoreLocation framework with new Concurency Mo

AsyncSwift 89 Dec 30, 2022
A Swift lib for network with async/await

SmileNetwork A Swift network utility with async/await applied UseAge enum MockEndpoint { case weather(cityId: String) } extension MockEndpoint: S

null 2 Jul 13, 2022
A simple network layer for use in small iOS projects with async/await support

SimpleNetwork Intro SimpleNetwork is simple network layer for use in small projects. Swift Package Manager Note: Instructions below are for using Swif

Alexandre Garrefa 1 Nov 30, 2021
Example project showing how to use async/await with iOS 13

ios13AsyncAwait Example project showing how to use async/await with iOS 13 Article This source code is a part of an article published at This Dev Brai

Michał Tynior 7 Oct 2, 2022
Using async / await on iOS 13: everything you need to know

Using async / await on iOS 13: everything you need to know! ?? Content This repository contains the code sample I've used during December 14th's lives

Vincent Pradeilles 11 Feb 1, 2022
iOS 13-compatible backports of commonly used async/await-based system APIs that are only available from iOS 15 by default.

AsyncCompatibilityKit Welcome to AsyncCompatibilityKit, a lightweight Swift package that adds iOS 13-compatible backports of commonly used async/await

John Sundell 367 Jan 5, 2023
straightforward networking and error handling with async-await and URLSession

AsyncAwaitNetworkingPlayground How To Run Just clone the project, open it and run. Some notes about AsyncAwaitNetworkingPlayground It's a straightforw

Fırat Yenidünya 17 Dec 11, 2022
AsyncTaskKit - contains some additions to async/await Task

AsyncTaskKit This repo contains some additions to async/await Task. In general i

Michał Zaborowski 0 Jan 2, 2022
Lightweight async/await networking library with interceptor support - usable from iOS 13+.

Lightweight async/await networking library with interceptor support. ?? Getting started AsyncNetwork's session acts as a wrapper to URLSession by addi

Paul 9 Oct 4, 2022
Test app for the InStat company. The project uses MVVM architecture, interface created with SwiftUI, network requests with async/await

FootStats Test app for the InStat company. The Project uses MVVM architecture, interface created with SwiftUI, network requests with async/await using

Nizami Tagiyev 4 Dec 15, 2022
A declarative state management and dependency injection library for SwiftUI x Concurrency

A declarative state management and dependency injection library for SwiftUI x Concurrency

Ryo Aoyama 199 Jan 1, 2023
⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your appl

Said Sikira 814 Oct 30, 2022
Async and concurrent versions of Swift’s forEach, map, flatMap, and compactMap APIs.

CollectionConcurrencyKit Welcome to CollectionConcurrencyKit, a lightweight Swift package that adds asynchronous and concurrent versions of the standa

John Sundell 684 Jan 9, 2023