Skip to content

Commit b6e31d0

Browse files
committed
#35: WIP: Starting AsyncLazy again
1 parent 3ffb734 commit b6e31d0

13 files changed

+301
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Initial movement from 4.x.
2323
- This should also help with SEO and intuition. `import Lazy` makes more sense when you want to import lazy behavior, compared to `import LazyContainers`
2424
- 6.x will still ship a product named `LazyContainers` for transitionary purposes, but it's deprecated and discoruaged. The `LazyContainers` product will be exactly the same as `Lazy` until it is removed in a future version.
2525

26+
- Renaming the `LazyContainer` protocol to `LazyProtocol`
27+
- Included a migration-assistant typealias to facilitate the transition
28+
2629
- Full migration to Swift Package Manager
2730
- It's now clear that SPM is the best way to distribute & use Swift packages, so this update removes CocoaPods support and no longer considers any other way of importing this package.
2831

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import PackageDescription
66
let package = Package(
77
name: "LazyContainers",
88

9+
platforms: [
10+
.macOS(.v10_15),
11+
],
12+
913
products: [
1014
.library(
1115
name: "LazyContainers",

Sources/Lazy/AnyLazy.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// AnyLazy.swift
3+
// https://github.com/RougeWare/Swift-Lazy-Containers
4+
//
5+
// Created by Ky on 2024-12-26.
6+
// Copyright waived. No rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
12+
13+
/// The protocol to which all lazy protocols & types conform
14+
public protocol AnyLazy {
15+
16+
/// The type of the value that will be lazily-initialized
17+
associatedtype Value
18+
}

Sources/Lazy/AsyncLazy.swift

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//
2+
// AsyncLazy.swift
3+
// https://github.com/RougeWare/Swift-Lazy-Containers
4+
//
5+
// Created by Ky on 2024-12-26.
6+
// Copyright waived. No rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
12+
13+
//@propertyWrapper // Property wrappers currently cannot define an 'async' or 'throws' accessor
14+
public struct AsyncLazy<Value: Sendable> {
15+
16+
private var _guts: AsyncValueReference<AsyncValueHolder>
17+
18+
19+
// init(initializer: @escaping AsyncInitializer<Value>) {
20+
// self.init(_guts: .init(wrappedValue: .unset(initializer: initializer)))
21+
// }
22+
23+
24+
/// Allows other initializers to have a shared point of initialization
25+
private init(_guts: AsyncValueReference<AsyncValueHolder>) {
26+
self._guts = _guts
27+
}
28+
29+
30+
public nonisolated var wrappedValue: Value {
31+
get async { await _guts.wrappedValue.wrappedValue }
32+
}
33+
}
34+
35+
36+
37+
extension AsyncLazy: AsyncLazyProtocol
38+
where Value: Sendable
39+
{
40+
public mutating func mutate(_ isolatedMutator: (inout Value) async -> Void) async {
41+
await _guts.mutate { asyncLazyContainerValueHolder in
42+
await asyncLazyContainerValueHolder.mutate { value in
43+
await isolatedMutator(&value)
44+
}
45+
}
46+
}
47+
48+
public func get() async -> Value {
49+
await _guts.wrappedValue.wrappedValue
50+
}
51+
52+
53+
public nonisolated func set(to newValue: sending Value) async {
54+
await _guts.set(to: .hasValue(value: newValue))
55+
}
56+
57+
58+
public static func preinitialized(_ initialValue: Value) -> Self {
59+
Self.init(_guts: .init(wrappedValue: .hasValue(value: initialValue)))
60+
}
61+
62+
63+
public var isInitialized: Bool {
64+
get async { await _guts.wrappedValue.hasValue }
65+
}
66+
67+
68+
public func initializeNow() async {
69+
await _guts.mutate { wrappedValue in
70+
await wrappedValue.initializeNow()
71+
}
72+
}
73+
}
74+
75+
76+
77+
// MARK: - ValueHolder
78+
79+
/// Takes care of keeping track of the state, value, and initializer of a lazy container, as needed
80+
//@propertyWrapper // Property wrappers currently cannot define an 'async' or 'throws' accessor
81+
public enum AsyncLazyContainerValueHolder<Value: Sendable>: Sendable {
82+
83+
/// Indicates that a value has been cached, and contains that cached value
84+
case hasValue(value: Value)
85+
86+
/// Indicates that the value has not yet been created, and contains its initializer
87+
case unset(initializer: AsyncInitializer<Value>)
88+
89+
90+
/// The value held inside this value holder.
91+
/// - Attention: Reading this value may mutate the state in order to compute the value. The complexity of that read
92+
/// operation is equal to the complexity of the initializer.
93+
public var wrappedValue: Value {
94+
mutating get async {
95+
switch self {
96+
case .hasValue(let value):
97+
return value
98+
99+
case .unset(let initializer):
100+
let value = await initializer()
101+
self = .hasValue(value: value)
102+
return value
103+
}
104+
}
105+
}
106+
107+
108+
/// Sets the value asynchronously
109+
public mutating func set(to newValue: Value) async {
110+
self = .hasValue(value: newValue)
111+
}
112+
113+
114+
/// Indicates whether this holder actually holds a value.
115+
/// This will be `true` after reading or writing `wrappedValue`.
116+
public var hasValue: Bool {
117+
switch self {
118+
case .hasValue(value: _): return true
119+
case .unset(initializer: _): return false
120+
}
121+
}
122+
123+
124+
/// Immediately initializes the value held inside this value holder
125+
///
126+
/// If this holder already contains a value, this does nothing
127+
mutating func initializeNow() async {
128+
switch self {
129+
case .hasValue(_): return
130+
case .unset(let initializer):
131+
self = await .hasValue(value: initializer())
132+
}
133+
}
134+
135+
136+
mutating func mutate(_ isolatedMutator: (inout Value) async -> Void) async {
137+
var wrappedValue = await self.wrappedValue
138+
await isolatedMutator(&wrappedValue)
139+
await self.set(to: wrappedValue)
140+
}
141+
}
142+
143+
144+
145+
146+
public extension AnyLazy {
147+
148+
/// Takes care of keeping track of the state, value, and initializer as needed
149+
typealias AsyncValueHolder = AsyncLazyContainerValueHolder<Value>
150+
}
151+

Sources/Lazy/AsyncLazyProtocol.swift

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//
2+
// AsyncLazyProtocol.swift
3+
// https://github.com/RougeWare/Swift-Lazy-Containers
4+
//
5+
// Created by Ky on 2024-12-26.
6+
// Copyright waived. No rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
12+
13+
/// Simply initializes a value
14+
public typealias AsyncInitializer<Value> = @Sendable () async -> Value
15+
16+
17+
18+
/// Defines how a lazy container should look when it performs all its operations asynchronously
19+
public protocol AsyncLazyProtocol: AnyLazy {
20+
21+
/// Gets the value, possibly initializing it first
22+
mutating func get() async -> Value
23+
24+
25+
/// Sets the value asynchronously
26+
mutating func set(to newValue: sending Value) async // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
27+
28+
29+
/// Mutate the value within the context of this container
30+
/// - Parameter isolatedMutator: Mutates the wrapped value within the proper isolation context
31+
mutating func mutate(_ isolatedMutator: (inout Value) async -> Void) async
32+
33+
34+
/// Indicates whether the value has indeed been initialized
35+
var isInitialized: Bool { get async }
36+
37+
38+
/// Immediately initializes the value held inside this lazy container
39+
///
40+
/// If this holder already contains a value, this does nothing
41+
// https://github.com/RougeWare/Swift-Lazy-Containers/issues/40
42+
mutating func initializeNow() async
43+
44+
45+
/// Creates a lazy container that already contains an initialized value.
46+
///
47+
/// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a `Lazy`),
48+
/// but require it to already hold a value up-front
49+
///
50+
/// - Parameter initialValue: The value to immediately store in the otherwise-lazy container
51+
static func preinitialized(_ initialValue: Value) -> Self
52+
}
53+
54+
55+
56+
// MARK: - ValueReference
57+
58+
/// Allows you to use reference-semantics to hold a value inside a lazy container
59+
//@propertyWrapper // Property wrappers currently cannot define an 'async' or 'throws' accessor
60+
public final actor LazyAsyncValueReference<Value: Sendable & Copyable> {
61+
62+
/// Holds some value- or reference-passed instance inside a reference-passed one
63+
public var wrappedValue: Value
64+
65+
66+
/// Creates a reference to the given value- or reference-passed instance
67+
///
68+
/// - Parameter wrappedValue: The instance to wrap
69+
public init(wrappedValue: Value) {
70+
self.wrappedValue = wrappedValue
71+
}
72+
73+
74+
func set(to newValue: Value) {
75+
wrappedValue = newValue
76+
}
77+
78+
79+
func mutate(_ isolatedMutator: @Sendable (inout Value) async -> Void) async {
80+
var wrappedValue = self.wrappedValue
81+
await isolatedMutator(&wrappedValue)
82+
self.wrappedValue = wrappedValue
83+
}
84+
}
85+
86+
87+
88+
public extension AnyLazy {
89+
90+
/// Allows you to use reference semantics to hold a value inside a lazy container.
91+
typealias AsyncValueReference = LazyAsyncValueReference
92+
}
93+

Sources/Lazy/FunctionalLazy.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Foundation
1515
/// - Attention: This is theoretically thread-safe, but hasn't undergone rigorous real-world testing. A short-lived
1616
/// semaphore was added to mitigate this, but again, it hasn't undergone rigorous real-world testing.
1717
@propertyWrapper
18-
public struct FunctionalLazy<Value>: LazyContainer {
18+
public struct FunctionalLazy<Value>: LazyProtocol {
1919

2020
/// Privatizes the inner-workings of this functional lazy container
2121
@Guts

Sources/Lazy/Lazy.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Foundation
1515
/// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use the
1616
/// language's built-in `lazy` instead wherever possible.
1717
@propertyWrapper
18-
public struct Lazy<Value>: LazyContainer {
18+
public struct Lazy<Value>: LazyProtocol {
1919

2020
/// Privatizes the inner-workings of this functional lazy container
2121
@ValueReference
@@ -142,7 +142,7 @@ public enum LazyContainerValueHolder<Value> {
142142

143143

144144

145-
public extension LazyContainer {
145+
public extension AnyLazy {
146146

147147
/// Takes care of keeping track of the state, value, and initializer as needed
148148
typealias ValueHolder = LazyContainerValueHolder<Value>

Sources/Lazy/LazyContainer + Codable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Foundation
1111

1212
// MARK: - Encodable
1313

14-
public extension LazyContainer where Self: Encodable, Value: Encodable {
14+
public extension LazyProtocol where Self: Encodable, Value: Encodable {
1515
func encode(to encoder: Encoder) throws {
1616
try wrappedValue.encode(to: encoder)
1717
}
@@ -27,7 +27,7 @@ extension FunctionalLazy: Encodable where Value: Encodable {}
2727

2828
// MARK: - Decodable
2929

30-
public extension LazyContainer where Self: Decodable, Value: Decodable {
30+
public extension LazyProtocol where Self: Decodable, Value: Decodable {
3131
init(from decoder: Decoder) throws {
3232
self = .preinitialized(try Value(from: decoder))
3333
}

Sources/Lazy/LazyContainer + Equatable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99

1010

1111

12-
public extension LazyContainer where Self: Equatable, Value: Equatable {
12+
public extension LazyProtocol where Self: Equatable, Value: Equatable {
1313
static func == (lhs: Self, rhs: Self) -> Bool {
1414
lhs.wrappedValue == rhs.wrappedValue
1515
}

Sources/Lazy/LazyContainer + Hashable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99

1010

1111

12-
public extension LazyContainer where Self: Hashable, Value: Hashable {
12+
public extension LazyProtocol where Self: Hashable, Value: Hashable {
1313
func hash(into hasher: inout Hasher) {
1414
wrappedValue.hash(into: &hasher)
1515
}

0 commit comments

Comments
 (0)