Skip to content

Commit 2a62ccc

Browse files
committed
Update to use newer Cache version
1 parent 1f13229 commit 2a62ccc

6 files changed

Lines changed: 340 additions & 45 deletions

File tree

Package.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import PackageDescription
55
let package = Package(
66
name: "AppState",
77
platforms: [
8-
.iOS(.v15),
9-
.watchOS(.v8),
10-
.macOS(.v11),
11-
.tvOS(.v15),
12-
.visionOS(.v1)
8+
.iOS(.v18),
9+
.watchOS(.v11),
10+
.macOS(.v15),
11+
.tvOS(.v18),
12+
.visionOS(.v2)
1313
],
1414
products: [
1515
.library(
@@ -18,19 +18,27 @@ let package = Package(
1818
)
1919
],
2020
dependencies: [
21-
.package(url: "https://github.com/0xLeif/Cache", from: "2.0.0"),
21+
.package(url: "https://github.com/0xLeif/Cache", branch: "leif/mutex"),
2222
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.0")
2323
],
2424
targets: [
2525
.target(
2626
name: "AppState",
2727
dependencies: [
2828
"Cache"
29+
],
30+
swiftSettings: [
31+
.swiftLanguageMode(.v6),
32+
.enableExperimentalFeature("StrictConcurrency")
2933
]
3034
),
3135
.testTarget(
3236
name: "AppStateTests",
33-
dependencies: ["AppState"]
37+
dependencies: ["AppState"],
38+
swiftSettings: [
39+
.swiftLanguageMode(.v6),
40+
.enableExperimentalFeature("StrictConcurrency")
41+
]
3442
)
3543
]
3644
)

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@ struct ContentView: View {
7272

7373
This snippet demonstrates defining a state value in an `Application` extension and using the `@AppState` property wrapper to bind it inside a view.
7474

75+
## Examples
76+
77+
Explore our comprehensive [Examples](Examples/) folder with 31 example projects:
78+
79+
| Category | Examples | Description |
80+
|----------|----------|-------------|
81+
| **Focused** | 2 | Production-quality apps (SyncNotes, MultiPlatformTracker) |
82+
| **Moderate** | 4 | Feature-focused apps (TodoCloud, SettingsKit, DataDashboard, SecureVault) |
83+
| **Lightweight** | 25 | Single-concept examples covering all AppState features |
84+
85+
### Featured Examples
86+
87+
- **[SyncNotes](Examples/Focused/SyncNotes/)** - Note-taking with iCloud sync across devices
88+
- **[MultiPlatformTracker](Examples/Focused/MultiPlatformTracker/)** - Habit tracker for iOS/macOS/watchOS
89+
- **[TodoCloud](Examples/Moderate/TodoCloud/)** - Progressive persistence (memory → local → cloud)
90+
- **[SecureVault](Examples/Moderate/SecureVault/)** - Password manager with Keychain storage
91+
92+
See [Examples/README.md](Examples/README.md) for the full list with descriptions.
93+
7594
## Documentation
7695

7796
Here’s a detailed breakdown of **AppState**'s documentation:

Sources/AppState/Application/Application+internal.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,11 @@ extension Application {
101101
)
102102
}
103103

104-
/// Returns value for the provided keyPath. This method is thread safe
104+
/// Returns value for the provided keyPath. Thread safety is provided by Cache's internal Mutex.
105105
///
106106
/// - Parameter keyPath: KeyPath of the value to be fetched
107107
func value<Value>(keyPath: KeyPath<Application, Value>) -> Value {
108-
lock.lock(); defer { lock.unlock() }
109-
110-
return self[keyPath: keyPath]
108+
self[keyPath: keyPath]
111109
}
112110

113111
/**

Sources/AppState/Application/Application.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ open class Application: NSObject {
2727
@MainActor
2828
static var isLoggingEnabled: Bool = false
2929

30-
/// A recursive lock to ensure thread-safe access to shared resources within the Application instance.
31-
let lock: NSRecursiveLock
32-
3330
/// The underlying cache used to store all state and dependency values.
3431
let cache: Cache<String, Any>
3532

@@ -51,7 +48,6 @@ open class Application: NSObject {
5148
public required init(
5249
setup: (Application) -> Void = { _ in }
5350
) {
54-
lock = NSRecursiveLock()
5551
cache = Cache()
5652

5753
super.init()

Sources/AppState/Dependencies/Keychain.swift

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#if !os(Linux) && !os(Windows)
22
import Cache
33
import Foundation
4+
import Synchronization
45

56
/**
67
A `Keychain` class that adopts the `Cacheable` protocol.
@@ -25,23 +26,19 @@ public final class Keychain: Sendable {
2526
public typealias Key = String
2627
public typealias Value = String
2728

28-
private let lock: NSLock
29-
@MainActor
30-
private var keys: Set<Key>
31-
29+
private let keys: Mutex<Set<Key>>
30+
3231
/// Default initializer
3332
public init() {
34-
self.lock = NSLock()
35-
self.keys = []
33+
self.keys = Mutex([])
3634
}
37-
35+
3836
/**
3937
Initialize with a predefined set of keys.
4038
- Parameter keys: The predefined set of keys.
4139
*/
4240
public init(keys: Set<Key>) {
43-
self.lock = NSLock()
44-
self.keys = keys
41+
self.keys = Mutex(keys)
4542
}
4643

4744
/**
@@ -57,19 +54,16 @@ public final class Keychain: Sendable {
5754
kSecReturnData: true,
5855
kSecMatchLimit: kSecMatchLimitOne
5956
]
60-
57+
6158
var dataTypeRef: AnyObject?
62-
63-
lock.lock()
6459
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
65-
lock.unlock()
66-
60+
6761
guard
6862
status == noErr,
6963
let data = dataTypeRef as? Data,
7064
let output = String(data: data, encoding: .utf8) as? Output
7165
else { return nil }
72-
66+
7367
return output
7468
}
7569

@@ -113,9 +107,7 @@ public final class Keychain: Sendable {
113107
SecItemAdd(addQuery as CFDictionary, nil)
114108
}
115109

116-
Task { @MainActor in
117-
keys.insert(key)
118-
}
110+
_ = keys.withLock { $0.insert(key) }
119111
}
120112

121113
/**
@@ -127,10 +119,9 @@ public final class Keychain: Sendable {
127119
kSecClass: kSecClassGenericPassword,
128120
kSecAttrAccount: key
129121
]
130-
131-
lock.lock()
122+
132123
SecItemDelete(query as CFDictionary)
133-
lock.unlock()
124+
_ = keys.withLock { $0.remove(key) }
134125
}
135126

136127
/**
@@ -174,21 +165,16 @@ public final class Keychain: Sendable {
174165
- Parameter ofType: The type of the values expected.
175166
- Returns: A dictionary with keys and their associated values.
176167
*/
177-
@MainActor
178168
public func values<Output>(ofType: Output.Type) -> [Key: Output] {
179-
let storedKeys: [Key]
169+
let storedKeys = keys.withLock { Array($0) }
180170
var values: [Key: Output] = [:]
181-
182-
lock.lock()
183-
storedKeys = Array(keys)
184-
lock.unlock()
185-
171+
186172
for key in storedKeys {
187173
if let value = get(key, as: Output.self) {
188174
values[key] = value
189175
}
190176
}
191-
177+
192178
return values
193179
}
194180
}
@@ -217,7 +203,6 @@ public extension Keychain {
217203
Returns all keys and their string values currently in the keychain.
218204
- Returns: A dictionary with keys and their corresponding string values.
219205
*/
220-
@MainActor
221206
func values() -> [Key: String] {
222207
values(ofType: String.self)
223208
}

0 commit comments

Comments
 (0)