-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
LazyContainers.swift
518 lines (385 loc) · 20.2 KB
/
LazyContainers.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
//
// LazyContainers.swift
// Lazy in Swift 5.2
//
// Created by Ben Leggiero on 2018-05-04.
// Version 4.0.0 (last edited 2020-08-01)
// Copyright Ben Leggiero © 2020
// https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/LICENSE.txt
//
import Foundation
// MARK: - Supporting types
/// Simply initializes a value
public typealias Initializer<Value> = () -> Value
/// Defines how a lazy container should look
public protocol LazyContainer {
/// The type of the value that will be lazily-initialized
associatedtype Value
/// Gets the value, possibly initializing it first
var wrappedValue: Value {
get
mutating set // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
/// Indicates whether the value has indeed been initialized
var isInitialized: Bool { get }
/// Creates a lazy container that already contains an initialized value.
///
/// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a `Lazy`),
/// but require it to already hold a value up-front
///
/// - Parameter initialValue: The value to immediately store in the otherwise-lazy container
static func preinitialized(_ initialValue: Value) -> Self
}
public extension LazyContainer {
/// Allows you to use reference semantics to hold a value inside a lazy container
typealias ValueReference = LazyContainerValueReference
/// Takes care of keeping track of the state, value, and initializer as needed
typealias ValueHolder = LazyContainerValueHolder
/// Takes care of keeping track of the state, value, and initializer as needed
typealias ResettableValueHolder = LazyContainerResettableValueHolder
}
/// Allows you to use reference-semantics to hold a value inside a lazy container
@propertyWrapper
public class LazyContainerValueReference<Value> {
/// Holds some value- or reference-passed instance inside a reference-passed one
public var wrappedValue: Value
/// Creates a reference to the given value- or reference-passed instance
///
/// - Parameter wrappedValue: The instance to wrap
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
/// Takes care of keeping track of the state, value, and initializer of a lazy container, as needed
@propertyWrapper
public enum LazyContainerValueHolder<Value> {
/// Indicates that a value has been cached, and contains that cached value
case hasValue(value: Value)
/// Indicates that the value has not yet been created, and contains its initializer
case unset(initializer: Initializer<Value>)
/// The value held inside this value holder.
/// - Attention: Reading this value may mutate the state in order to compute the value. The complexity of that read
/// operation is equal to the complexity of the initializer.
public var wrappedValue: Value {
mutating get {
switch self {
case .hasValue(let value):
return value
case .unset(let initializer):
let value = initializer()
self = .hasValue(value: value)
return value
}
}
set {
self = .hasValue(value: newValue)
}
}
/// Indicates whether this holder actually holds a value.
/// This will be `true` after reading or writing `wrappedValue`.
public var hasValue: Bool {
switch self {
case .hasValue(value: _): return true
case .unset(initializer: _): return false
}
}
}
/// Takes care of keeping track of the state, value, and initializer of a resettable lazy container, as needed
@propertyWrapper
public enum LazyContainerResettableValueHolder<Value> {
/// Indicates that a value has been cached, and contains that cached value, and the initializer in case the
/// value is cleared again later on
case hasValue(value: Value, initializer: Initializer<Value>)
/// Indicates that the value has not yet been created, and contains its initializer
case unset(initializer: Initializer<Value>)
/// Finds and returns the initializer held within this enum case
var initializer: Initializer<Value> {
switch self {
case .hasValue(value: _, let initializer),
.unset(let initializer):
return initializer
}
}
/// The value held inside this value holder.
/// - Attention: Reading this value may mutate the state in order to compute the value. The complexity of that read
/// operation is equal to the complexity of the initializer.
public var wrappedValue: Value {
mutating get {
switch self {
case .hasValue(let value, initializer: _):
return value
case .unset(let initializer):
let value = initializer()
self = .hasValue(value: value, initializer: initializer)
return value
}
}
set {
switch self {
case .hasValue(value: _, let initializer),
.unset(let initializer):
self = .hasValue(value: newValue, initializer: initializer)
}
}
}
/// Indicates whether this holder actually holds a value.
/// This will be `true` after reading or writing `wrappedValue`.
public var hasValue: Bool {
switch self {
case .hasValue(value: _, initializer: _): return true
case .unset(initializer: _): return false
}
}
}
// MARK: - Lazy
/// A non-resettable lazy container, to guarantee lazy behavior across language versions
///
/// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use the
/// language's built-in `lazy` instead wherever possible.
@propertyWrapper
public struct Lazy<Value>: LazyContainer {
/// Privatizes the inner-workings of this functional lazy container
@LazyContainerValueReference
private var guts: LazyContainerValueHolder<Value>
/// Allows other initializers to have a shared point of initialization
private init(_guts: LazyContainerValueReference<LazyContainerValueHolder<Value>>) {
self._guts = _guts
}
/// Creates a non-resettable lazy container with the given value-initializer. That function will be called the very
/// first time a value is needed:
///
/// 1. The first time `wrappedValue` is called, the result from `initializer` will be cached and returned
/// 2. Subsequent calls to get `wrappedValue` will return the cached value
///
/// - Parameter initializer: The closure that will be called the very first time a value is needed
public init(initializer: @escaping Initializer<Value>) {
self.init(_guts: .init(wrappedValue: .unset(initializer: initializer)))
}
/// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
///
/// - Parameter initializer: The closure that will be called the very first time a value is needed
@available(swift, // https://github.com/RougeWare/Swift-Lazy-Patterns/issues/20
introduced: 5.3,
message: """
Due to changes introduced in Swift 5.3, property wrappers can now be passed their initial value lazily, through the language assignment syntax. This initializer requires that behavior to work properly.
For Swift 5.2.x and earlier, I recommend you don't use `Lazy` as a property wrapper, since Swift 5.2.x property wrappers can't guarantee lazy evaluation.
This is a real downer for me, but at least it appears to have been a fixable bug, rather than a problem with the core feature itself.
""")
public init(wrappedValue initializer: @autoclosure @escaping Initializer<Value>) {
self.init(initializer: initializer)
}
/// Creates a `Lazy` that already contains an initialized value.
///
/// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a `Lazy`),
/// but require it to already hold a value up-front
///
/// - Parameter initialValue: The value to immediately store in this `Lazy` container
public static func preinitialized(_ initialValue: Value) -> Lazy<Value> {
self.init(_guts: .init(wrappedValue: .hasValue(value: initialValue)))
}
/// Returns the value held within this container.
/// If there is none, it is created using the initializer given when this struct was created. This process only
/// happens on the first call to `wrappedValue`; subsequent calls are guaranteed to return the cached value from
/// the first call.
public var wrappedValue: Value {
get { guts.wrappedValue }
mutating set { guts.wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
/// Indicates whether the value has indeed been initialized
public var isInitialized: Bool { _guts.wrappedValue.hasValue }
}
// MARK: Resettable lazy
/// A resettable lazy container, whose value is generated and cached only when first needed, and can be destroyed when
/// no longer needed.
///
/// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use `Lazy`
/// or the language's built-in `lazy` instead wherever possible.
@propertyWrapper
public struct ResettableLazy<Value>: LazyContainer {
/// Privatizes the inner-workings of this functional lazy container
@LazyContainerValueReference
private var guts: LazyContainerResettableValueHolder<Value>
/// Allows other initializers to have a shared point of initialization
private init(_guts: LazyContainerValueReference<LazyContainerResettableValueHolder<Value>>) {
self._guts = _guts
}
/// Creates a resettable lazy container with the given value-initializer. That function will be called every time a
/// value is needed:
///
/// 1. The first time `wrappedValue` is called, the result from `initializer` will be cached and returned
/// 2. Subsequent calls to get `wrappedValue` will return the cached value
/// 3. If `clear()` is called, the state is set back to step 1
///
/// - Parameter initializer: The closure that will be called every time a value is needed
public init(initializer: @escaping Initializer<Value>) {
self.init(_guts: .init(wrappedValue: .unset(initializer: initializer)))
}
/// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
///
/// - Parameter initializer: The closure that will be called every time a value is needed
public init(wrappedValue initializer: @autoclosure @escaping Initializer<Value>) {
self.init(initializer: initializer)
}
/// Creates a `ResettableLazy` that already contains an initialized value.
///
/// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a
/// `ResettableLazy`), but require it to already hold a value up-front
///
/// - Parameter initialValue: The value to immediately store in this `ResettableLazy` container
public static func preinitialized(_ initialValue: Value) -> ResettableLazy<Value> {
self.init(_guts: .init(wrappedValue: .hasValue(value: initialValue, initializer: { initialValue })))
}
/// Sets or returns the value held within this container.
///
/// If there is none, it is created using the initializer given when this container was created. This process
/// only happens on the first call to `wrappedValue`;
/// subsequent calls are guaranteed to return the cached value from the first call.
///
/// You may also use this to set the value manually if you wish.
/// That value will stay cached until `clear()` is called, after which calls to `wrappedValue` will return the
/// original value, re-initializing it as necessary.
public var wrappedValue: Value {
get { guts.wrappedValue }
mutating set { guts.wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
/// Indicates whether the value has indeed been initialized
public var isInitialized: Bool { _guts.wrappedValue.hasValue }
/// Resets this lazy structure back to its unset state. Next time a value is needed, it will be regenerated using
/// the initializer given by the constructor
public func clear() {
_guts.wrappedValue = .unset(initializer: _guts.wrappedValue.initializer)
}
}
// MARK: - Functional lazy
/// An idea about how to approach the lazy container by using functions instead of branches.
///
/// - Attention: This is theoretically thread-safe, but hasn't undergone rigorous real-world testing. A short-lived
/// semaphore was added to mitigate this, but again, it hasn't undergone rigorous real-world testing.
@propertyWrapper
public struct FunctionalLazy<Value>: LazyContainer {
/// Privatizes the inner-workings of this functional lazy container
@Guts
private var guts: Value
/// Allows other initializers to have a shared point of initialization
private init(_guts: Guts<Value>) {
self._guts = _guts
}
/// Creates a non-resettable lazy container (implemented with functions!) with the given value-initializer. That
/// function will be called the very first time a value is needed:
///
/// 1. The first time `wrappedValue` is called, the result from `initializer` will be cached and returned
/// 2. Subsequent calls to get `wrappedValue` will return the cached value
///
/// - Parameter initializer: The closure that will be called the very first time a value is needed
public init(initializer: @escaping Initializer<Value>) {
self.init(_guts: .init(initializer: initializer))
}
/// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
///
/// - Parameter initializer: The closure that will be called the very first time a value is needed
public init(wrappedValue initializer: @autoclosure @escaping Initializer<Value>) {
self.init(initializer: initializer)
}
/// Creates a `FunctionalLazy` that already contains an initialized value.
///
/// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a
/// `FunctionalLazy`), but require it to already hold a value up-front
///
/// - Parameter initialValue: The value to immediately store in this `FunctionalLazy` container
public static func preinitialized(_ initialValue: Value) -> FunctionalLazy<Value> {
self.init(_guts: .init(initializer: { initialValue }))
}
/// Returns the value held within this container.
/// If there is none, it is created using the initializer given when this container was created. This process
/// only happens on the first call to `wrappedValue`; subsequent calls return the cached value from the first call,
/// or any value you've set this to.
public var wrappedValue: Value {
get { guts }
mutating set { guts = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
/// Indicates whether the value has indeed been initialized
public var isInitialized: Bool { _guts.isInitialized }
/// The actual functionality of `FunctionalLazy`, separated so that the semantics work out better
@propertyWrapper
private final class Guts<Value> {
/// The closure called every time a value is needed
var initializer: Initializer<Value>
/// Guarantees that, on first-init, only one thread initializes the value. After that, this is set to `nil`
/// because subsequent threads can safely access the value without the risk of setting it again.
var semaphore: DispatchSemaphore? = .init(value: 1)
/// Creates a non-resettable lazy container's guts with the given value-initializer. That function will be
/// called the very first time a value is needed.
init(initializer: @escaping Initializer<Value>) {
self.initializer = initializer
self.initializer = {
let semaphore = self.semaphore
semaphore?.wait()
let initialValue = initializer()
self.initializer = { initialValue }
semaphore?.signal()
self.semaphore = nil
return initialValue
}
}
/// Returns the value held within this container.
/// If there is none, it is created using the initializer given when these guts were created. This process
/// only happens on the first call to `wrappedValue`; subsequent calls return the cached value from the first
/// call, or any value you've set this to.
var wrappedValue: Value {
get { initializer() }
set { initializer = { newValue } }
}
/// Indicates whether the value has indeed been initialized
public var isInitialized: Bool { nil == semaphore }
}
}
// MARK: - Migration Assistant
/// The name which was used for `LazyContainer` in version `1.x` of this API. Included for transition smoothness.
@available(*, deprecated, renamed: "LazyContainer")
public typealias LazyPattern = LazyContainer
public extension Lazy {
/// Returns the value held within this struct.
/// If there is none, it is created using the initializer given when this struct was initialized. This process only
/// happens on the first call to `value`; subsequent calls are guaranteed to return the cached value from the first
/// call.
@available(*, deprecated, renamed: "wrappedValue",
message: """
`Lazy` is now a Swift 5.1 property wrapper, which requires a `wrappedValue` field.
Since these behave identically, you should use the newer `wrappedValue` field instead.
"""
)
var value: Value {
get { wrappedValue }
mutating set { wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
}
public extension ResettableLazy {
/// Returns the value held within this struct.
/// If there is none, it is created using the initializer given when this struct was initialized. This process only
/// happens on the first call to `value`; subsequent calls return the cached value from the first call.
@available(*, deprecated, renamed: "wrappedValue",
message: """
`ResettableLazy` is now a Swift 5.1 property wrapper, which requires a `wrappedValue` field.
Since these behave identically, you should use the newer `wrappedValue` field instead.
"""
)
var value: Value {
get { wrappedValue }
mutating set { wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
}
public extension FunctionalLazy {
/// Returns the value held within this struct.
/// If there is none, it is created using the initializer given when this struct was initialized. This process only
/// happens on the first call to `value`; subsequent calls return the cached value from the first call.
@available(*, deprecated, renamed: "wrappedValue",
message: """
`FunctionalLazy` is now a Swift 5.1 property wrapper, which requires a `wrappedValue` field.
Since these behave identically, you should use the newer `wrappedValue` field instead.
"""
)
var value: Value {
get { wrappedValue }
mutating set { wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
}
}