├── .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 | [![Tested on GitHub Actions](https://github.com/RougeWare/swift-lazy-containers/actions/workflows/swift.yml/badge.svg)](https://github.com/RougeWare/swift-lazy-containers/actions/workflows/swift.yml) [![](https://www.codefactor.io/repository/github/rougeware/swift-lazy-containers/badge)](https://www.codefactor.io/repository/github/rougeware/swift-lazy-containers) 2 | 3 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FRougeWare%2FSwift-Lazy-Containers%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/RougeWare/Swift-Lazy-Containers) [![swift package manager 5.2 is supported](https://img.shields.io/badge/swift%20package%20manager-5.2-brightgreen.svg)](https://swift.org/package-manager) [![Supports macOS, iOS, tvOS, watchOS, Linux, & Windows](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FRougeWare%2FSwift-Lazy-Containers%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/RougeWare/Swift-Lazy-Containers) 4 | [![](https://img.shields.io/github/release-date/rougeware/swift-lazy-containers?label=latest%20release)](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 | --------------------------------------------------------------------------------