{"id":29089995,"url":"https://github.com/takeshishimada/lockman","last_synced_at":"2026-06-05T06:31:13.505Z","repository":{"id":299244015,"uuid":"1002434037","full_name":"takeshishimada/Lockman","owner":"takeshishimada","description":"A library for building action exclusive control with feedback-first approach, with responsiveness, transparency, and declarative design in mind.","archived":false,"fork":false,"pushed_at":"2025-06-23T01:29:54.000Z","size":801,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-23T01:32:21.780Z","etag":null,"topics":["action-management","exclusive-control","ios","swift","tca","the-composable-architecture"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/takeshishimada.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-15T13:21:51.000Z","updated_at":"2025-06-22T12:00:10.000Z","dependencies_parsed_at":"2025-06-15T15:37:28.045Z","dependency_job_id":"4a25dc98-f29f-4709-816f-1832967a955b","html_url":"https://github.com/takeshishimada/Lockman","commit_stats":null,"previous_names":["takeshishimada/lockman"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/takeshishimada/Lockman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takeshishimada%2FLockman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takeshishimada%2FLockman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takeshishimada%2FLockman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takeshishimada%2FLockman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/takeshishimada","download_url":"https://codeload.github.com/takeshishimada/Lockman/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takeshishimada%2FLockman/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262371698,"owners_count":23300599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["action-management","exclusive-control","ios","swift","tca","the-composable-architecture"],"created_at":"2025-06-28T04:06:29.717Z","updated_at":"2026-06-05T06:31:13.471Z","avatar_url":"https://github.com/takeshishimada.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"Lockman.png\" alt=\"Lockman Logo\" width=\"400\"\u003e\n\n[![CI](https://github.com/takeshishimada/Lockman/workflows/CI/badge.svg)](https://github.com/takeshishimada/Lockman/actions?query=workflow%3ACI)\n[![Swift](https://img.shields.io/badge/Swift-5.9%20%7C%205.10%20%7C%206.0-ED523F.svg?style=flat)](https://swift.org/download/)\n[![Platforms](https://img.shields.io/badge/Platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Mac%20Catalyst-333333.svg?style=flat)](https://developer.apple.com/)\n\nLockman is a Swift library that solves exclusive action control issues in The Composable Architecture (TCA) applications, with responsiveness, transparency, and declarative design in mind.\n\n* [Design Philosophy](#design-philosophy)\n* [Overview](#overview)\n* [Basic Example](#basic-example)\n* [Installation](#installation)\n* [Community](#community)\n\n## Design Philosophy\n\n### Principles from Designing Fluid Interfaces\n\nWWDC18's \"Designing Fluid Interfaces\" presented principles for exceptional interfaces:\n\n* **Immediate Response and Continuous Redirection** - Responsiveness that doesn't allow even 10ms of delay\n* **One-to-One Touch and Content Movement** - Content follows the finger during drag operations\n* **Continuous Feedback** - Immediate reaction to all interactions\n* **Parallel Gesture Detection** - Recognizing multiple gestures simultaneously\n* **Spatial Consistency** - Maintaining position consistency during animations\n* **Lightweight Interactions, Amplified Output** - Large effects from small inputs\n\n### Traditional Challenges\n\nTraditional UI development has solved problems by simply prohibiting simultaneous button presses and duplicate executions. These approaches have become factors that hinder user experience in modern fluid interface design.\n\nUsers expect some form of feedback even when pressing buttons simultaneously. It's crucial to clearly separate immediate response at the UI layer from appropriate mutual exclusion control at the business logic layer.\n\n## Overview\n\nLockman provides the following control strategies to address common problems in app development:\n\n* **Single Execution**: Prevents duplicate execution of the same action\n* **Priority Based**: Action control and cancellation based on priority\n* **Group Coordination**: Group control through leader/member roles\n* **Dynamic Condition**: Runtime condition evaluation with unified reducer API\n* **Concurrency Limited**: Limits the number of concurrent executions per group\n* **Composite Strategy**: Combination of multiple strategies\n\n## Examples\n\n| Single Execution Strategy | Priority Based Strategy | Concurrency Limited Strategy |\n|--------------------------|------------------------|------------------------------|\n| ![Single Execution Strategy](Sources/Lockman/Documentation.docc/images/01-SingleExecutionStrategy.gif) | ![Priority Based Strategy](Sources/Lockman/Documentation.docc/images/02-PriorityBasedStrategy.gif) | ![Concurrency Limited Strategy](Sources/Lockman/Documentation.docc/images/03-ConcurrencyLimitedStrategy.gif) |\n\n## Code Example\n\nHere's how to implement a feature that prevents duplicate execution of processes using the `@LockmanSingleExecution` macro:\n\n```swift\nimport CasePaths\nimport ComposableArchitecture\nimport Lockman\n\n@Reducer\nstruct ProcessFeature {\n    @ObservableState\n    struct State: Equatable {\n        var isProcessing = false\n        var message = \"\"\n    }\n    \n    @CasePathable\n    enum Action: ViewAction {\n        case view(ViewAction)\n        case `internal`(InternalAction)\n        \n        @LockmanSingleExecution\n        enum ViewAction {\n            case startProcessButtonTapped\n            \n            var lockmanInfo: LockmanSingleExecutionInfo {\n                .init(actionId: actionName, mode: .boundary)\n            }\n        }\n        \n        enum InternalAction {\n            case processStart\n            case processCompleted\n            case updateMessage(String)\n        }\n    }\n    \n    enum CancelID {\n        case userAction\n    }\n    \n    var body: some Reducer\u003cState, Action\u003e {\n        Reduce { state, action in\n            switch action {\n            case let .view(viewAction):\n                switch viewAction {\n                case .startProcessButtonTapped:\n                    return .run { send in\n                        await send(.internal(.processStart))\n                        // Simulate heavy processing\n                        try await Task.sleep(nanoseconds: 3_000_000_000)\n                        await send(.internal(.processCompleted))\n                    }\n                }\n                \n            case let .internal(internalAction):\n                switch internalAction {\n                case .processStart:\n                    state.isProcessing = true\n                    state.message = \"Processing started...\"\n                    return .none\n                    \n                case .processCompleted:\n                    state.isProcessing = false\n                    state.message = \"Processing completed\"\n                    return .none\n                    \n                case .updateMessage(let message):\n                    state.message = message\n                    return .none\n                }\n            }\n        }\n        .lock(\n            boundaryId: CancelID.userAction,\n            lockFailure: { error, send in\n                // When processing is already in progress\n                if error is LockmanSingleExecutionError {\n                    // Update message through an action instead of direct state mutation\n                    await send(.internal(.updateMessage(\"Processing is already in progress\")))\n                }\n            },\n            for: \\.view\n        )\n    }\n}\n```\n\nThe `Reducer.lock` modifier automatically applies lock management to actions that conform to `LockmanAction`. Since the `ViewAction` enum is marked with `@LockmanSingleExecution`, the `startProcessButtonTapped` action won't execute while processing is in progress. The `for: \\.view` parameter tells Lockman to check actions nested in the `view` case for `LockmanAction` conformance.\n\n### Debug Output Example\n\n```\n✅ [Lockman] canLock succeeded - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: 7BFC785A-3D25-4722-B9BC-A3A63A7F49FC, mode: boundary)\n❌ [Lockman] canLock failed - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: 1EBA9632-DE39-43B6-BE75-7C754476CD4E, mode: boundary), Reason: Boundary 'process' already has an active lock\n❌ [Lockman] canLock failed - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: 6C5C569F-4534-40D7-98F6-B4F4B0EE1293, mode: boundary), Reason: Boundary 'process' already has an active lock\n✅ [Lockman] canLock succeeded - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: C6779CD1-F8FE-46EB-8605-109F7C8DCEA8, mode: boundary)\n❌ [Lockman] canLock failed - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: A54E7748-A3DE-451A-BF06-56224A5C94DA, mode: boundary), Reason: Boundary 'process' already has an active lock\n❌ [Lockman] canLock failed - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: 7D4D67A7-1A8C-4521-BB16-92E0D551451A, mode: boundary), Reason: Boundary 'process' already has an active lock\n✅ [Lockman] canLock succeeded - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: 08CC1862-136F-4643-A796-F63156D8BF56, mode: boundary)\n❌ [Lockman] canLock failed - Strategy: SingleExecution, BoundaryId: process, Info: LockmanSingleExecutionInfo(actionId: 'startProcessButtonTapped', uniqueId: DED418D1-4A10-4EF8-A5BC-9E93D04188CA, mode: boundary), Reason: Boundary 'process' already has an active lock\n\n📊 Current Lock State (SingleExecutionStrategy):\n┌─────────────────┬──────────────────┬──────────────────────────────────────┬─────────────────┐\n│ Strategy        │ BoundaryId       │ ActionId/UniqueId                    │ Additional Info │\n├─────────────────┼──────────────────┼──────────────────────────────────────┼─────────────────┤\n│ SingleExecution │ CancelID.process │ startProcessButtonTapped             │ mode: boundary  │\n│                 │                  │ 08CC1862-136F-4643-A796-F63156D8BF56 │                 │\n└─────────────────┴──────────────────┴──────────────────────────────────────┴─────────────────┘\n```\n\n## Documentation\n\nThe documentation for releases and `main` are available here:\n\n* [`main`](https://takeshishimada.github.io/Lockman/main/documentation/lockman/)\n* [1.3.0](https://takeshishimada.github.io/Lockman/1.3.0/documentation/lockman/) ([migration guide](https://takeshishimada.github.io/Lockman/1.3.0/documentation/lockman/migratingto1.3))\n* [1.2.0](https://takeshishimada.github.io/Lockman/1.2.0/documentation/lockman/) ([migration guide](https://takeshishimada.github.io/Lockman/1.2.0/documentation/lockman/migratingto1.2))\n* [1.1.0](https://takeshishimada.github.io/Lockman/1.1.0/documentation/lockman/) ([migration guide](https://takeshishimada.github.io/Lockman/1.1.0/documentation/lockman/migratingto1.1))\n\n\u003cdetails\u003e\n\u003csummary\u003eOther versions\u003c/summary\u003e\n\n* [1.0.0](https://takeshishimada.github.io/Lockman/1.0.0/documentation/lockman/) ([migration guide](https://takeshishimada.github.io/Lockman/1.0.0/documentation/lockman/migratingto1.0))\n* [0.13.0](https://takeshishimada.github.io/Lockman/0.13.0/documentation/lockman/)\n* [0.12.0](https://takeshishimada.github.io/Lockman/0.12.0/documentation/lockman/)\n* [0.11.0](https://takeshishimada.github.io/Lockman/0.11.0/documentation/lockman/)\n* [0.10.0](https://takeshishimada.github.io/Lockman/0.10.0/documentation/lockman/)\n* [0.9.0](https://takeshishimada.github.io/Lockman/0.9.0/documentation/lockman/)\n* [0.8.0](https://takeshishimada.github.io/Lockman/0.8.0/documentation/lockman/)\n* [0.7.0](https://takeshishimada.github.io/Lockman/0.7.0/documentation/lockman/)\n* [0.6.0](https://takeshishimada.github.io/Lockman/0.6.0/documentation/lockman/)\n* [0.5.0](https://takeshishimada.github.io/Lockman/0.5.0/documentation/lockman/)\n* [0.4.0](https://takeshishimada.github.io/Lockman/0.4.0/documentation/lockman/)\n* [0.3.0](https://takeshishimada.github.io/Lockman/0.3.0/documentation/lockman/)\n\n\u003c/details\u003e\n\nThere are a number of articles in the documentation that you may find helpful as you become more comfortable with the library:\n\n### Essentials\n* [Getting Started](https://takeshishimada.github.io/Lockman/main/documentation/lockman/gettingstarted) - Learn how to integrate Lockman into your TCA application\n* [Boundary Overview](https://takeshishimada.github.io/Lockman/main/documentation/lockman/boundaryoverview) - Understand the concept of boundaries in Lockman\n* [Lock](https://takeshishimada.github.io/Lockman/main/documentation/lockman/lock) - Understanding the locking mechanism\n* [Unlock](https://takeshishimada.github.io/Lockman/main/documentation/lockman/unlock) - Understanding the unlocking mechanism\n* [Choosing a Strategy](https://takeshishimada.github.io/Lockman/main/documentation/lockman/choosingstrategy) - Select the right strategy for your use case\n* [Configuration](https://takeshishimada.github.io/Lockman/main/documentation/lockman/configuration) - Configure Lockman for your application's needs\n* [Error Handling](https://takeshishimada.github.io/Lockman/main/documentation/lockman/errorhandling) - Learn about common error handling patterns\n* [Debugging](https://takeshishimada.github.io/Lockman/main/documentation/lockman/debuggingguide) - Debug Lockman-related issues in your application\n\n### Strategies\n* [Single Execution Strategy](https://takeshishimada.github.io/Lockman/main/documentation/lockman/singleexecutionstrategy) - Prevent duplicate execution\n* [Priority Based Strategy](https://takeshishimada.github.io/Lockman/main/documentation/lockman/prioritybasedstrategy) - Control based on priority\n* [Concurrency Limited Strategy](https://takeshishimada.github.io/Lockman/main/documentation/lockman/concurrencylimitedstrategy) - Limit concurrent executions\n* [Group Coordination Strategy](https://takeshishimada.github.io/Lockman/main/documentation/lockman/groupcoordinationstrategy) - Coordinate related actions\n* [Dynamic Condition Evaluation](https://takeshishimada.github.io/Lockman/main/documentation/lockman/dynamicconditionstrategy) - Unified condition evaluation API\n* [Composite Strategy](https://takeshishimada.github.io/Lockman/main/documentation/lockman/compositestrategy) - Combine multiple strategies\n\n## Installation\n\nLockman can be installed using [Swift Package Manager](https://swift.org/package-manager/).\n\n### Xcode\n\nIn Xcode, select File → Add Package Dependencies and enter the following URL:\n\n```\nhttps://github.com/takeshishimada/Lockman\n```\n\n### Package.swift\n\nAdd the dependency to your Package.swift file:\n\n```swift\ndependencies: [\n  .package(url: \"https://github.com/takeshishimada/Lockman\", from: \"1.5.0\")\n]\n```\n\nAdd the dependency to your target:\n\n```swift\n.target(\n  name: \"MyApp\",\n  dependencies: [\n    .product(name: \"Lockman\", package: \"Lockman\"),\n  ]\n)\n```\n\n### Requirements\n\n| Platform | Minimum Version |\n|----------|----------------|\n| iOS      | 13.0           |\n| macOS    | 10.15          |\n| tvOS     | 13.0           |\n| watchOS  | 6.0            |\n\n### Version Compatibility\n\n| Lockman | The Composable Architecture |\n|---------|----------------------------|\n| 1.5.0   | 1.20.2                     |\n\n\u003cdetails\u003e\n\u003csummary\u003eOther versions\u003c/summary\u003e\n\n| Lockman | The Composable Architecture |\n|---------|----------------------------|\n| 1.4.0   | 1.20.2                     |\n| 1.3.2   | 1.20.2                     |\n| 1.3.1   | 1.20.2                     |\n| 1.3.0   | 1.20.2                     |\n| 1.2.0   | 1.20.2                     |\n| 1.1.0   | 1.20.2                     |\n| 1.0.0   | 1.20.2                     |\n| 0.13.4  | 1.20.2                     |\n| 0.13.3  | 1.20.2                     |\n| 0.13.2  | 1.20.2                     |\n| 0.13.1  | 1.20.2                     |\n| 0.13.0  | 1.20.2                     |\n| 0.12.0  | 1.20.1                     |\n| 0.11.0  | 1.19.1                     |\n| 0.10.0  | 1.19.0                     |\n| 0.9.0   | 1.18.0                     |\n| 0.8.0   | 1.17.1                     |\n| 0.7.0   | 1.17.1                     |\n| 0.6.0   | 1.17.1                     |\n| 0.5.0   | 1.17.1                     |\n| 0.4.0   | 1.17.1                     |\n| 0.3.0   | 1.17.1                     |\n| 0.2.1   | 1.17.1                     |\n| 0.2.0   | 1.17.1                     |\n| 0.1.0   | 1.17.1                     |\n\n\u003c/details\u003e\n\n### Translations\n\nThe following translations of this README have been contributed by members of the community:\n\n- [English](README.md)\n- [Japanese](READMEs/README_ja.md)\n- [Simplified Chinese](READMEs/README_zh-CN.md)\n- [Traditional Chinese](READMEs/README_zh-TW.md)\n- [Spanish](READMEs/README_es.md)\n- [French](READMEs/README_fr.md)\n- [German](READMEs/README_de.md)\n- [Korean](READMEs/README_ko.md)\n- [Portuguese](READMEs/README_pt-BR.md)\n- [Italian](READMEs/README_it.md)\n\n## Community\n\n### Discussion and Help\n\nQuestions and discussions can be held on [GitHub Discussions](https://github.com/takeshishimada/Lockman/discussions).\n\n### Bug Reports\n\nIf you find a bug, please report it on [Issues](https://github.com/takeshishimada/Lockman/issues).\n\n### Contributing\n\nIf you'd like to contribute to the library, please open a PR with a link to it!\n\n## License\n\nThis library is released under the MIT License. See the [LICENSE](./LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakeshishimada%2Flockman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftakeshishimada%2Flockman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakeshishimada%2Flockman/lists"}