├── .github
├── FUNDING.yml
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE-GENERAL.txt
├── LICENSE-SPECIAL.txt
├── LICENSE.txt
├── LazyContainers.podspec
├── Package.swift
├── README.md
├── Sources
└── LazyContainers
│ ├── LazyContainer + Codable.swift
│ ├── LazyContainer + Equatable.swift
│ ├── LazyContainer + Hashable.swift
│ └── LazyContainers.swift
└── Tests
├── LazyContainersTests
├── GitHubIssue20Tests.swift
├── LazyContainer + Codable tests.swift
├── LazyContainer + Equatable tests.swift
├── LazyContainer + Hashable tests.swift
├── LazyContainersTests.swift
└── XCTestManifests.swift
└── LinuxMain.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: BenLeggiero # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: BlueHuskyStudios # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: BlueHuskyStudios # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://PayPal.me/BenLeggiero # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macOS-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Build
13 | run: swift build -v
14 | - name: Run tests
15 | run: swift test -v
16 | - name: Lint podspec
17 | run: pod lib lint
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 |
69 | \.DS_Store
70 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE-GENERAL.txt:
--------------------------------------------------------------------------------
1 | BLUE HUSKY LICENSE 1 - PUBLIC SHARE - OPUS 3
2 |
3 | 0: LAYMAN'S TERMS
4 | This section is meant to explain the purpose of this license in Layman's
5 | terms, and should thusly never be seen as legally binding. This disclaimer does
6 | not apply to further sections of this license.
7 |
8 | If you made this:
9 |
10 | 1. This is your product and you can do what you want with it
11 | a. You must make sure that if you use anyone else's stuff in it, you do so
12 | with their permission, or under Fair Use
13 | b. If you do include someone else's stuff, you can't claim you made that
14 | c. You guarantee you're providing this product for free
15 | 2. You must let others do what they want with it
16 | a. They still have to say you made it in the first place
17 | b. You have to include this license along with your product for it to count
18 | c. They have to include this license when they redistribute your product
19 | 3. This license is overridden by governmental laws
20 | 4. You're not responsible for bad things that might happen because someone
21 | used your product
22 |
23 | If you got this:
24 |
25 | 1. This is not your product, and you need to respect that
26 | a. If you see anyone else's stuff in it, that was put there with their
27 | permission, or under Fair Use
28 | b. You are guaranteed you're getting this product for free
29 | 2. You can do what you want with this
30 | a. You still have to say who made it in the first place
31 | b. You get all the freedoms in this license along with this product
32 | c. You have to include this license when you redistribute this product,
33 | unless the creator says otherwise
34 | d. You can edit this license as long as you let other editors know,
35 | especially the original author
36 | e. You must make sure that if you use anyone else's stuff in it, you do so
37 | with their permission, or under Fair Use
38 | f. If you do include someone else's stuff, you can't claim you made that
39 | 3. This license is overridden by governmental laws
40 | 4. The original creator is not responsible for bad things that might happen
41 | because you used this product
42 |
43 |
44 | 1: DECLARATION OF COPYRIGHT
45 | This Product is copyright Ben Leggiero (c) 2020 BH-1-PS
46 |
47 | Ben Leggiero may be reached at BenLeggiero@Gmail.com
48 |
49 |
50 | 2: DEFINITIONS
51 | - "License": this text.
52 | - "Product": the product with which this License was distributed, and any
53 | assets necessary for its use.
54 | - "Creator": any entity or entities who originally invented the Product,
55 | withstanding related and contributing parties.
56 | - "Licensee": any entity or entities involved in consumption of the Product.
57 | - "Licensor": any entity or entities involved in distribution of the Product
58 | and of this License. The Creator is always a Licensor, but a Licensor is not
59 | always the Creator.
60 |
61 |
62 | 3: NOTICE OF PERMISSIONS
63 | Permission is hereby granted to any entity obtaining a copy of the Product,
64 | to use, copy, modify, host, redistribute, sublicense, sell copies of, and/or
65 | otherwise handle the Product, and to permit Licensees to also do so, given the
66 | following conditions are obeyed:
67 |
68 | 1. The above copyright notice and this permission notice shall be included in
69 | all copies and/or substantial portions of the Product.
70 | 2. This License shall be blatantly provided, unmodified, along with the
71 | Product.
72 | 3. Attribution to the Creator must be blatantly stated.
73 |
74 |
75 | 4: DECLARATION OF OWNERSHIP
76 | The Creator hereby declares that everything which makes up the Product is
77 | the intellectual property solely of the Creator and of no other party,
78 | notwithstanding any intellectual property which is included under Fair Use Laws
79 | as described in § 5.4 of this License, or within the restrictions of the
80 | licenses thereof. Such intellectual property falls under § 5.1 of this License.
81 |
82 |
83 | 5: THE RIGHTS OF THE CREATOR AND FURTHER LICENSORS
84 | 1. The Creator reserves no rights to any previously-established intellectual
85 | property which is implicitly or explicitly owned by any other entity or
86 | entities.
87 | a. If any such intellectual property is contained within or bundled
88 | alongside the Product, or is otherwise distributed with or appears
89 | covered under this License, the Creator forfeits any and all claims on
90 | said property, and said property shall not be covered by this License and
91 | shall not be considered a part of the Product.
92 | 2. The Creator will not gain any profit through the creation or distribution
93 | of the Product.
94 | a. The Creator forfeits the right to request compensation for any use of the
95 | Product which adheres to the restrictions described by this License.
96 | 3. The Creator reserves the right to freely create, edit, distribute, destroy,
97 | or otherwise interact with the whole of, or any part of, the Product.
98 | 4. In the case that any entity other than the Creator claims to own
99 | intellectual property contained within the Product and requests
100 | Compensation for the use of such intellectual property, the Creator
101 | reserves the right to refuse such Compensation as allowed by the following
102 | legal documents:
103 | a. Amendment I of the United States Constitution.
104 | b. Article 19 of the Universal Declaration of Human Rights.
105 | c. 17 USC § 107 - Limitations on exclusive rights: Fair use.
106 | 5. In cases where all of the documents listed under § 5.4 of this
107 | License are not legally recognized, the Creator agrees to pay the owner of
108 | the disputed intellectual property a sum which equals the proportion of the
109 | prevalence that the disputed intellectual property maintains in the Product
110 | out of the total sum of profits garnered by the Creator for creating the
111 | Product.
112 | 6. The Creator reserves the right to waive any conditions of this License at
113 | the Creator's discretion.
114 | 7. The Licensor reserves the right to edit this License at any time under the
115 | terms of BH-1-PS.
116 | a. The Licensor agrees to increment the Opus number of this License to
117 | correspond with version changes.
118 | b. The Licensor agrees to never publish two or more versions of this License
119 | under the same Opus number.
120 | c. The Licensor agrees to notify the License's original author of any
121 | changes made to the License. If such author is not reasonably accessible,
122 | the Licensor agrees to notify any known authors which have previously
123 | contributed to the License.
124 | 8. In no way are any of the Creator's moral rights affected by this License.
125 |
126 |
127 | 6: RIGHTS AND RESTRICTIONS OF LICENSEES AND LICENSORS ("YOU")
128 | 1. You maintain the following rights and restrictions:
129 | a. You are free to copy, distribute and transmit the Product.
130 | i. For any reuse or distribution, You must make clear to others the
131 | License terms of the License.
132 | b. You are free to adapt the Product.
133 | i. If You alter, transform, or build upon the Product, You may distribute
134 | the resulting Product only under this License.
135 | ii. If you alter, transform, or build upon this License, You must do so
136 | under the terms of § 5.7 of this License.
137 | c. You are free to make indirect commercial use of the Product.
138 | i. You may not directly profit from distribution of the Product.
139 | d. You must attribute the Product in the manner specified by the Creator (but
140 | not in any way that suggests that the Creator endorses You or such use of
141 | the Product).
142 | e. The Creator and any Licensors or Licensees previous to You are not
143 | responsible for any changes made to the Product by You, or any effects
144 | arising thereof.
145 | f. You can be granted special rights by the Creator as described in § 5.6 of
146 | this License.
147 | 2. In no way are any of the following rights affected by the License:
148 | a. The fair dealing or fair use rights afforded to You, or other applicable
149 | copyright exceptions and limitations.
150 | b. Rights another entity may have, either in the Product itself or in how
151 | the Product is used, such as publicity, privacy, or repair rights.
152 |
153 |
154 | 7: ACCORDANCE WITH THE LAW
155 | The terms that make up the whole and parts of this License shall not apply
156 | where local, state, provincial, federal, national, or any other law prohibits
157 | such terms.
158 |
159 |
160 | 8: DISCLAIMER
161 | THE PRODUCT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
162 | IMPLIED. IN NO EVENT SHALL THE CREATORS, LICENSEES, OR LICENSORS BE LIABLE FOR
163 | ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT,
164 | OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE PRODUCT OR THE
165 | USE OR OTHER DEALINGS IN OR WITH THE PRODUCT.
166 |
--------------------------------------------------------------------------------
/LICENSE-SPECIAL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 Ben Leggiero
2 |
3 | Permission is hereby granted, free of charge, to entities enumerated in Section
4 | 1 of LICENSE.txt, when obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify,
7 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and
8 | to permit persons to whom the Software is furnished to do so, subject to the
9 | following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | This repository uses multiple licenses. To determine which license is
2 | appropriate, use the following guidance:
3 |
4 | 1. The following entities may use the license provided in the
5 | LICENSE-SPECIAL.txt file in this repository:
6 | a. PKWARE, Inc.
7 |
8 | 2. All other entities must use the license provided in the LICENSE-GENERAL.txt
9 | file in this repository.
10 |
--------------------------------------------------------------------------------
/LazyContainers.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "LazyContainers"
3 | s.version = "4.0.0"
4 | s.summary = "A few ways to have a lazily-initialized value in Swift 5.1"
5 | s.homepage = "https://github.com/RougeWare/Swift-Lazy-Containers.git"
6 | s.license = {:type => "multiple", :file => "LICENSE.txt" }
7 | s.author = { "Ben Leggiero" => "BenLeggiero@Gmail.com" }
8 | s.ios.deployment_target = "9.0"
9 | s.osx.deployment_target = "10.10"
10 | s.watchos.deployment_target = "2.0"
11 | s.tvos.deployment_target = "9.0"
12 | s.source = { :git => "https://github.com/RougeWare/Swift-Lazy-Containers.git", :tag => s.version.to_s }
13 | s.source_files = 'Sources/LazyContainers/**/*.swift'
14 | s.frameworks = "Foundation"
15 | s.swift_versions = ['5.1', '5.2', '5.3']
16 | end
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "LazyContainers",
8 |
9 | products: [
10 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
11 | .library(
12 | name: "LazyContainers",
13 | targets: ["LazyContainers"]),
14 |
15 | .library(
16 | name: "LazyContainers_dynamic",
17 | type: .dynamic,
18 | targets: ["LazyContainers"]),
19 | ],
20 |
21 | dependencies: [
22 | // Dependencies declare other packages that this package depends on.
23 | // .package(url: /* package url */, from: "1.0.0"),
24 | ],
25 |
26 | targets: [
27 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
28 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
29 | .target(
30 | name: "LazyContainers",
31 | dependencies: []),
32 | .testTarget(
33 | name: "LazyContainersTests",
34 | dependencies: ["LazyContainers"]),
35 | ],
36 |
37 | swiftLanguageVersions: [.v5]
38 | )
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/RougeWare/swift-lazy-containers/actions/workflows/swift.yml) [](https://www.codefactor.io/repository/github/rougeware/swift-lazy-containers)
2 |
3 | [](https://swiftpackageindex.com/RougeWare/Swift-Lazy-Containers) [](https://swift.org/package-manager) [](https://swiftpackageindex.com/RougeWare/Swift-Lazy-Containers)
4 | [](https://github.com/RougeWare/Swift-Lazy-Containers/releases/latest)
5 |
6 |
7 |
8 | # [Swift Lazy Containers](https://github.com/RougeWare/Swift-Lazy-Patterns) #
9 | A few ways to have a lazily-initialized value in Swift 5.1. Note that, if you are OK with the behavior of Swift's `lazy` keyword, you should use that. This is for [those who want very specific behaviors](https://stackoverflow.com/a/40847994/3939277):
10 |
11 | * [`Lazy`](https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/Sources/LazyContainers/LazyContainers.swift#L184-L248): A non-resettable lazy pattern, to guarantee lazy behavior across Swift language versions
12 | * [`ResettableLazy`](https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/Sources/LazyContainers/LazyContainers.swift#L252-L330): A resettable lazy pattern, whose value is generated and cached only when first needed, and can be destroyed when no longer needed.
13 | * [`FunctionalLazy`](https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/Sources/LazyContainers/LazyContainers.swift#L334-L444): An idea about how to approach the lazy pattern by using functions instead of branches.
14 |
15 |
16 |
17 | # Automatic Conformance #
18 |
19 | The built-in containers (`Lazy`, `ResettableLazy`, and `FunctionalLazy`) automatically conform to `Equatable`, `Hashable`, `Encodable`, and `Decodable` when their values conform do too! This is a passthrough conformance, simply calling the functions of the wrapped value.
20 |
21 | Keep in mind, though, that in order to do this, the value is automatically initialized and accessed!
22 |
23 |
24 |
25 | # Compatibility Notice #
26 |
27 | The entire repository structure had to be changed in order to be compatible with Swift Package Manager ([#4](https://github.com/RougeWare/Swift-Lazy-Patterns/issues/4)). Because of this, the API version changed from 2.0.0 to 3.0.0. Very little of the actual API changed along with this ([#8](https://github.com/RougeWare/Swift-Lazy-Patterns/issues/8)); it was almost entirely to service Swift Package manager.
28 |
29 | In version 2.0.0, [this readme recommended](https://github.com/RougeWare/Swift-Lazy-Patterns/commit/68fd42023fe5642dd9841ea1411027f6cbc1032f#diff-04c6e90faac2675aa89e2176d2eec7d8) that you change any reference to `./Lazy.swift` to `./LazyContainers/Sources/LazyContainers/LazyContainers.swift`. Unfortunately, that wasn't compatible with Swift Package Manager, so `./Lazy.swift` was changed to `./Sources/LazyContainers/LazyContainers.swift`. Because of this, please change any reference to `./LazyContainers/Sources/LazyContainers/LazyContainers.swift` to `./Sources/LazyContainers/LazyContainers.swift`. Sorry about that 🤷🏽
30 |
31 |
32 |
33 | # Examples #
34 |
35 | It's easy to use each of these. Simply place the appropriate one as a property wrapper where you want it.
36 |
37 |
38 | ## `Lazy` ##
39 |
40 | The simple usage of this is very straightforward:
41 |
42 | ```swift
43 |
44 | @Lazy
45 | var myLazyString = "Hello, lazy!"
46 |
47 | print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
48 | print(myLazyString) // Just returns the value "Hello, lazy!"
49 |
50 | myLazyString = "Overwritten"
51 | print(myLazyString) // Just returns the value "Overwritten"
52 | print(myLazyString) // Just returns the value "Overwritten"
53 | ```
54 |
55 | This will print:
56 |
57 | ```plain
58 | Hello, lazy!
59 | Hello, lazy!
60 | Overwritten
61 | Overwritten
62 | ```
63 |
64 | ### More complex initializer ##
65 |
66 | If you have complex initializer logic, you can pass that to the property wrapper:
67 |
68 | ```swift
69 |
70 | func makeLazyString() -> String {
71 | print("Initializer side-effect")
72 | return "Hello, lazy!"
73 | }
74 |
75 | @Lazy(initializer: makeLazyString)
76 | var myLazyString: String
77 |
78 | print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
79 | print(myLazyString) // Just returns the value "Hello, lazy!"
80 |
81 | myLazyString = "Overwritten"
82 | print(myLazyString) // Just returns the value "Overwritten"
83 | print(myLazyString) // Just returns the value "Overwritten"
84 | ```
85 |
86 | You can also use it directly (instaed of as a property wrapper):
87 |
88 | ```swift
89 | var myLazyString = Lazy() {
90 | print("Initializer side-effect")
91 | return "Hello, lazy!"
92 | }
93 |
94 | print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
95 | print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"
96 |
97 | myLazyString.wrappedValue = "Overwritten"
98 | print(myLazyString.wrappedValue) // Just returns the value "Overwritten"
99 | print(myLazyString.wrappedValue) // Just returns the value "Overwritten"
100 | ```
101 |
102 | These will both print:
103 |
104 | ```plain
105 | Initializer side-effect
106 | Hello, lazy!
107 | Hello, lazy!
108 | Overwritten
109 | Overwritten
110 | ```
111 |
112 |
113 | ## `ResettableLazy` ##
114 |
115 | The simple usage of this is very straightforward:
116 |
117 | ```swift
118 |
119 | @ResettableLazy
120 | var myLazyString = "Hello, lazy!"
121 |
122 | print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
123 | print(myLazyString) // Just returns the value "Hello, lazy!"
124 |
125 | _myLazyString.clear()
126 | print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
127 | print(myLazyString) // Just returns the value "Hello, lazy!"
128 |
129 | myLazyString = "Overwritten"
130 | print(myLazyString) // Just returns the value "Overwritten"
131 | _myLazyString.clear()
132 | print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
133 | ```
134 |
135 | This will print:
136 |
137 | ```plain
138 | Hello, lazy!
139 | Hello, lazy!
140 | Hello, lazy!
141 | Hello, lazy!
142 | Overwritten
143 | Hello, lazy!
144 | ```
145 |
146 | ### More complex initializer ##
147 |
148 | If you have complex initializer logic, you can pass that to the property wrapper:
149 |
150 | ```swift
151 |
152 | func makeLazyString() -> String {
153 | print("Initializer side-effect")
154 | return "Hello, lazy!"
155 | }
156 |
157 | @ResettableLazy(initializer: makeLazyString)
158 | var myLazyString: String
159 |
160 | print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
161 | print(myLazyString) // Just returns the value "Hello, lazy!"
162 |
163 | _myLazyString.clear()
164 | print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
165 | print(myLazyString) // Just returns the value "Hello, lazy!"
166 |
167 | myLazyString = "Overwritten"
168 | print(myLazyString) // Just returns the value "Overwritten"
169 | _myLazyString.clear()
170 | print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
171 | ```
172 |
173 | You can also use it directly (instaed of as a property wrapper):
174 |
175 | ```swift
176 | var myLazyString = ResettableLazy() {
177 | print("Initializer side-effect")
178 | return "Hello, lazy!"
179 | }
180 |
181 | print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
182 | print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"
183 |
184 | myLazyString.clear()
185 | print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
186 | print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"
187 |
188 | myLazyString.wrappedValue = "Overwritten"
189 | print(myLazyString.wrappedValue) // Just returns the value "Overwritten"
190 | _myLazyString.clear()
191 | print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
192 | ```
193 |
194 | These will both print:
195 |
196 | ```plain
197 | Initializer side-effect
198 | Hello, lazy!
199 | Hello, lazy!
200 | Initializer side-effect
201 | Hello, lazy!
202 | Hello, lazy!
203 | Overwritten
204 | Initializer side-effect
205 | Hello, lazy!
206 | ```
207 |
208 |
209 |
210 | ## `FunctionalLazy` ##
211 |
212 | This is functionally (ha!) the same as `Lazy`. The only difference is I thought it'd be fun to implement it with functions instead of enums. 🤓
213 |
--------------------------------------------------------------------------------
/Sources/LazyContainers/LazyContainer + Codable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainer + Equatable.swift
3 | //
4 | //
5 | // Created by Ky on 2022-06-03.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 |
12 | // MARK: - Encodable
13 |
14 | public extension LazyContainer where Self: Encodable, Value: Encodable {
15 | func encode(to encoder: Encoder) throws {
16 | try wrappedValue.encode(to: encoder)
17 | }
18 | }
19 |
20 |
21 |
22 | extension Lazy: Encodable where Value: Encodable {}
23 | extension ResettableLazy: Encodable where Value: Encodable {}
24 | extension FunctionalLazy: Encodable where Value: Encodable {}
25 |
26 |
27 |
28 | // MARK: - Decodable
29 |
30 | public extension LazyContainer where Self: Decodable, Value: Decodable {
31 | init(from decoder: Decoder) throws {
32 | self = .preinitialized(try Value(from: decoder))
33 | }
34 | }
35 |
36 |
37 |
38 | extension Lazy: Decodable where Value: Decodable {}
39 | extension ResettableLazy: Decodable where Value: Decodable {}
40 | extension FunctionalLazy: Decodable where Value: Decodable {}
41 |
--------------------------------------------------------------------------------
/Sources/LazyContainers/LazyContainer + Equatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainer + Equatable.swift
3 | //
4 | //
5 | // Created by Ky on 2022-06-03.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 |
12 | public extension LazyContainer where Self: Equatable, Value: Equatable {
13 | static func == (lhs: Self, rhs: Self) -> Bool {
14 | lhs.wrappedValue == rhs.wrappedValue
15 | }
16 |
17 |
18 | static func == (lhs: Self, rhs: Value) -> Bool {
19 | lhs.wrappedValue == rhs
20 | }
21 |
22 |
23 | static func == (lhs: Value, rhs: Self) -> Bool {
24 | lhs == rhs.wrappedValue
25 | }
26 | }
27 |
28 |
29 |
30 | extension Lazy: Equatable where Value: Equatable {}
31 | extension ResettableLazy: Equatable where Value: Equatable {}
32 | extension FunctionalLazy: Equatable where Value: Equatable {}
33 |
--------------------------------------------------------------------------------
/Sources/LazyContainers/LazyContainer + Hashable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainer + Hashable.swift
3 | //
4 | //
5 | // Created by Ky on 2022-06-03.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 |
12 | public extension LazyContainer where Self: Hashable, Value: Hashable {
13 | func hash(into hasher: inout Hasher) {
14 | wrappedValue.hash(into: &hasher)
15 | }
16 | }
17 |
18 |
19 |
20 | extension Lazy: Hashable where Value: Hashable {}
21 | extension ResettableLazy: Hashable where Value: Hashable {}
22 | extension FunctionalLazy: Hashable where Value: Hashable {}
23 |
--------------------------------------------------------------------------------
/Sources/LazyContainers/LazyContainers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainers.swift
3 | // Lazy in Swift 5.2
4 | //
5 | // Created by Ben Leggiero on 2018-05-04.
6 | // Version 4.0.0 (last edited 2020-08-01)
7 | // Copyright Ben Leggiero © 2020
8 | // https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/LICENSE.txt
9 | //
10 |
11 | import Foundation
12 |
13 |
14 |
15 | // MARK: - Supporting types
16 |
17 | /// Simply initializes a value
18 | public typealias Initializer = () -> Value
19 |
20 |
21 |
22 | /// Defines how a lazy container should look
23 | public protocol LazyContainer {
24 |
25 | /// The type of the value that will be lazily-initialized
26 | associatedtype Value
27 |
28 | /// Gets the value, possibly initializing it first
29 | var wrappedValue: Value {
30 | get
31 | mutating set // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
32 | }
33 |
34 |
35 | /// Indicates whether the value has indeed been initialized
36 | var isInitialized: Bool { get }
37 |
38 |
39 | /// Creates a lazy container that already contains an initialized value.
40 | ///
41 | /// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a `Lazy`),
42 | /// but require it to already hold a value up-front
43 | ///
44 | /// - Parameter initialValue: The value to immediately store in the otherwise-lazy container
45 | static func preinitialized(_ initialValue: Value) -> Self
46 | }
47 |
48 |
49 |
50 | public extension LazyContainer {
51 | /// Allows you to use reference semantics to hold a value inside a lazy container
52 | typealias ValueReference = LazyContainerValueReference
53 |
54 | /// Takes care of keeping track of the state, value, and initializer as needed
55 | typealias ValueHolder = LazyContainerValueHolder
56 |
57 | /// Takes care of keeping track of the state, value, and initializer as needed
58 | typealias ResettableValueHolder = LazyContainerResettableValueHolder
59 | }
60 |
61 |
62 |
63 | /// Allows you to use reference-semantics to hold a value inside a lazy container
64 | @propertyWrapper
65 | public class LazyContainerValueReference {
66 |
67 | /// Holds some value- or reference-passed instance inside a reference-passed one
68 | public var wrappedValue: Value
69 |
70 |
71 | /// Creates a reference to the given value- or reference-passed instance
72 | ///
73 | /// - Parameter wrappedValue: The instance to wrap
74 | public init(wrappedValue: Value) {
75 | self.wrappedValue = wrappedValue
76 | }
77 | }
78 |
79 |
80 |
81 | /// Takes care of keeping track of the state, value, and initializer of a lazy container, as needed
82 | @propertyWrapper
83 | public enum LazyContainerValueHolder {
84 |
85 | /// Indicates that a value has been cached, and contains that cached value
86 | case hasValue(value: Value)
87 |
88 | /// Indicates that the value has not yet been created, and contains its initializer
89 | case unset(initializer: Initializer)
90 |
91 |
92 | /// The value held inside this value holder.
93 | /// - Attention: Reading this value may mutate the state in order to compute the value. The complexity of that read
94 | /// operation is equal to the complexity of the initializer.
95 | public var wrappedValue: Value {
96 | mutating get {
97 | switch self {
98 | case .hasValue(let value):
99 | return value
100 |
101 | case .unset(let initializer):
102 | let value = initializer()
103 | self = .hasValue(value: value)
104 | return value
105 | }
106 | }
107 |
108 | set {
109 | self = .hasValue(value: newValue)
110 | }
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 |
125 |
126 | /// Takes care of keeping track of the state, value, and initializer of a resettable lazy container, as needed
127 | @propertyWrapper
128 | public enum LazyContainerResettableValueHolder {
129 |
130 | /// Indicates that a value has been cached, and contains that cached value, and the initializer in case the
131 | /// value is cleared again later on
132 | case hasValue(value: Value, initializer: Initializer)
133 |
134 | /// Indicates that the value has not yet been created, and contains its initializer
135 | case unset(initializer: Initializer)
136 |
137 | /// Finds and returns the initializer held within this enum case
138 | var initializer: Initializer {
139 | switch self {
140 | case .hasValue(value: _, let initializer),
141 | .unset(let initializer):
142 | return initializer
143 | }
144 | }
145 |
146 |
147 | /// The value held inside this value holder.
148 | /// - Attention: Reading this value may mutate the state in order to compute the value. The complexity of that read
149 | /// operation is equal to the complexity of the initializer.
150 | public var wrappedValue: Value {
151 | mutating get {
152 | switch self {
153 | case .hasValue(let value, initializer: _):
154 | return value
155 |
156 | case .unset(let initializer):
157 | let value = initializer()
158 | self = .hasValue(value: value, initializer: initializer)
159 | return value
160 | }
161 | }
162 |
163 | set {
164 | switch self {
165 | case .hasValue(value: _, let initializer),
166 | .unset(let initializer):
167 | self = .hasValue(value: newValue, initializer: initializer)
168 | }
169 | }
170 | }
171 |
172 |
173 | /// Indicates whether this holder actually holds a value.
174 | /// This will be `true` after reading or writing `wrappedValue`.
175 | public var hasValue: Bool {
176 | switch self {
177 | case .hasValue(value: _, initializer: _): return true
178 | case .unset(initializer: _): return false
179 | }
180 | }
181 | }
182 |
183 |
184 |
185 | // MARK: - Lazy
186 |
187 | /// A non-resettable lazy container, to guarantee lazy behavior across language versions
188 | ///
189 | /// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use the
190 | /// language's built-in `lazy` instead wherever possible.
191 | @propertyWrapper
192 | public struct Lazy: LazyContainer {
193 |
194 | /// Privatizes the inner-workings of this functional lazy container
195 | @LazyContainerValueReference
196 | private var guts: LazyContainerValueHolder
197 |
198 |
199 | /// Allows other initializers to have a shared point of initialization
200 | private init(_guts: LazyContainerValueReference>) {
201 | self._guts = _guts
202 | }
203 |
204 |
205 | /// Creates a non-resettable lazy container with the given value-initializer. That function will be called the very
206 | /// first time a value is needed:
207 | ///
208 | /// 1. The first time `wrappedValue` is called, the result from `initializer` will be cached and returned
209 | /// 2. Subsequent calls to get `wrappedValue` will return the cached value
210 | ///
211 | /// - Parameter initializer: The closure that will be called the very first time a value is needed
212 | public init(initializer: @escaping Initializer) {
213 | self.init(_guts: .init(wrappedValue: .unset(initializer: initializer)))
214 | }
215 |
216 |
217 | /// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
218 | ///
219 | /// - Parameter initializer: The closure that will be called the very first time a value is needed
220 | @available(swift, // https://github.com/RougeWare/Swift-Lazy-Patterns/issues/20
221 | introduced: 5.3,
222 | message: """
223 | 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.
224 | 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.
225 | 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.
226 | """)
227 | public init(wrappedValue initializer: @autoclosure @escaping Initializer) {
228 | self.init(initializer: initializer)
229 | }
230 |
231 |
232 | /// Creates a `Lazy` that already contains an initialized value.
233 | ///
234 | /// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a `Lazy`),
235 | /// but require it to already hold a value up-front
236 | ///
237 | /// - Parameter initialValue: The value to immediately store in this `Lazy` container
238 | public static func preinitialized(_ initialValue: Value) -> Lazy {
239 | self.init(_guts: .init(wrappedValue: .hasValue(value: initialValue)))
240 | }
241 |
242 |
243 | /// Returns the value held within this container.
244 | /// If there is none, it is created using the initializer given when this struct was created. This process only
245 | /// happens on the first call to `wrappedValue`; subsequent calls are guaranteed to return the cached value from
246 | /// the first call.
247 | public var wrappedValue: Value {
248 | get { guts.wrappedValue }
249 | mutating set { guts.wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
250 | }
251 |
252 |
253 | /// Indicates whether the value has indeed been initialized
254 | public var isInitialized: Bool { _guts.wrappedValue.hasValue }
255 | }
256 |
257 |
258 |
259 | // MARK: Resettable lazy
260 |
261 | /// A resettable lazy container, whose value is generated and cached only when first needed, and can be destroyed when
262 | /// no longer needed.
263 | ///
264 | /// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use `Lazy`
265 | /// or the language's built-in `lazy` instead wherever possible.
266 | @propertyWrapper
267 | public struct ResettableLazy: LazyContainer {
268 |
269 | /// Privatizes the inner-workings of this functional lazy container
270 | @LazyContainerValueReference
271 | private var guts: LazyContainerResettableValueHolder
272 |
273 |
274 | /// Allows other initializers to have a shared point of initialization
275 | private init(_guts: LazyContainerValueReference>) {
276 | self._guts = _guts
277 | }
278 |
279 |
280 | /// Creates a resettable lazy container with the given value-initializer. That function will be called every time a
281 | /// value is needed:
282 | ///
283 | /// 1. The first time `wrappedValue` is called, the result from `initializer` will be cached and returned
284 | /// 2. Subsequent calls to get `wrappedValue` will return the cached value
285 | /// 3. If `clear()` is called, the state is set back to step 1
286 | ///
287 | /// - Parameter initializer: The closure that will be called every time a value is needed
288 | public init(initializer: @escaping Initializer) {
289 | self.init(_guts: .init(wrappedValue: .unset(initializer: initializer)))
290 | }
291 |
292 |
293 | /// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
294 | ///
295 | /// - Parameter initializer: The closure that will be called every time a value is needed
296 | public init(wrappedValue initializer: @autoclosure @escaping Initializer) {
297 | self.init(initializer: initializer)
298 | }
299 |
300 |
301 | /// Creates a `ResettableLazy` that already contains an initialized value.
302 | ///
303 | /// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a
304 | /// `ResettableLazy`), but require it to already hold a value up-front
305 | ///
306 | /// - Parameter initialValue: The value to immediately store in this `ResettableLazy` container
307 | public static func preinitialized(_ initialValue: Value) -> ResettableLazy {
308 | self.init(_guts: .init(wrappedValue: .hasValue(value: initialValue, initializer: { initialValue })))
309 | }
310 |
311 |
312 | /// Sets or returns the value held within this container.
313 | ///
314 | /// If there is none, it is created using the initializer given when this container was created. This process
315 | /// only happens on the first call to `wrappedValue`;
316 | /// subsequent calls are guaranteed to return the cached value from the first call.
317 | ///
318 | /// You may also use this to set the value manually if you wish.
319 | /// That value will stay cached until `clear()` is called, after which calls to `wrappedValue` will return the
320 | /// original value, re-initializing it as necessary.
321 | public var wrappedValue: Value {
322 | get { guts.wrappedValue }
323 | mutating set { guts.wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
324 | }
325 |
326 |
327 | /// Indicates whether the value has indeed been initialized
328 | public var isInitialized: Bool { _guts.wrappedValue.hasValue }
329 |
330 |
331 | /// Resets this lazy structure back to its unset state. Next time a value is needed, it will be regenerated using
332 | /// the initializer given by the constructor
333 | public func clear() {
334 | _guts.wrappedValue = .unset(initializer: _guts.wrappedValue.initializer)
335 | }
336 | }
337 |
338 |
339 |
340 | // MARK: - Functional lazy
341 |
342 | /// An idea about how to approach the lazy container by using functions instead of branches.
343 | ///
344 | /// - Attention: This is theoretically thread-safe, but hasn't undergone rigorous real-world testing. A short-lived
345 | /// semaphore was added to mitigate this, but again, it hasn't undergone rigorous real-world testing.
346 | @propertyWrapper
347 | public struct FunctionalLazy: LazyContainer {
348 |
349 | /// Privatizes the inner-workings of this functional lazy container
350 | @Guts
351 | private var guts: Value
352 |
353 |
354 | /// Allows other initializers to have a shared point of initialization
355 | private init(_guts: Guts) {
356 | self._guts = _guts
357 | }
358 |
359 |
360 | /// Creates a non-resettable lazy container (implemented with functions!) with the given value-initializer. That
361 | /// function will be called the very first time a value is needed:
362 | ///
363 | /// 1. The first time `wrappedValue` is called, the result from `initializer` will be cached and returned
364 | /// 2. Subsequent calls to get `wrappedValue` will return the cached value
365 | ///
366 | /// - Parameter initializer: The closure that will be called the very first time a value is needed
367 | public init(initializer: @escaping Initializer) {
368 | self.init(_guts: .init(initializer: initializer))
369 | }
370 |
371 |
372 | /// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
373 | ///
374 | /// - Parameter initializer: The closure that will be called the very first time a value is needed
375 | public init(wrappedValue initializer: @autoclosure @escaping Initializer) {
376 | self.init(initializer: initializer)
377 | }
378 |
379 |
380 | /// Creates a `FunctionalLazy` that already contains an initialized value.
381 | ///
382 | /// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a
383 | /// `FunctionalLazy`), but require it to already hold a value up-front
384 | ///
385 | /// - Parameter initialValue: The value to immediately store in this `FunctionalLazy` container
386 | public static func preinitialized(_ initialValue: Value) -> FunctionalLazy {
387 | self.init(_guts: .init(initializer: { initialValue }))
388 | }
389 |
390 |
391 | /// Returns the value held within this container.
392 | /// If there is none, it is created using the initializer given when this container was created. This process
393 | /// only happens on the first call to `wrappedValue`; subsequent calls return the cached value from the first call,
394 | /// or any value you've set this to.
395 | public var wrappedValue: Value {
396 | get { guts }
397 | mutating set { guts = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
398 | }
399 |
400 |
401 | /// Indicates whether the value has indeed been initialized
402 | public var isInitialized: Bool { _guts.isInitialized }
403 |
404 |
405 |
406 | /// The actual functionality of `FunctionalLazy`, separated so that the semantics work out better
407 | @propertyWrapper
408 | private final class Guts {
409 |
410 | /// The closure called every time a value is needed
411 | var initializer: Initializer
412 |
413 | /// Guarantees that, on first-init, only one thread initializes the value. After that, this is set to `nil`
414 | /// because subsequent threads can safely access the value without the risk of setting it again.
415 | var semaphore: DispatchSemaphore? = .init(value: 1)
416 |
417 |
418 | /// Creates a non-resettable lazy container's guts with the given value-initializer. That function will be
419 | /// called the very first time a value is needed.
420 | init(initializer: @escaping Initializer) {
421 | self.initializer = initializer
422 | self.initializer = {
423 | let semaphore = self.semaphore
424 | semaphore?.wait()
425 |
426 | let initialValue = initializer()
427 | self.initializer = { initialValue }
428 |
429 | semaphore?.signal()
430 | self.semaphore = nil
431 |
432 | return initialValue
433 | }
434 | }
435 |
436 |
437 | /// Returns the value held within this container.
438 | /// If there is none, it is created using the initializer given when these guts were created. This process
439 | /// only happens on the first call to `wrappedValue`; subsequent calls return the cached value from the first
440 | /// call, or any value you've set this to.
441 | var wrappedValue: Value {
442 | get { initializer() }
443 | set { initializer = { newValue } }
444 | }
445 |
446 |
447 | /// Indicates whether the value has indeed been initialized
448 | public var isInitialized: Bool { nil == semaphore }
449 | }
450 | }
451 |
452 |
453 |
454 | // MARK: - Migration Assistant
455 |
456 |
457 |
458 | /// The name which was used for `LazyContainer` in version `1.x` of this API. Included for transition smoothness.
459 | @available(*, deprecated, renamed: "LazyContainer")
460 | public typealias LazyPattern = LazyContainer
461 |
462 |
463 |
464 | public extension Lazy {
465 |
466 | /// Returns the value held within this struct.
467 | /// If there is none, it is created using the initializer given when this struct was initialized. This process only
468 | /// happens on the first call to `value`; subsequent calls are guaranteed to return the cached value from the first
469 | /// call.
470 | @available(*, deprecated, renamed: "wrappedValue",
471 | message: """
472 | `Lazy` is now a Swift 5.1 property wrapper, which requires a `wrappedValue` field.
473 | Since these behave identically, you should use the newer `wrappedValue` field instead.
474 | """
475 | )
476 | var value: Value {
477 | get { wrappedValue }
478 | mutating set { wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
479 | }
480 | }
481 |
482 |
483 |
484 | public extension ResettableLazy {
485 |
486 | /// Returns the value held within this struct.
487 | /// If there is none, it is created using the initializer given when this struct was initialized. This process only
488 | /// happens on the first call to `value`; subsequent calls return the cached value from the first call.
489 | @available(*, deprecated, renamed: "wrappedValue",
490 | message: """
491 | `ResettableLazy` is now a Swift 5.1 property wrapper, which requires a `wrappedValue` field.
492 | Since these behave identically, you should use the newer `wrappedValue` field instead.
493 | """
494 | )
495 | var value: Value {
496 | get { wrappedValue }
497 | mutating set { wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
498 | }
499 | }
500 |
501 |
502 |
503 | public extension FunctionalLazy {
504 |
505 | /// Returns the value held within this struct.
506 | /// If there is none, it is created using the initializer given when this struct was initialized. This process only
507 | /// happens on the first call to `value`; subsequent calls return the cached value from the first call.
508 | @available(*, deprecated, renamed: "wrappedValue",
509 | message: """
510 | `FunctionalLazy` is now a Swift 5.1 property wrapper, which requires a `wrappedValue` field.
511 | Since these behave identically, you should use the newer `wrappedValue` field instead.
512 | """
513 | )
514 | var value: Value {
515 | get { wrappedValue }
516 | mutating set { wrappedValue = newValue } // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer
517 | }
518 | }
519 |
--------------------------------------------------------------------------------
/Tests/LazyContainersTests/GitHubIssue20Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitHubIssue20Tests.swift
3 | // LazyContainersTests
4 | //
5 | // Created by Gabe Shahbazian on 2020-07-29.
6 | //
7 |
8 | import XCTest
9 | @testable import LazyContainers
10 |
11 |
12 |
13 | #if swift(>=5.3)
14 | var shouldNotRun = false
15 |
16 | class ShouldNotInit {
17 | init() {
18 | shouldNotRun = true
19 | }
20 | }
21 |
22 |
23 |
24 | /// Guards against issue #20
25 | /// https://github.com/RougeWare/Swift-Lazy-Patterns/issues/20
26 | final class GitHubIssue20Tests: XCTestCase {
27 |
28 | @Lazy
29 | var lazyShouldNotRun = ShouldNotInit()
30 |
31 | func testLazyInitWithPropertyWrapper() {
32 | XCTAssertFalse(shouldNotRun)
33 | }
34 |
35 | static var allTests = [
36 | ("testLazyInitWithPropertyWrapper", testLazyInitWithPropertyWrapper)
37 | ]
38 | }
39 | #endif
40 |
--------------------------------------------------------------------------------
/Tests/LazyContainersTests/LazyContainer + Codable tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainer + Hashable tests.swift
3 | //
4 | //
5 | // Created by S🌟System on 2022-06-03.
6 | //
7 |
8 | import XCTest
9 |
10 | import LazyContainers
11 |
12 |
13 |
14 | final class LazyContainer_Codable_tests: XCTestCase {
15 |
16 | func testHashableConformance() {
17 |
18 | struct Test: Codable {
19 |
20 | @Lazy(initializer: { 42 })
21 | var lazyInt
22 |
23 | @FunctionalLazy(initializer: { CGFloat.pi })
24 | var lazyFloat
25 |
26 | @ResettableLazy(initializer: { "foobar" })
27 | var lazyString
28 | }
29 |
30 |
31 |
32 | let encoder = JSONEncoder()
33 | encoder.outputFormatting = .sortedKeys
34 |
35 | XCTAssertEqual(String(data: try encoder.encode(Test()), encoding: .utf8),
36 | #"{"lazyFloat":3.1415926535897931,"lazyInt":42,"lazyString":"foobar"}"#)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tests/LazyContainersTests/LazyContainer + Equatable tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainer + Equatable tests.swift
3 | //
4 | //
5 | // Created by S🌟System on 2022-06-03.
6 | //
7 |
8 | import XCTest
9 |
10 | import LazyContainers
11 |
12 |
13 |
14 | final class LazyContainer_Equatable_tests: XCTestCase {
15 |
16 | func testEquatableConformance() {
17 |
18 | struct Test: Equatable {
19 |
20 | @Lazy(initializer: { 42 })
21 | var lazyInt
22 |
23 | @FunctionalLazy(initializer: { CGFloat.pi })
24 | var lazyFloat
25 |
26 | @ResettableLazy(initializer: { "foobar" })
27 | var lazyString
28 | }
29 |
30 | XCTAssertEqual(Test(), Test())
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/LazyContainersTests/LazyContainer + Hashable tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainer + Hashable tests.swift
3 | //
4 | //
5 | // Created by S🌟System on 2022-06-03.
6 | //
7 |
8 | import XCTest
9 |
10 | import LazyContainers
11 |
12 |
13 |
14 | final class LazyContainer_Hashable_tests: XCTestCase {
15 |
16 | func testHashableConformance() {
17 |
18 | struct Test: Hashable {
19 |
20 | @Lazy(initializer: { 42 })
21 | var lazyInt
22 |
23 | @FunctionalLazy(initializer: { CGFloat.pi })
24 | var lazyFloat
25 |
26 | @ResettableLazy(initializer: { "foobar" })
27 | var lazyString
28 | }
29 |
30 | XCTAssertEqual(Test().hashValue, Test().hashValue)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/LazyContainersTests/LazyContainersTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LazyContainersTests.swift
3 | // LazyContainersTests
4 | //
5 | // Created by Ben Leggiero on 2019-08-19
6 | // Copyright Ben Leggiero © 2020
7 | // https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/LICENSE.txt
8 | //
9 |
10 |
11 |
12 | import XCTest
13 | @testable import LazyContainers
14 |
15 |
16 |
17 | var sideEffectA: String?
18 | func makeLazyA() -> String {
19 | sideEffectA = "Side effect A1"
20 | return "lAzy"
21 | }
22 |
23 | var sideEffectB: String?
24 | func makeLazyB() -> String {
25 | sideEffectB = "Side effect B"
26 | return "Lazy B (this time with side-effects)"
27 | }
28 |
29 |
30 |
31 | final class LazyContainersTests: XCTestCase {
32 |
33 | #if swift(>=5.3)
34 | @Lazy(initializer: makeLazyA)
35 | var lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect: String
36 | #endif
37 |
38 | var lazyInitTraditionally = Lazy() {
39 | sideEffectA = "Side effect A2"
40 | return "lazy B"
41 | }
42 |
43 | @ResettableLazy
44 | var resettableLazyInitWithPropertyWrapper = "lazy C"
45 |
46 | var resettableLazyInitTraditionally = ResettableLazy(wrappedValue: "lazy D")
47 |
48 | @FunctionalLazy
49 | var functionalLazyInitWithPropertyWrapper = "lazy E"
50 |
51 | var functionalLazyInitTraditionally = FunctionalLazy(wrappedValue: "lazy F")
52 |
53 |
54 | override func setUp() {
55 | sideEffectA = nil
56 | sideEffectB = nil
57 | }
58 |
59 |
60 | override func tearDown() {
61 | // Put teardown code here. This method is called after the invocation of each test method in the class.
62 | }
63 |
64 |
65 |
66 | // MARK: - `Lazy`
67 |
68 | #if swift(>=5.3)
69 | func testLazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect() {
70 | XCTAssertEqual(sideEffectA, nil)
71 | XCTAssertFalse(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
72 | XCTAssertEqual(sideEffectA, nil)
73 | XCTAssertEqual("lAzy", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
74 | XCTAssertEqual(sideEffectA, "Side effect A1")
75 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
76 | XCTAssertEqual(sideEffectA, "Side effect A1")
77 | XCTAssertEqual("lAzy", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
78 | XCTAssertEqual(sideEffectA, "Side effect A1")
79 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
80 | XCTAssertEqual(sideEffectA, "Side effect A1")
81 | XCTAssertEqual("lAzy", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
82 | XCTAssertEqual(sideEffectA, "Side effect A1")
83 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
84 | XCTAssertEqual(sideEffectA, "Side effect A1")
85 |
86 | lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect = "MAnual"
87 |
88 | XCTAssertEqual(sideEffectA, "Side effect A1")
89 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
90 | XCTAssertEqual(sideEffectA, "Side effect A1")
91 | XCTAssertEqual("MAnual", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
92 | XCTAssertEqual(sideEffectA, "Side effect A1")
93 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
94 | XCTAssertEqual(sideEffectA, "Side effect A1")
95 | XCTAssertEqual("MAnual", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
96 | XCTAssertEqual(sideEffectA, "Side effect A1")
97 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
98 | XCTAssertEqual(sideEffectA, "Side effect A1")
99 | XCTAssertEqual("MAnual", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
100 | XCTAssertEqual(sideEffectA, "Side effect A1")
101 | XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
102 | XCTAssertEqual(sideEffectA, "Side effect A1")
103 | }
104 |
105 |
106 | func testLazyInitWithPropertyWrapperAndSideEffect() {
107 |
108 | struct Test {
109 | @Lazy
110 | var lazyInitWithPropertyWrapperAndSideEffect = makeLazyB()
111 | }
112 |
113 |
114 | let test = Test()
115 |
116 | XCTAssertNil(sideEffectB, "@Lazy eagerly evaluated its initial value")
117 | XCTAssertEqual(test.lazyInitWithPropertyWrapperAndSideEffect, "Lazy B (this time with side-effects)")
118 | }
119 | #endif
120 |
121 |
122 | func testLazyInitTraditionally() {
123 | XCTAssertFalse(lazyInitTraditionally.isInitialized)
124 | XCTAssertEqual("lazy B", lazyInitTraditionally.wrappedValue)
125 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
126 | XCTAssertEqual("lazy B", lazyInitTraditionally.wrappedValue)
127 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
128 | XCTAssertEqual("lazy B", lazyInitTraditionally.wrappedValue)
129 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
130 | XCTAssertEqual("lazy B", lazyInitTraditionally.value)
131 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
132 | XCTAssertEqual("lazy B", lazyInitTraditionally.value)
133 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
134 | XCTAssertEqual("lazy B", lazyInitTraditionally.value)
135 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
136 |
137 | lazyInitTraditionally.wrappedValue = "Manual B"
138 |
139 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
140 | XCTAssertEqual("Manual B", lazyInitTraditionally.wrappedValue)
141 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
142 | XCTAssertEqual("Manual B", lazyInitTraditionally.wrappedValue)
143 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
144 | XCTAssertEqual("Manual B", lazyInitTraditionally.wrappedValue)
145 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
146 | XCTAssertEqual("Manual B", lazyInitTraditionally.value)
147 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
148 | XCTAssertEqual("Manual B", lazyInitTraditionally.value)
149 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
150 | XCTAssertEqual("Manual B", lazyInitTraditionally.value)
151 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
152 |
153 | lazyInitTraditionally.value = "Manual B2"
154 |
155 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
156 | XCTAssertEqual("Manual B2", lazyInitTraditionally.wrappedValue)
157 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
158 | XCTAssertEqual("Manual B2", lazyInitTraditionally.wrappedValue)
159 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
160 | XCTAssertEqual("Manual B2", lazyInitTraditionally.wrappedValue)
161 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
162 | XCTAssertEqual("Manual B2", lazyInitTraditionally.value)
163 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
164 | XCTAssertEqual("Manual B2", lazyInitTraditionally.value)
165 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
166 | XCTAssertEqual("Manual B2", lazyInitTraditionally.value)
167 | XCTAssertTrue(lazyInitTraditionally.isInitialized)
168 | }
169 |
170 |
171 |
172 | // MARK: - `ResettableLazy`
173 |
174 | func testResettableLazyInitWithPropertyWrapper() {
175 | XCTAssertFalse(_resettableLazyInitWithPropertyWrapper.isInitialized)
176 | XCTAssertEqual("lazy C", resettableLazyInitWithPropertyWrapper)
177 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
178 | XCTAssertEqual("lazy C", resettableLazyInitWithPropertyWrapper)
179 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
180 | XCTAssertEqual("lazy C", resettableLazyInitWithPropertyWrapper)
181 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
182 |
183 | resettableLazyInitWithPropertyWrapper = "Manual C"
184 |
185 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
186 | XCTAssertEqual("Manual C", resettableLazyInitWithPropertyWrapper)
187 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
188 | XCTAssertEqual("Manual C", resettableLazyInitWithPropertyWrapper)
189 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
190 | XCTAssertEqual("Manual C", resettableLazyInitWithPropertyWrapper)
191 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
192 |
193 | _resettableLazyInitWithPropertyWrapper.clear()
194 |
195 | XCTAssertFalse(_resettableLazyInitWithPropertyWrapper.isInitialized)
196 | XCTAssertEqual("lazy C", resettableLazyInitWithPropertyWrapper)
197 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
198 | XCTAssertEqual("lazy C", resettableLazyInitWithPropertyWrapper)
199 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
200 | XCTAssertEqual("lazy C", resettableLazyInitWithPropertyWrapper)
201 | XCTAssertTrue(_resettableLazyInitWithPropertyWrapper.isInitialized)
202 | }
203 |
204 |
205 | func testResettableLazyInitTraditionally() {
206 | XCTAssertFalse(resettableLazyInitTraditionally.isInitialized)
207 | XCTAssertEqual("lazy D", resettableLazyInitTraditionally.wrappedValue)
208 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
209 | XCTAssertEqual("lazy D", resettableLazyInitTraditionally.wrappedValue)
210 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
211 | XCTAssertEqual("lazy D", resettableLazyInitTraditionally.wrappedValue)
212 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
213 |
214 | resettableLazyInitTraditionally.wrappedValue = "Manual D"
215 |
216 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
217 | XCTAssertEqual("Manual D", resettableLazyInitTraditionally.wrappedValue)
218 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
219 | XCTAssertEqual("Manual D", resettableLazyInitTraditionally.wrappedValue)
220 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
221 | XCTAssertEqual("Manual D", resettableLazyInitTraditionally.wrappedValue)
222 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
223 |
224 | resettableLazyInitTraditionally.clear()
225 |
226 | XCTAssertFalse(resettableLazyInitTraditionally.isInitialized)
227 | XCTAssertEqual("lazy D", resettableLazyInitTraditionally.wrappedValue)
228 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
229 | XCTAssertEqual("lazy D", resettableLazyInitTraditionally.wrappedValue)
230 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
231 | XCTAssertEqual("lazy D", resettableLazyInitTraditionally.wrappedValue)
232 | XCTAssertTrue(resettableLazyInitTraditionally.isInitialized)
233 | }
234 |
235 |
236 |
237 | // MARK: - `FuctionalLazy`
238 |
239 | func testFunctionalLazyInitWithPropertyWrapper() {
240 | XCTAssertFalse(_functionalLazyInitWithPropertyWrapper.isInitialized)
241 | XCTAssertEqual("lazy E", functionalLazyInitWithPropertyWrapper)
242 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
243 | XCTAssertEqual("lazy E", functionalLazyInitWithPropertyWrapper)
244 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
245 | XCTAssertEqual("lazy E", functionalLazyInitWithPropertyWrapper)
246 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
247 |
248 | functionalLazyInitWithPropertyWrapper = "Manual E"
249 |
250 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
251 | XCTAssertEqual("Manual E", functionalLazyInitWithPropertyWrapper)
252 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
253 | XCTAssertEqual("Manual E", functionalLazyInitWithPropertyWrapper)
254 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
255 | XCTAssertEqual("Manual E", functionalLazyInitWithPropertyWrapper)
256 | XCTAssertTrue(_functionalLazyInitWithPropertyWrapper.isInitialized)
257 | }
258 |
259 |
260 | func testFunctionalLazyInitTraditionally() {
261 | XCTAssertFalse(functionalLazyInitTraditionally.isInitialized)
262 | XCTAssertEqual("lazy F", functionalLazyInitTraditionally.wrappedValue)
263 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
264 | XCTAssertEqual("lazy F", functionalLazyInitTraditionally.wrappedValue)
265 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
266 | XCTAssertEqual("lazy F", functionalLazyInitTraditionally.wrappedValue)
267 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
268 |
269 | functionalLazyInitTraditionally.wrappedValue = "Manual F"
270 |
271 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
272 | XCTAssertEqual("Manual F", functionalLazyInitTraditionally.wrappedValue)
273 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
274 | XCTAssertEqual("Manual F", functionalLazyInitTraditionally.wrappedValue)
275 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
276 | XCTAssertEqual("Manual F", functionalLazyInitTraditionally.wrappedValue)
277 | XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
278 | }
279 |
280 | #if swift(>=5.3)
281 | static let testsWhichRequireSwift5_3 = [
282 | ("testLazyInitWithPropertyWrapperWithCustomInitializerAndSideEffect", testLazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect),
283 | ("testLazyInitWithPropertyWrapperAndSideEffect", testLazyInitWithPropertyWrapperAndSideEffect),
284 | ]
285 | #endif
286 |
287 |
288 | static let testsWhichWorkBeforeSwift5_3 = [
289 | ("testLazyInitTraditionally", testLazyInitTraditionally),
290 |
291 | ("testResettableLazyInitWithPropertyWrapper", testResettableLazyInitWithPropertyWrapper),
292 | ("testResettableLazyInitTraditionally", testResettableLazyInitTraditionally),
293 |
294 | ("testFunctionalLazyInitWithPropertyWrapper", testFunctionalLazyInitWithPropertyWrapper),
295 | ("testFunctionalLazyInitTraditionally", testFunctionalLazyInitTraditionally),
296 | ]
297 |
298 |
299 | #if swift(>=5.3)
300 | static let allTests = testsWhichRequireSwift5_3 + testsWhichWorkBeforeSwift5_3
301 | #else
302 | @inline(__always)
303 | static let allTests = testsWhichWorkBeforeSwift5_3
304 | #endif
305 | }
306 |
--------------------------------------------------------------------------------
/Tests/LazyContainersTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(LazyContainersTests.allTests),
7 | testCase(GitHubIssue20Tests.allTests),
8 | ]
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import LazyContainersTests
4 |
5 | let tests: [XCTestCaseEntry] = [
6 | LazyContainersTests.allTests,
7 | GitHubIssue20Tests.allTests,
8 | ]
9 |
10 | XCTMain(tests)
11 |
--------------------------------------------------------------------------------