├── .circleci
└── config.yml
├── .gitignore
├── .idea
├── MemoryLeakTestKit.iml
├── codeStyles
│ └── Project.xml
├── misc.xml
├── modules.xml
├── runConfigurations
│ ├── MemoryLeakTestKitTests.xml
│ └── MemoryLeakTestKit_Package.xml
├── vcs.xml
└── xcode.xml
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── MemoryLeakTestKit.podspec
├── Package.swift
├── README.md
├── Sources
└── MemoryLeakTestKit
│ ├── Arrays.swift
│ ├── CircularReferencePath.swift
│ ├── IdentifiableReferencePath.swift
│ ├── IdentifiableReferencePathComponent.swift
│ ├── LeakedObject.swift
│ ├── MemoryLeakDetector.swift
│ ├── MemoryLeakReport.swift
│ ├── NotNormalizedPathComponent.swift
│ ├── ObjectTraverser.swift
│ ├── PrefixedArray.generated.swift
│ ├── PrefixedArray.swift.gyb
│ ├── PrettyPrint.swift
│ ├── Reference.swift
│ ├── ReferenceID.swift
│ ├── ReferencePath.swift
│ ├── ReferencePathComponent.swift
│ ├── ReferencePathNormalization.swift
│ ├── Types.swift
│ ├── Weak.swift
│ └── WeakOrNotReference.swift
├── Tests
├── LinuxMain.swift
└── MemoryLeakTestKitTests
│ ├── MemoryLeakDetectorTests.swift
│ ├── ReferenceIDTests.swift
│ ├── ReferencePathNormalizationTests.swift
│ └── XCTestManifests.swift
└── bin
├── gyb
├── gyb
├── gyb.py
└── gyb.pyc
└── update
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: library/swift:4.2
6 | steps:
7 | - checkout
8 | - run:
9 | name: Test
10 | command: swift test
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /MemoryLeakTestKit.framework.zip
2 | .DS_Store
3 | /.build
4 | /Packages
5 | /*.xcodeproj
6 |
7 | # Created by https://www.gitignore.io/api/swift
8 |
9 | ### Swift ###
10 | # Xcode
11 | #
12 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
13 |
14 | ## Build generated
15 | build/
16 | DerivedData/
17 |
18 | ## Various settings
19 | *.pbxuser
20 | !default.pbxuser
21 | *.mode1v3
22 | !default.mode1v3
23 | *.mode2v3
24 | !default.mode2v3
25 | *.perspectivev3
26 | !default.perspectivev3
27 | xcuserdata/
28 |
29 | ## Other
30 | *.moved-aside
31 | *.xccheckout
32 | *.xcscmblueprint
33 |
34 | ## Obj-C/Swift specific
35 | *.hmap
36 | *.ipa
37 | *.dSYM.zip
38 | *.dSYM
39 |
40 | ## Playgrounds
41 | timeline.xctimeline
42 | playground.xcworkspace
43 |
44 | # Swift Package Manager
45 | #
46 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
47 | # Packages/
48 | # Package.pins
49 | # Package.resolved
50 | .build/
51 |
52 | # CocoaPods
53 | #
54 | # We recommend against adding the Pods directory to your .gitignore. However
55 | # you should judge for yourself, the pros and cons are mentioned at:
56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
57 | #
58 | # Pods/
59 | #
60 | # Add this line if you want to avoid checking in source code from the Xcode workspace
61 | # *.xcworkspace
62 |
63 | # Carthage
64 | #
65 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
66 | # Carthage/Checkouts
67 |
68 | Carthage/Build
69 |
70 | # fastlane
71 | #
72 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
73 | # screenshots whenever they are needed.
74 | # For more information about the recommended setup visit:
75 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
76 |
77 | fastlane/report.xml
78 | fastlane/Preview.html
79 | fastlane/screenshots/**/*.png
80 | fastlane/test_output
81 |
82 | # Code Injection
83 | #
84 | # After new code Injection tools there's a generated folder /iOSInjectionProject
85 | # https://github.com/johnno1962/injectionforxcode
86 |
87 | iOSInjectionProject/
88 |
89 |
90 | # End of https://www.gitignore.io/api/swift
91 |
92 | # Created by https://www.gitignore.io/api/xcode
93 |
94 | ### Xcode ###
95 | # Xcode
96 | #
97 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
98 |
99 | ## User settings
100 | xcuserdata/
101 |
102 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
103 | *.xcscmblueprint
104 | *.xccheckout
105 |
106 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
107 | build/
108 | DerivedData/
109 | *.moved-aside
110 | *.pbxuser
111 | !default.pbxuser
112 | *.mode1v3
113 | !default.mode1v3
114 | *.mode2v3
115 | !default.mode2v3
116 | *.perspectivev3
117 | !default.perspectivev3
118 |
119 | ### Xcode Patch ###
120 | *.xcodeproj/*
121 | !*.xcodeproj/project.pbxproj
122 | !*.xcodeproj/xcshareddata/
123 | !*.xcworkspace/contents.xcworkspacedata
124 | /*.gcno
125 |
126 |
127 | # End of https://www.gitignore.io/api/xcode
128 |
129 | # Created by https://www.gitignore.io/api/appcode
130 |
131 | ### AppCode ###
132 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
133 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
134 |
135 | # User-specific stuff
136 | .idea/**/workspace.xml
137 | .idea/**/tasks.xml
138 | .idea/**/usage.statistics.xml
139 | .idea/**/dictionaries
140 | .idea/**/shelf
141 |
142 | # Generated files
143 | .idea/**/contentModel.xml
144 |
145 | # Sensitive or high-churn files
146 | .idea/**/dataSources/
147 | .idea/**/dataSources.ids
148 | .idea/**/dataSources.local.xml
149 | .idea/**/sqlDataSources.xml
150 | .idea/**/dynamic.xml
151 | .idea/**/uiDesigner.xml
152 | .idea/**/dbnavigator.xml
153 |
154 | # Gradle
155 | .idea/**/gradle.xml
156 | .idea/**/libraries
157 |
158 | # Gradle and Maven with auto-import
159 | # When using Gradle or Maven with auto-import, you should exclude module files,
160 | # since they will be recreated, and may cause churn. Uncomment if using
161 | # auto-import.
162 | # .idea/modules.xml
163 | # .idea/*.iml
164 | # .idea/modules
165 |
166 | # CMake
167 | cmake-build-*/
168 |
169 | # Mongo Explorer plugin
170 | .idea/**/mongoSettings.xml
171 |
172 | # File-based project format
173 | *.iws
174 |
175 | # IntelliJ
176 | out/
177 |
178 | # mpeltonen/sbt-idea plugin
179 | .idea_modules/
180 |
181 | # JIRA plugin
182 | atlassian-ide-plugin.xml
183 |
184 | # Cursive Clojure plugin
185 | .idea/replstate.xml
186 |
187 | # Crashlytics plugin (for Android Studio and IntelliJ)
188 | com_crashlytics_export_strings.xml
189 | crashlytics.properties
190 | crashlytics-build.properties
191 | fabric.properties
192 |
193 | # Editor-based Rest Client
194 | .idea/httpRequests
195 |
196 | # Android studio 3.1+ serialized cache file
197 | .idea/caches/build_file_checksums.ser
198 |
199 | ### AppCode Patch ###
200 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
201 |
202 | # *.iml
203 | # modules.xml
204 | # .idea/misc.xml
205 | # *.ipr
206 |
207 | # Sonarlint plugin
208 | .idea/sonarlint
209 |
210 |
211 | # End of https://www.gitignore.io/api/appcode
212 |
213 | # Created by https://www.gitignore.io/api/ruby
214 |
215 | ### Ruby ###
216 | *.gem
217 | *.rbc
218 | /.config
219 | /coverage/
220 | /InstalledFiles
221 | /pkg/
222 | /spec/reports/
223 | /spec/examples.txt
224 | /test/tmp/
225 | /test/version_tmp/
226 | /tmp/
227 |
228 | # Used by dotenv library to load environment variables.
229 | # .env
230 |
231 | ## Specific to RubyMotion:
232 | .dat*
233 | .repl_history
234 | build/
235 | *.bridgesupport
236 | build-iPhoneOS/
237 | build-iPhoneSimulator/
238 |
239 | ## Specific to RubyMotion (use of CocoaPods):
240 | #
241 | # We recommend against adding the Pods directory to your .gitignore. However
242 | # you should judge for yourself, the pros and cons are mentioned at:
243 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
244 | #
245 | # vendor/Pods/
246 |
247 | ## Documentation cache and generated files:
248 | /.yardoc/
249 | /_yardoc/
250 | /doc/
251 | /rdoc/
252 |
253 | ## Environment normalization:
254 | /.bundle/
255 | /vendor/bundle
256 | /lib/bundler/man/
257 |
258 | # for a library or gem, you might want to ignore these files since the code is
259 | # intended to run in multiple environments; otherwise, check them in:
260 | # Gemfile.lock
261 | # .ruby-version
262 | # .ruby-gemset
263 |
264 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
265 | .rvmrc
266 |
267 |
268 | # End of https://www.gitignore.io/api/ruby
269 |
--------------------------------------------------------------------------------
/.idea/MemoryLeakTestKit.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/MemoryLeakTestKitTests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/MemoryLeakTestKit_Package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/xcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6 |
7 | gem "cocoapods"
8 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.0)
5 | activesupport (4.2.10)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | atomos (0.1.3)
11 | claide (1.0.2)
12 | cocoapods (1.5.3)
13 | activesupport (>= 4.0.2, < 5)
14 | claide (>= 1.0.2, < 2.0)
15 | cocoapods-core (= 1.5.3)
16 | cocoapods-deintegrate (>= 1.0.2, < 2.0)
17 | cocoapods-downloader (>= 1.2.0, < 2.0)
18 | cocoapods-plugins (>= 1.0.0, < 2.0)
19 | cocoapods-search (>= 1.0.0, < 2.0)
20 | cocoapods-stats (>= 1.0.0, < 2.0)
21 | cocoapods-trunk (>= 1.3.0, < 2.0)
22 | cocoapods-try (>= 1.1.0, < 2.0)
23 | colored2 (~> 3.1)
24 | escape (~> 0.0.4)
25 | fourflusher (~> 2.0.1)
26 | gh_inspector (~> 1.0)
27 | molinillo (~> 0.6.5)
28 | nap (~> 1.0)
29 | ruby-macho (~> 1.1)
30 | xcodeproj (>= 1.5.7, < 2.0)
31 | cocoapods-core (1.5.3)
32 | activesupport (>= 4.0.2, < 6)
33 | fuzzy_match (~> 2.0.4)
34 | nap (~> 1.0)
35 | cocoapods-deintegrate (1.0.2)
36 | cocoapods-downloader (1.2.2)
37 | cocoapods-plugins (1.0.0)
38 | nap
39 | cocoapods-search (1.0.0)
40 | cocoapods-stats (1.0.0)
41 | cocoapods-trunk (1.3.1)
42 | nap (>= 0.8, < 2.0)
43 | netrc (~> 0.11)
44 | cocoapods-try (1.1.0)
45 | colored2 (3.1.2)
46 | concurrent-ruby (1.0.5)
47 | escape (0.0.4)
48 | fourflusher (2.0.1)
49 | fuzzy_match (2.0.4)
50 | gh_inspector (1.1.3)
51 | i18n (0.9.5)
52 | concurrent-ruby (~> 1.0)
53 | minitest (5.11.3)
54 | molinillo (0.6.6)
55 | nanaimo (0.2.6)
56 | nap (1.1.0)
57 | netrc (0.11.0)
58 | ruby-macho (1.3.1)
59 | thread_safe (0.3.6)
60 | tzinfo (1.2.5)
61 | thread_safe (~> 0.1)
62 | xcodeproj (1.7.0)
63 | CFPropertyList (>= 2.3.3, < 4.0)
64 | atomos (~> 0.1.3)
65 | claide (>= 1.0.2, < 2.0)
66 | colored2 (~> 3.1)
67 | nanaimo (~> 0.2.6)
68 |
69 | PLATFORMS
70 | ruby
71 |
72 | DEPENDENCIES
73 | cocoapods
74 |
75 | BUNDLED WITH
76 | 1.16.1
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Kuniwak
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/MemoryLeakTestKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "MemoryLeakTestKit"
3 | s.version = "0.0.1"
4 | s.summary = "A testing library to detect memory leaks for Swift."
5 | s.description = <<-DESC
6 | A testing library to detect memory leaks for Swift. This library can report many information such as leaked object's type/string representation/location/circular reference paths.
7 | DESC
8 | s.homepage = "https://github.com/Kuniwak/MemoryLeakTestKit"
9 | s.license = { :type => "MIT", :file => "LICENSE" }
10 | s.swift_version = "4.2"
11 | s.ios.deployment_target = "8.0"
12 | s.osx.deployment_target = "10.9"
13 | s.watchos.deployment_target = "2.0"
14 | s.tvos.deployment_target = "9.0"
15 | s.author = { "Kuniwak" => "orga.chem.job@gmail.com" }
16 | s.source = { :git => "https://github.com/Kuniwak/MemoryLeakTestKit.git", :tag => "#{s.version}" }
17 | s.source_files = "Sources/**/*.swift"
18 | s.exclude_files = "Sources/**/*.gyb"
19 | s.framework = "Foundation"
20 | end
21 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.2
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: "MemoryLeakTestKit",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "MemoryLeakTestKit",
12 | targets: ["MemoryLeakTestKit"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "MemoryLeakTestKit",
23 | dependencies: []),
24 | .testTarget(
25 | name: "MemoryLeakTestKitTests",
26 | dependencies: ["MemoryLeakTestKit"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MemoryLeakTestKit
2 |
3 | 
4 | 
5 | 
6 | 
7 | [](https://github.com/Kuniwak/MemoryLeakTestKit/blob/master/LICENSE)
8 |
9 | A testing library to detect memory leaks for Swift.
10 |
11 | This library is under development.
12 |
13 |
14 | ## Supported Platforms
15 |
16 | | Platform | Build Status |
17 | |:---------|:-------------|
18 | | Linux | [](https://circleci.com/gh/Kuniwak/MemoryLeakTestKit/tree/master) |
19 | | iOS | [](https://app.bitrise.io/app/457e68f44175b9c9) |
20 |
21 |
22 | ## Usage
23 |
24 | ```swift
25 | import MemoryLeakTestKit
26 |
27 |
28 | let memoryLeaks = detectLeaks {
29 | // Create a instance
30 | return target
31 | }
32 |
33 | XCTAssertTrue(
34 | memoryLeaks.leakedObjects.isEmpty,
35 | memoryLeaks.prettyDescription
36 | )
37 | ```
38 |
39 |
40 | ## Example output
41 |
42 | ```
43 | Summary:
44 | Found 2 leaked objects
45 |
46 | Leaked objects:
47 | 0:
48 | Description: Node
49 | Type: Node
50 | Location: (root).linkedNodes[0]
51 | Circular Paths:
52 | self.linkedNodes[1] === self
53 |
54 | 1:
55 | Description: Node
56 | Type: Node
57 | Location: (root)
58 | Circular Paths:
59 | self.linkedNodes[0].linkedNodes[0] === self
60 | ```
61 |
62 |
63 | # License
64 |
65 | MIT
66 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/Arrays.swift:
--------------------------------------------------------------------------------
1 | public func intersperse(
2 | _ xs: CollectionT,
3 | _ y: T
4 | ) -> [T] where CollectionT.Element == T {
5 | guard let first = xs.first else {
6 | return []
7 | }
8 |
9 | var result = [first]
10 |
11 | xs.dropFirst().forEach { x in
12 | result.append(y)
13 | result.append(x)
14 | }
15 |
16 | return result
17 | }
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/CircularReferencePath.swift:
--------------------------------------------------------------------------------
1 | public struct CircularReferencePath: Hashable {
2 | public let end: CircularPathEnd
3 | public let components: ArrayLongerThan1
4 |
5 |
6 | public var description: String {
7 | let accessors = self.components
8 | .map { $0.description }
9 | .joined(separator: "")
10 |
11 | return "self\(accessors) === self"
12 | }
13 |
14 |
15 | public init(end: CircularPathEnd, components: ArrayLongerThan1) {
16 | self.end = end
17 | self.components = components
18 | }
19 |
20 |
21 | public static func from(rootTypeName: TypeName, identifiablePath: IdentifiableReferencePath) -> Set {
22 | guard let idComponents = ArrayLongerThan1(identifiablePath.idComponents) else {
23 | return []
24 | }
25 |
26 | let lastIdComponent = idComponents.last
27 |
28 | var result = Set()
29 | let idComponentsCount = idComponents.count
30 |
31 | if lastIdComponent.isIdentified(by: identifiablePath.rootID) {
32 | result.insert(CircularReferencePath(
33 | end: .root(rootTypeName),
34 | components: ReferencePathNormalization.normalize(idComponents.map { $0.noNormalizedComponent })
35 | ))
36 | }
37 |
38 | result.formUnion(
39 | Set(idComponents
40 | .enumerated()
41 | .filter { indexAndIdComponent in
42 | let (_, idComponent) = indexAndIdComponent
43 | return idComponent == lastIdComponent
44 | }
45 | .compactMap { indexAndIdComponent -> ArrayLongerThan1? in
46 | let (circularStartIndex, _) = indexAndIdComponent
47 | let circularNextIndex = circularStartIndex + 1
48 | let circularIdComponents = idComponents[circularNextIndex..(circularIdComponents)
50 | }
51 | .map { circularComponents -> CircularReferencePath in
52 | return CircularReferencePath(
53 | end: .intermediate(lastIdComponent.typeName),
54 | components: ReferencePathNormalization.normalize(circularComponents.map { $0.noNormalizedComponent })
55 | )
56 | }
57 | )
58 | )
59 |
60 | return result
61 | }
62 | }
63 |
64 |
65 |
66 | public enum CircularPathEnd: Hashable {
67 | case root(TypeName)
68 | case intermediate(TypeName)
69 | }
70 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/IdentifiableReferencePath.swift:
--------------------------------------------------------------------------------
1 | public struct IdentifiableReferencePath: Hashable {
2 | public let rootID: ReferenceID
3 | public let idComponents: [IdentifiableReferencePathComponent]
4 |
5 |
6 | public var isRoot: Bool {
7 | return self.idComponents.isEmpty
8 | }
9 |
10 |
11 | public init(rootID: ReferenceID, idComponents: [IdentifiableReferencePathComponent]) {
12 | self.rootID = rootID
13 | self.idComponents = idComponents
14 | }
15 |
16 |
17 | public init(
18 | root: Any, componentAndValuePairs: Pairs
19 | ) where Pairs.Element == (component: NotNormalizedReferencePathComponent, value: Any) {
20 | self.init(
21 | rootID: ReferenceID(of: root),
22 | idComponents: componentAndValuePairs.map { pair in
23 | let (component: component, value: value) = pair
24 | return IdentifiableReferencePathComponent(
25 | id: ReferenceID(of: value),
26 | typeName: TypeName(of: value),
27 | noNormalizedComponent: component
28 | )
29 | }
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/IdentifiableReferencePathComponent.swift:
--------------------------------------------------------------------------------
1 | public struct IdentifiableReferencePathComponent: Hashable {
2 | public let noNormalizedComponent: NotNormalizedReferencePathComponent
3 | public let typeName: TypeName
4 | private let id: ReferenceID
5 |
6 |
7 | public var hashValue: Int {
8 | return self.id.hashValue
9 | }
10 |
11 |
12 | public init(id: ReferenceID, typeName: TypeName, noNormalizedComponent: NotNormalizedReferencePathComponent) {
13 | self.id = id
14 | self.typeName = typeName
15 | self.noNormalizedComponent = noNormalizedComponent
16 | }
17 |
18 |
19 | public func isIdentified(by id: ReferenceID) -> Bool {
20 | return self.id == id
21 | }
22 |
23 |
24 | public static func ==(lhs: IdentifiableReferencePathComponent, rhs: IdentifiableReferencePathComponent) -> Bool {
25 | return lhs.id == rhs.id
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/LeakedObject.swift:
--------------------------------------------------------------------------------
1 | public struct LeakedObject: Hashable {
2 | public let objectDescription: String
3 | public let typeName: TypeName
4 | public let location: ReferencePath
5 | public let circularPaths: Set
6 |
7 |
8 | public init(
9 | objectDescription: String,
10 | typeName: TypeName,
11 | location: ReferencePath,
12 | circularPaths: Set
13 | ) {
14 | self.objectDescription = objectDescription
15 | self.typeName = typeName
16 | self.location = location
17 | self.circularPaths = circularPaths
18 | }
19 |
20 |
21 | public init?(reference: Reference) {
22 | guard !reference.isReleased else {
23 | return nil
24 | }
25 |
26 | self.init(
27 | objectDescription: reference.destinationObjectDescription,
28 | typeName: reference.destinationTypeName,
29 | location: ReferencePath(identifiablePath: reference.foundLocations.first),
30 | circularPaths: Set(reference.foundLocations.flatMap { identifiablePath in
31 | return CircularReferencePath.from(
32 | rootTypeName: reference.destinationTypeName,
33 | identifiablePath: identifiablePath
34 | )
35 | })
36 | )
37 | }
38 | }
39 |
40 |
41 |
42 | extension LeakedObject: PrettyPrintable {
43 | public var descriptionLines: [IndentedLine] {
44 | let circularPathsDescription: [IndentedLine]
45 |
46 | if self.circularPaths.isEmpty {
47 | circularPathsDescription = lines(["No circular references found. There are 2 possible reasons:"])
48 | + indent(lines([
49 | "1. Some outer instances own it",
50 | "2. Anonymous instances that are on circular references end own it",
51 | ]))
52 | }
53 | else {
54 | circularPathsDescription = indent(lines(self.circularPaths.map { $0.description }))
55 | }
56 |
57 | return descriptionList([
58 | (label: "Description", description: self.objectDescription),
59 | (label: "Type", description: self.typeName.text),
60 | (label: "Location", description: self.location.description),
61 | (label: "Circular Paths", description: "")
62 | ]) + circularPathsDescription
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/MemoryLeakDetector.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 |
5 | public func detectLeaks(by build: () -> T) -> MemoryLeakReport {
6 | let releasedWeakMap = createWeakMap(from: build())
7 |
8 | return MemoryLeakReport(references: releasedWeakMap.values)
9 | }
10 |
11 |
12 |
13 | public func detectLeaks(by build: (@escaping (T) -> Void) -> Void, _ callback: @escaping (MemoryLeakReport) -> Void) {
14 | build { target in
15 | let releasedWeakMap = createWeakMap(from: target)
16 |
17 | callback(MemoryLeakReport(references: releasedWeakMap.values))
18 | }
19 | }
20 |
21 |
22 |
23 | public func createWeakMap(from target: T) -> [ReferenceID: Reference] {
24 | var result = [
25 | ReferenceID(of: target): Reference(
26 | target,
27 | foundLocations: ArrayLongerThan1(
28 | prefix: IdentifiableReferencePath(root: target, componentAndValuePairs: []), []
29 | )
30 | )
31 | ]
32 |
33 | traverseObjectWithPath(
34 | target,
35 | onEnter: { (_, value, path) in
36 | let childReferenceID = ReferenceID(of: value)
37 |
38 | if let visitedReference = result[childReferenceID] {
39 | visitedReference.found(location: .init(
40 | root: target,
41 | componentAndValuePairs: path
42 | ))
43 | }
44 | else {
45 | result[childReferenceID] = Reference(
46 | value,
47 | foundLocations: ArrayLongerThan1(
48 | prefix: IdentifiableReferencePath(
49 | root: target,
50 | componentAndValuePairs: path
51 | ),
52 | []
53 | )
54 | )
55 | }
56 | },
57 | onLeave: nil
58 | )
59 |
60 | return result
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/MemoryLeakReport.swift:
--------------------------------------------------------------------------------
1 | public struct MemoryLeakReport: Hashable {
2 | public let leakedObjects: Set
3 |
4 |
5 | public init(leakedObjects: Set) {
6 | self.leakedObjects = leakedObjects
7 | }
8 |
9 |
10 | public init(references: Seq) where Seq.Element == Reference {
11 | let leakedObjects = Set(references.compactMap { LeakedObject(reference: $0) })
12 | self.init(leakedObjects: leakedObjects)
13 | }
14 | }
15 |
16 |
17 |
18 | extension MemoryLeakReport: PrettyPrintable {
19 | public var descriptionLines: [IndentedLine] {
20 | let leakedObjectsPart = sections(self.leakedObjects.map { $0.descriptionLines })
21 |
22 | return sections([
23 | (name: "Summary", body: lines([
24 | "Found \(self.leakedObjects.count) leaked objects",
25 | ])),
26 | (name: "Leaked objects", body: leakedObjectsPart),
27 | ])
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/NotNormalizedPathComponent.swift:
--------------------------------------------------------------------------------
1 | public enum NotNormalizedReferencePathComponent: Hashable {
2 | case label(String)
3 | case index(Int)
4 | case noLabel
5 |
6 |
7 | public init(
8 | isCollection: Bool,
9 | index: Int,
10 | label: String?
11 | ) {
12 | guard !isCollection else {
13 | self = .index(index)
14 | return
15 | }
16 |
17 | guard let label = label else {
18 | self = .noLabel
19 | return
20 | }
21 |
22 | self = .label(label)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/ObjectTraverser.swift:
--------------------------------------------------------------------------------
1 | public func traverseObjectWithPath(
2 | _ target: Any,
3 | onEnter: ((NotNormalizedReferencePathComponent, Any, [(component: NotNormalizedReferencePathComponent, value: Any)]) -> Void)?,
4 | onLeave: ((NotNormalizedReferencePathComponent, Any, [(component: NotNormalizedReferencePathComponent, value: Any)]) -> Void)?
5 | ) {
6 | var currentPath: [(component: NotNormalizedReferencePathComponent, value: Any)] = []
7 |
8 | traverseObject(
9 | target,
10 | onEnter: { (component, value) in
11 | currentPath.append((component: component, value: value))
12 | onEnter?(component, value, currentPath)
13 | },
14 | onLeave: { (component, value) in
15 | onLeave?(component, value, currentPath)
16 | currentPath.removeLast()
17 | }
18 | )
19 | }
20 |
21 |
22 |
23 | public func traverseObject(
24 | _ target: Any,
25 | onEnter: ((NotNormalizedReferencePathComponent, Any) -> Void)?,
26 | onLeave: ((NotNormalizedReferencePathComponent, Any) -> Void)?
27 | ) {
28 | var footprint = Set()
29 |
30 | traverseObjectRecursive(
31 | target,
32 | footprint: &footprint,
33 | onEnter: onEnter,
34 | onLeave: onLeave
35 | )
36 | }
37 |
38 |
39 |
40 | private func traverseObjectRecursive(
41 | _ target: Any,
42 | footprint: inout Set,
43 | onEnter: ((NotNormalizedReferencePathComponent, Any) -> Void)?,
44 | onLeave: ((NotNormalizedReferencePathComponent, Any) -> Void)?
45 | ) {
46 | // NOTE: Avoid infinite recursions caused by circular references.
47 | let id = ReferenceID(of: target)
48 | if !footprint.contains(id) {
49 | footprint.insert(id)
50 |
51 | let mirror = Mirror(reflecting: target)
52 | mirror.children.enumerated().forEach { indexAndChild in
53 | let (index, (label: label, value: value)) = indexAndChild
54 |
55 | let component = NotNormalizedReferencePathComponent(
56 | isCollection: mirror.displayStyle == .collection,
57 | index: index,
58 | label: label
59 | )
60 |
61 | onEnter?(component, value)
62 |
63 | traverseObjectRecursive(
64 | value,
65 | footprint: &footprint,
66 | onEnter: onEnter,
67 | onLeave: onLeave
68 | )
69 |
70 | onLeave?(component, value)
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/PrefixedArray.generated.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | public struct PrefixedArray {
4 | public let prefix: Element
5 | public let rest: RestElements
6 |
7 |
8 | public var first: Element {
9 | return self.prefix
10 | }
11 |
12 |
13 | public var firstAndRest: (Element, RestElements) {
14 | return (self.first, self.rest)
15 | }
16 |
17 |
18 | public init(prefix: Element, _ rest: RestElements) {
19 | self.prefix = prefix
20 | self.rest = rest
21 | }
22 |
23 |
24 | public func dropFirst() -> RestElements {
25 | return self.rest
26 | }
27 | }
28 |
29 |
30 |
31 | extension PrefixedArray: Equatable where Element: Equatable, RestElements: Equatable {
32 | public static func ==(lhs: PrefixedArray, rhs: PrefixedArray) -> Bool {
33 | return lhs.prefix == rhs.prefix
34 | && lhs.rest == rhs.rest
35 | }
36 | }
37 |
38 |
39 |
40 | extension PrefixedArray: Hashable where Element: Hashable, RestElements: Hashable {
41 | public func hash(into hasher: inout Hasher) {
42 | hasher.combine(self.prefix)
43 | hasher.combine(self.rest)
44 | }
45 | }
46 |
47 |
48 |
49 | extension PrefixedArray where RestElements == ArrayLongerThan0 {
50 | public func sequence() -> ArrayLongerThan1Sequence {
51 | return ArrayLongerThan1Sequence(
52 | prefix: self.prefix,
53 | rest: self.rest.sequence()
54 | )
55 | }
56 | }
57 | extension PrefixedArray where RestElements == ArrayLongerThan1 {
58 | public func sequence() -> ArrayLongerThan2Sequence {
59 | return ArrayLongerThan2Sequence(
60 | prefix: self.prefix,
61 | rest: self.rest.sequence()
62 | )
63 | }
64 | }
65 |
66 |
67 |
68 | public typealias ArrayLongerThan0Sequence = ArrayLongerThan0
69 | public typealias ArrayLongerThan1Sequence = PrefixedArraySequence>
70 | public typealias ArrayLongerThan2Sequence = PrefixedArraySequence>
71 |
72 |
73 |
74 | public struct PrefixedArraySequence: Sequence where RestElements.Element == E {
75 | public typealias Element = E
76 |
77 |
78 | public let prefix: Element
79 | public let rest: RestElements
80 |
81 |
82 | public init(prefix: Element, rest: RestElements) {
83 | self.prefix = prefix
84 | self.rest = rest
85 | }
86 |
87 |
88 | public func makeIterator() -> PrefixedArrayIterator {
89 | return PrefixedArrayIterator(iterate: self)
90 | }
91 | }
92 |
93 |
94 |
95 | public class PrefixedArrayIterator: IteratorProtocol {
96 | public typealias Element = RestElements.Element
97 |
98 |
99 | private var nextIterator: AnyIterator? = nil
100 | private let array: PrefixedArraySequence
101 |
102 |
103 | public init(iterate array: PrefixedArraySequence) {
104 | self.array = array
105 | }
106 |
107 |
108 | public func next() -> Element? {
109 | guard let nextIterator = self.nextIterator else {
110 | self.nextIterator = AnyIterator(self.array.rest.makeIterator())
111 | return self.array.prefix
112 | }
113 |
114 | let result = nextIterator.next()
115 | self.nextIterator = nextIterator
116 | return result
117 | }
118 | }
119 |
120 |
121 |
122 | public struct PrefixedArrayEnd {
123 | private let array: AnyBidirectionalCollection
124 |
125 |
126 | public var startIndex: Int {
127 | return 0
128 | }
129 |
130 |
131 | public var endIndex: Int {
132 | return self.count - 1
133 | }
134 |
135 |
136 | public var count: Int {
137 | return self.array.count
138 | }
139 |
140 |
141 | public var first: Element? {
142 | return self.array.first
143 | }
144 |
145 |
146 | public var last: Element? {
147 | return self.array.last
148 | }
149 |
150 |
151 | public var firstAndRest: (Element, PrefixedArrayEnd)? {
152 | guard let first = self.first, let rest = self.dropFirst() else {
153 | return nil
154 | }
155 |
156 | return (first, rest)
157 | }
158 |
159 |
160 | public init(
161 | _ array: S
162 | ) where S.Element == Element {
163 | self.array = AnyBidirectionalCollection(array)
164 | }
165 |
166 |
167 | public init(
168 | prefix: Element,
169 | _ array: S
170 | ) where S.Element == Element {
171 | var newArray = Array(array)
172 | newArray.insert(prefix, at: 0)
173 | self.init(newArray)
174 | }
175 |
176 |
177 | public init(
178 | suffix: Element,
179 | _ array: S
180 | ) where S.Element == Element {
181 | guard let first = array.first else {
182 | self.init(prefix: suffix, [])
183 | return
184 | }
185 |
186 | var newArray = Array(array.dropFirst())
187 | newArray.append(suffix)
188 |
189 | self.init(prefix: first, newArray)
190 | }
191 |
192 |
193 | public init(_ array: ArrayLongerThan0) {
194 | self = array
195 | }
196 |
197 |
198 | public init(_ array: ArrayLongerThan1) {
199 | self.init(array.relaxed())
200 | }
201 | public init(_ array: ArrayLongerThan2) {
202 | self.init(array.relaxed())
203 | }
204 |
205 |
206 | public init(
207 | suffix: Element,
208 | _ array: ArrayLongerThan0
209 | ) {
210 | self.init(suffix: suffix, array.relaxed())
211 | }
212 |
213 |
214 | public subscript(index: Int) -> Element {
215 | let index = self.array.index(self.array.startIndex, offsetBy: index)
216 | return self.array[index]
217 | }
218 |
219 |
220 | public subscript(range: Range) -> ArrayLongerThan0 {
221 | let upperBound = self.array.index(self.array.startIndex, offsetBy: range.upperBound)
222 | let lowerBound = self.array.index(self.array.startIndex, offsetBy: range.lowerBound)
223 |
224 | return ArrayLongerThan0(self.array[lowerBound..(newArray)
232 | }
233 |
234 |
235 | public mutating func insert(contentsOf newElements: C, at i: Int) where C.Element == Element {
236 | var newArray = Array(self.array)
237 | newArray.insert(contentsOf: newElements, at: i)
238 | self = PrefixedArrayEnd(newArray)
239 | }
240 |
241 |
242 | public mutating func insert(contentsOf newElements: ArrayLongerThan0, at i: Int) {
243 | self.insert(contentsOf: newElements.relaxed(), at: i)
244 | }
245 | public mutating func insert(contentsOf newElements: ArrayLongerThan1, at i: Int) {
246 | self.insert(contentsOf: newElements.relaxed(), at: i)
247 | }
248 | public mutating func insert(contentsOf newElements: ArrayLongerThan2, at i: Int) {
249 | self.insert(contentsOf: newElements.relaxed(), at: i)
250 | }
251 |
252 |
253 | public mutating func append(_ newElement: Element) {
254 | var newArray = Array(self.array)
255 | newArray.append(newElement)
256 | self = PrefixedArrayEnd(newArray)
257 | }
258 |
259 |
260 | public mutating func append(
261 | contentsOf newElements: S
262 | ) where S.Element == Element {
263 | var newArray = Array(self.array)
264 | newArray.append(contentsOf: newElements)
265 | self = PrefixedArrayEnd(newArray)
266 | }
267 |
268 |
269 | public mutating func append(contentsOf newElements: ArrayLongerThan0) {
270 | self.append(contentsOf: newElements.relaxed())
271 | }
272 | public mutating func append(contentsOf newElements: ArrayLongerThan1) {
273 | self.append(contentsOf: newElements.relaxed())
274 | }
275 | public mutating func append(contentsOf newElements: ArrayLongerThan2) {
276 | self.append(contentsOf: newElements.relaxed())
277 | }
278 |
279 |
280 | public func dropFirst() -> PrefixedArrayEnd? {
281 | guard !self.array.isEmpty else {
282 | return nil
283 | }
284 |
285 | return PrefixedArrayEnd(self.array.dropFirst())
286 | }
287 |
288 |
289 | public func dropLast() -> AnyBidirectionalCollection? {
290 | guard !self.array.isEmpty else {
291 | return nil
292 | }
293 |
294 | return self.array.dropLast()
295 | }
296 |
297 |
298 | public func map(_ f: (Element) throws -> T) rethrows -> PrefixedArrayEnd {
299 | return PrefixedArrayEnd(try self.array.map(f))
300 | }
301 |
302 |
303 | public func compactMap(_ f: (Element) throws -> T?) rethrows -> PrefixedArrayEnd {
304 | return PrefixedArrayEnd(try self.array.compactMap(f))
305 | }
306 |
307 |
308 | public func flatMap(_ f: (Element) throws -> S) rethrows -> PrefixedArrayEnd where S.Element == T {
309 | return PrefixedArrayEnd(try self.array.flatMap(f))
310 | }
311 |
312 |
313 | public func enumerated() -> PrefixedArrayEnd<(Int, Element)> {
314 | return PrefixedArrayEnd<(Int, Element)>(Array(self.array.enumerated()))
315 | }
316 |
317 |
318 | public func flatMap(_ f: (Element) throws -> ArrayLongerThan0) rethrows -> PrefixedArrayEnd {
319 | return try self.flatMap { try f($0).relaxed() }
320 | }
321 | public func flatMap(_ f: (Element) throws -> ArrayLongerThan1) rethrows -> PrefixedArrayEnd {
322 | return try self.flatMap { try f($0).relaxed() }
323 | }
324 | public func flatMap(_ f: (Element) throws -> ArrayLongerThan2) rethrows -> PrefixedArrayEnd {
325 | return try self.flatMap { try f($0).relaxed() }
326 | }
327 |
328 |
329 | public func filter(_ f: (Element) throws -> Bool) rethrows -> PrefixedArrayEnd {
330 | return PrefixedArrayEnd(try self.array.filter(f))
331 | }
332 |
333 |
334 | public func relaxed() -> AnyBidirectionalCollection {
335 | return self.array
336 | }
337 |
338 |
339 | public func sequence() -> ArrayLongerThan0Sequence {
340 | return self
341 | }
342 | }
343 |
344 |
345 |
346 | extension PrefixedArrayEnd: Equatable where Element: Equatable {
347 | public static func ==(lhs: PrefixedArrayEnd, rhs: PrefixedArrayEnd) -> Bool {
348 | guard lhs.count == rhs.count else { return false }
349 | return zip(lhs.array, rhs.array).allSatisfy { $0.0 == $0.1 }
350 | }
351 | }
352 |
353 |
354 |
355 | extension PrefixedArrayEnd: Hashable where Element: Hashable {
356 | public func hash(into hasher: inout Hasher) {
357 | self.array.forEach { hasher.combine($0) }
358 | }
359 | }
360 |
361 |
362 |
363 | extension PrefixedArrayEnd: Sequence {
364 | public typealias Iterator = PrefixedArrayEndIterator
365 |
366 |
367 | public func makeIterator() -> PrefixedArrayEndIterator {
368 | return Iterator(self)
369 | }
370 | }
371 |
372 |
373 |
374 | public struct PrefixedArrayEndIterator: IteratorProtocol {
375 | private let array: PrefixedArrayEnd
376 | private var position: Int
377 |
378 |
379 | public init(_ array: PrefixedArrayEnd) {
380 | self.init(array, at: array.startIndex)
381 | }
382 |
383 |
384 | public init(_ array: PrefixedArrayEnd, at position: Int) {
385 | self.array = array
386 | self.position = position
387 | }
388 |
389 |
390 | public mutating func next() -> Element? {
391 | guard self.position <= self.array.endIndex else {
392 | return nil
393 | }
394 |
395 | let result = self.array[self.position]
396 | self.position += 1
397 | return result
398 | }
399 | }
400 |
401 |
402 |
403 | public typealias ArrayLongerThan0 = PrefixedArrayEnd
404 | public typealias ArrayLongerThan1 = PrefixedArray>
405 | public typealias ArrayLongerThan2 = PrefixedArray>
406 |
407 |
408 |
409 | extension PrefixedArray where RestElements == ArrayLongerThan0 {
410 | // ArrayLongerThan1
411 | public var count: Int {
412 | return self.rest.count + 1
413 | }
414 |
415 |
416 | // ArrayLongerThan1
417 | public var last: Element {
418 | guard let last = self.rest.last else {
419 | return self.first
420 | }
421 | return last
422 | }
423 |
424 |
425 | // ArrayLongerThan1
426 | public init?(_ array: C) where C.Element == Element {
427 | guard let first = array.first else {
428 | return nil
429 | }
430 |
431 | let restEnoughLength = PrefixedArrayEnd(array.dropFirst())
432 |
433 | self.init(prefix: first, restEnoughLength)
434 | }
435 |
436 |
437 | // ArrayLongerThan1
438 | public init?(_ array: ArrayLongerThan0) {
439 | guard let (first, rest) = array.firstAndRest else {
440 | return nil
441 | }
442 |
443 | let restEnoughLength = ArrayLongerThan0(rest)
444 |
445 | self.init(prefix: first, restEnoughLength)
446 | }
447 |
448 |
449 | // ArrayLongerThan1
450 | public init(_ array: ArrayLongerThan1) {
451 | self = array
452 | }
453 | // ArrayLongerThan1
454 | public init(_ array: ArrayLongerThan2) {
455 | self.init(prefix: array.first, array.rest.relaxed())
456 | }
457 |
458 |
459 | // ArrayLongerThan1
460 | public init(prefix: Element, _ array: ArrayLongerThan1) {
461 | self.init(prefix: prefix, array.relaxed())
462 | }
463 |
464 |
465 | // ArrayLongerThan1
466 | public init(suffix: Element, _ array: ArrayLongerThan1) {
467 | self.init(suffix: suffix, array.relaxed())
468 | }
469 | // ArrayLongerThan1
470 | public init(prefix: Element, _ array: ArrayLongerThan2) {
471 | self.init(prefix: prefix, array.relaxed())
472 | }
473 |
474 |
475 | // ArrayLongerThan1
476 | public init(suffix: Element, _ array: ArrayLongerThan2) {
477 | self.init(suffix: suffix, array.relaxed())
478 | }
479 |
480 |
481 | // ArrayLongerThan1
482 | public init(suffix: Element, _ array: ArrayLongerThan0) {
483 | guard let (first, rest) = array.firstAndRest else {
484 | self.init(prefix: suffix, PrefixedArrayEnd([]))
485 | return
486 | }
487 |
488 | self.init(
489 | prefix: first,
490 | ArrayLongerThan0(suffix: suffix, rest)
491 | )
492 | }
493 |
494 |
495 | // ArrayLongerThan1
496 | public init(prefix: Element, _ array: C) where C.Element == Element {
497 | self.init(prefix: prefix, PrefixedArrayEnd(array))
498 | }
499 |
500 |
501 | // ArrayLongerThan1
502 | public init(suffix: Element, _ array: C) where C.Element == Element {
503 | guard let first = array.first else {
504 | self.init(prefix: suffix, PrefixedArrayEnd([]))
505 | return
506 | }
507 |
508 | var newRest = Array(array.dropFirst())
509 | newRest.append(suffix)
510 |
511 | self.init(prefix: first, PrefixedArrayEnd(newRest))
512 | }
513 |
514 |
515 | // ArrayLongerThan1
516 | public subscript(index: Int) -> Element {
517 | guard index != 0 else {
518 | return self.first
519 | }
520 | return self.rest[index - 1]
521 | }
522 |
523 |
524 | // ArrayLongerThan1
525 | public subscript(range: Range) -> ArrayLongerThan0 {
526 | return self.relaxed()[range]
527 | }
528 |
529 |
530 | // ArrayLongerThan1
531 | public mutating func insert(_ newElement: Element, at i: Int) {
532 | guard i > 0 else {
533 | self = ArrayLongerThan1(
534 | prefix: newElement,
535 | self.relaxed()
536 | )
537 | return
538 | }
539 |
540 | var newRest = self.rest
541 | newRest.insert(newElement, at: i - 1)
542 |
543 | self = ArrayLongerThan1(
544 | prefix: self.prefix,
545 | newRest
546 | )
547 | }
548 |
549 |
550 | // ArrayLongerThan1
551 | public mutating func insert(contentsOf newElements: ArrayLongerThan0, at i: Int) {
552 | // TODO: Check the standard behavior to handle negative values.
553 | guard i > 0 else {
554 | guard let (first, rest) = newElements.firstAndRest else { return }
555 |
556 | self = ArrayLongerThan1(
557 | prefix: first,
558 | rest + ArrayLongerThan1(self) // NOTE: Avoid to use exceeded length types.
559 | )
560 | return
561 | }
562 |
563 | var newRest = self.rest
564 | newRest.insert(contentsOf: newElements, at: i - 1)
565 |
566 | self = ArrayLongerThan1(
567 | prefix: self.prefix,
568 | newRest
569 | )
570 | }
571 | // ArrayLongerThan1
572 | public mutating func insert(contentsOf newElements: ArrayLongerThan1, at i: Int) {
573 | // TODO: Check the standard behavior to handle negative values.
574 | guard i > 0 else {
575 | let (first, rest) = newElements.firstAndRest
576 |
577 | self = ArrayLongerThan1(
578 | prefix: first,
579 | rest + ArrayLongerThan0(self) // NOTE: Avoid to use exceeded length types.
580 | )
581 | return
582 | }
583 |
584 | var newRest = self.rest
585 | newRest.insert(contentsOf: newElements, at: i - 1)
586 |
587 | self = ArrayLongerThan1(
588 | prefix: self.prefix,
589 | newRest
590 | )
591 | }
592 | // ArrayLongerThan1
593 | public mutating func insert(contentsOf newElements: ArrayLongerThan2, at i: Int) {
594 | // TODO: Check the standard behavior to handle negative values.
595 | guard i > 0 else {
596 | let (first, rest) = newElements.firstAndRest
597 |
598 | self = ArrayLongerThan1(
599 | prefix: first,
600 | rest + ArrayLongerThan0(self) // NOTE: Avoid to use exceeded length types.
601 | )
602 | return
603 | }
604 |
605 | var newRest = self.rest
606 | newRest.insert(contentsOf: newElements, at: i - 1)
607 |
608 | self = ArrayLongerThan1(
609 | prefix: self.prefix,
610 | newRest
611 | )
612 | }
613 |
614 |
615 | // ArrayLongerThan1
616 | public mutating func append(_ newElement: Element) {
617 | var newRest = self.rest
618 | newRest.append(newElement)
619 | self = ArrayLongerThan1(
620 | prefix: self.prefix,
621 | newRest
622 | )
623 | }
624 |
625 |
626 | // ArrayLongerThan1
627 | public mutating func append(contentsOf newElements: ArrayLongerThan0) {
628 | var newRest = self.rest
629 | newRest.append(contentsOf: newElements)
630 | self = ArrayLongerThan1(
631 | prefix: self.prefix,
632 | newRest
633 | )
634 | }
635 | // ArrayLongerThan1
636 | public mutating func append(contentsOf newElements: ArrayLongerThan1) {
637 | var newRest = self.rest
638 | newRest.append(contentsOf: newElements)
639 | self = ArrayLongerThan1(
640 | prefix: self.prefix,
641 | newRest
642 | )
643 | }
644 | // ArrayLongerThan1
645 | public mutating func append(contentsOf newElements: ArrayLongerThan2) {
646 | var newRest = self.rest
647 | newRest.append(contentsOf: newElements)
648 | self = ArrayLongerThan1(
649 | prefix: self.prefix,
650 | newRest
651 | )
652 | }
653 |
654 |
655 | // ArrayLongerThan1
656 | public func dropLast() -> PrefixedArrayEnd {
657 | guard let rest = self.rest.dropLast() else {
658 | return PrefixedArrayEnd([])
659 | }
660 |
661 | return PrefixedArrayEnd(
662 | prefix: self.first,
663 | rest
664 | )
665 | }
666 |
667 |
668 | // ArrayLongerThan1
669 | public func map(_ f: (Element) throws -> T) rethrows -> ArrayLongerThan1 {
670 | return ArrayLongerThan1(
671 | prefix: try f(self.first),
672 | try self.rest.map(f)
673 | )
674 | }
675 |
676 |
677 | // ArrayLongerThan1
678 | public func compactMap(_ f: (Element) throws -> T?) rethrows -> PrefixedArrayEnd {
679 | return try self.relaxed().compactMap(f)
680 | }
681 |
682 |
683 | // ArrayLongerThan1
684 | public func filter(_ f: (Element) throws -> Bool) rethrows -> PrefixedArrayEnd {
685 | return try self.relaxed().filter(f)
686 | }
687 |
688 |
689 | // ArrayLongerThan1
690 | public func flatMap(_ f: (Element) throws -> S) rethrows -> PrefixedArrayEnd where S.Element == T {
691 | return try self.relaxed().flatMap(f)
692 | }
693 |
694 |
695 | // ArrayLongerThan1
696 | public func enumerated() -> ArrayLongerThan1<(Int, Element)> {
697 | return ArrayLongerThan1<(Int, Element)>(
698 | prefix: (0, self.first),
699 | self.rest.enumerated().map { ($0.0 + 1, $0.1) }
700 | )
701 | }
702 |
703 |
704 | // ArrayLongerThan1
705 | public func relaxed() -> ArrayLongerThan0 {
706 | return ArrayLongerThan0(
707 | prefix: self.prefix,
708 | self.rest.relaxed()
709 | )
710 | }
711 | }
712 | extension PrefixedArray where RestElements == ArrayLongerThan1 {
713 | // ArrayLongerThan2
714 | public var count: Int {
715 | return self.rest.count + 1
716 | }
717 |
718 |
719 | // ArrayLongerThan2
720 | public var last: Element {
721 | return self.rest.last
722 | }
723 |
724 |
725 | // ArrayLongerThan2
726 | public init?(_ array: C) where C.Element == Element {
727 | guard let first = array.first else {
728 | return nil
729 | }
730 |
731 | guard let restEnoughLength = ArrayLongerThan1(array.dropFirst()) else { return nil }
732 |
733 | self.init(prefix: first, restEnoughLength)
734 | }
735 |
736 |
737 | // ArrayLongerThan2
738 | public init?(_ array: ArrayLongerThan0) {
739 | guard let (first, rest) = array.firstAndRest else {
740 | return nil
741 | }
742 |
743 | guard let restEnoughLength = ArrayLongerThan1(rest) else {
744 | return nil
745 | }
746 |
747 | self.init(prefix: first, restEnoughLength)
748 | }
749 |
750 |
751 | // ArrayLongerThan2
752 | public init?(_ array: ArrayLongerThan1) {
753 | guard let restEnoughLength = ArrayLongerThan1(array.rest) else {
754 | return nil
755 | }
756 |
757 | self.init(prefix: array.first, restEnoughLength)
758 | }
759 | // ArrayLongerThan2
760 | public init(_ array: ArrayLongerThan2) {
761 | self = array
762 | }
763 |
764 |
765 | // ArrayLongerThan2
766 | public init(prefix: Element, _ array: ArrayLongerThan2) {
767 | self.init(prefix: prefix, array.relaxed())
768 | }
769 |
770 |
771 | // ArrayLongerThan2
772 | public init(suffix: Element, _ array: ArrayLongerThan2) {
773 | self.init(suffix: suffix, array.relaxed())
774 | }
775 |
776 |
777 | public init(suffix: Element, _ array: ArrayLongerThan1) {
778 | self.init(
779 | prefix: array.first,
780 | ArrayLongerThan1(suffix: suffix, array.rest)
781 | )
782 | }
783 |
784 |
785 | // ArrayLongerThan2
786 | public init?(prefix: Element, _ array: C) where C.Element == Element {
787 | guard let rest = ArrayLongerThan1(array) else {
788 | return nil
789 | }
790 |
791 | self.init(prefix: prefix, rest)
792 | }
793 |
794 |
795 | // ArrayLongerThan2
796 | public init?(suffix: Element, _ array: C) where C.Element == Element {
797 | guard let head = ArrayLongerThan1(array) else {
798 | return nil
799 | }
800 |
801 | self.init(suffix: suffix, head)
802 | }
803 |
804 |
805 | // ArrayLongerThan2
806 | public subscript(index: Int) -> Element {
807 | guard index != 0 else {
808 | return self.first
809 | }
810 | return self.rest[index - 1]
811 | }
812 |
813 |
814 | // ArrayLongerThan2
815 | public subscript(range: Range) -> ArrayLongerThan0 {
816 | return self.relaxed()[range]
817 | }
818 |
819 |
820 | // ArrayLongerThan2
821 | public mutating func insert(_ newElement: Element, at i: Int) {
822 | guard i > 0 else {
823 | self = ArrayLongerThan2(
824 | prefix: newElement,
825 | self.relaxed()
826 | )
827 | return
828 | }
829 |
830 | var newRest = self.rest
831 | newRest.insert(newElement, at: i - 1)
832 |
833 | self = ArrayLongerThan2(
834 | prefix: self.prefix,
835 | newRest
836 | )
837 | }
838 |
839 |
840 | // ArrayLongerThan2
841 | public mutating func insert(contentsOf newElements: ArrayLongerThan0, at i: Int) {
842 | // TODO: Check the standard behavior to handle negative values.
843 | guard i > 0 else {
844 | guard let (first, rest) = newElements.firstAndRest else { return }
845 |
846 | self = ArrayLongerThan2(
847 | prefix: first,
848 | rest + ArrayLongerThan2(self) // NOTE: Avoid to use exceeded length types.
849 | )
850 | return
851 | }
852 |
853 | var newRest = self.rest
854 | newRest.insert(contentsOf: newElements, at: i - 1)
855 |
856 | self = ArrayLongerThan2(
857 | prefix: self.prefix,
858 | newRest
859 | )
860 | }
861 | // ArrayLongerThan2
862 | public mutating func insert(contentsOf newElements: ArrayLongerThan1, at i: Int) {
863 | // TODO: Check the standard behavior to handle negative values.
864 | guard i > 0 else {
865 | let (first, rest) = newElements.firstAndRest
866 |
867 | self = ArrayLongerThan2(
868 | prefix: first,
869 | rest + ArrayLongerThan1(self) // NOTE: Avoid to use exceeded length types.
870 | )
871 | return
872 | }
873 |
874 | var newRest = self.rest
875 | newRest.insert(contentsOf: newElements, at: i - 1)
876 |
877 | self = ArrayLongerThan2(
878 | prefix: self.prefix,
879 | newRest
880 | )
881 | }
882 | // ArrayLongerThan2
883 | public mutating func insert(contentsOf newElements: ArrayLongerThan2, at i: Int) {
884 | // TODO: Check the standard behavior to handle negative values.
885 | guard i > 0 else {
886 | let (first, rest) = newElements.firstAndRest
887 |
888 | self = ArrayLongerThan2(
889 | prefix: first,
890 | rest + ArrayLongerThan0(self) // NOTE: Avoid to use exceeded length types.
891 | )
892 | return
893 | }
894 |
895 | var newRest = self.rest
896 | newRest.insert(contentsOf: newElements, at: i - 1)
897 |
898 | self = ArrayLongerThan2(
899 | prefix: self.prefix,
900 | newRest
901 | )
902 | }
903 |
904 |
905 | // ArrayLongerThan2
906 | public mutating func append(_ newElement: Element) {
907 | var newRest = self.rest
908 | newRest.append(newElement)
909 | self = ArrayLongerThan2(
910 | prefix: self.prefix,
911 | newRest
912 | )
913 | }
914 |
915 |
916 | // ArrayLongerThan2
917 | public mutating func append(contentsOf newElements: ArrayLongerThan0) {
918 | var newRest = self.rest
919 | newRest.append(contentsOf: newElements)
920 | self = ArrayLongerThan2(
921 | prefix: self.prefix,
922 | newRest
923 | )
924 | }
925 | // ArrayLongerThan2
926 | public mutating func append(contentsOf newElements: ArrayLongerThan1) {
927 | var newRest = self.rest
928 | newRest.append(contentsOf: newElements)
929 | self = ArrayLongerThan2(
930 | prefix: self.prefix,
931 | newRest
932 | )
933 | }
934 | // ArrayLongerThan2
935 | public mutating func append(contentsOf newElements: ArrayLongerThan2) {
936 | var newRest = self.rest
937 | newRest.append(contentsOf: newElements)
938 | self = ArrayLongerThan2(
939 | prefix: self.prefix,
940 | newRest
941 | )
942 | }
943 |
944 |
945 | // ArrayLongerThan2
946 | public func dropLast() -> ArrayLongerThan1 {
947 | return ArrayLongerThan1(
948 | prefix: self.first,
949 | rest.dropLast()
950 | )
951 | }
952 |
953 |
954 | // ArrayLongerThan2
955 | public func map(_ f: (Element) throws -> T) rethrows -> ArrayLongerThan2 {
956 | return ArrayLongerThan2(
957 | prefix: try f(self.first),
958 | try self.rest.map(f)
959 | )
960 | }
961 |
962 |
963 | // ArrayLongerThan2
964 | public func compactMap(_ f: (Element) throws -> T?) rethrows -> PrefixedArrayEnd {
965 | return try self.relaxed().compactMap(f)
966 | }
967 |
968 |
969 | // ArrayLongerThan2
970 | public func filter(_ f: (Element) throws -> Bool) rethrows -> PrefixedArrayEnd {
971 | return try self.relaxed().filter(f)
972 | }
973 |
974 |
975 | // ArrayLongerThan2
976 | public func flatMap(_ f: (Element) throws -> S) rethrows -> PrefixedArrayEnd where S.Element == T {
977 | return try self.relaxed().flatMap(f)
978 | }
979 |
980 |
981 | // ArrayLongerThan2
982 | public func enumerated() -> ArrayLongerThan2<(Int, Element)> {
983 | return ArrayLongerThan2<(Int, Element)>(
984 | prefix: (0, self.first),
985 | self.rest.enumerated().map { ($0.0 + 1, $0.1) }
986 | )
987 | }
988 |
989 |
990 | // ArrayLongerThan2
991 | public func relaxed() -> ArrayLongerThan1 {
992 | return ArrayLongerThan1(
993 | prefix: self.prefix,
994 | self.rest.relaxed()
995 | )
996 | }
997 | }
998 |
999 |
1000 | public func +(lhs: ArrayLongerThan0, rhs: PrefixedArrayEnd) -> ArrayLongerThan0 {
1001 | var result = lhs
1002 | result.append(contentsOf: rhs)
1003 | return result
1004 | }
1005 | public func +(lhs: ArrayLongerThan0, rhs: ArrayLongerThan1) -> ArrayLongerThan1 {
1006 | return ArrayLongerThan1(suffix: rhs.last, lhs + rhs.dropLast())
1007 | }
1008 | public func +(lhs: ArrayLongerThan0, rhs: ArrayLongerThan2) -> ArrayLongerThan2 {
1009 | return ArrayLongerThan2(suffix: rhs.last, lhs + rhs.dropLast())
1010 | }
1011 | public func +(lhs: ArrayLongerThan1, rhs: PrefixedArrayEnd) -> ArrayLongerThan1 {
1012 | var result = lhs
1013 | result.append(contentsOf: rhs)
1014 | return result
1015 | }
1016 | public func +(lhs: ArrayLongerThan1, rhs: ArrayLongerThan1) -> ArrayLongerThan2 {
1017 | return ArrayLongerThan2(suffix: rhs.last, lhs + rhs.dropLast())
1018 | }
1019 | public func +(lhs: ArrayLongerThan2, rhs: PrefixedArrayEnd) -> ArrayLongerThan2 {
1020 | var result = lhs
1021 | result.append(contentsOf: rhs)
1022 | return result
1023 | }
1024 |
1025 |
1026 |
1027 | extension PrefixedArrayEnd where Element: Sequence {
1028 | public func joined() -> PrefixedArrayEnd {
1029 | return PrefixedArrayEnd(Array(self.array.joined()))
1030 | }
1031 | }
1032 |
1033 |
1034 |
1035 | extension PrefixedArray where Element: Sequence, RestElements == ArrayLongerThan0 {
1036 | public func joined() -> PrefixedArrayEnd {
1037 | return self.relaxed().joined()
1038 | }
1039 | }
1040 | extension PrefixedArray where Element: Sequence, RestElements == ArrayLongerThan1 {
1041 | public func joined() -> PrefixedArrayEnd {
1042 | return self.relaxed().joined()
1043 | }
1044 | }
1045 |
1046 |
1047 |
1048 | extension PrefixedArrayEnd where Element == String {
1049 | public func joined(separator: String) -> String {
1050 | return self.array.joined(separator: separator)
1051 | }
1052 | }
1053 |
1054 |
1055 |
1056 | extension PrefixedArray where Element == String, RestElements == ArrayLongerThan0 {
1057 | public func joined(separator: String) -> String {
1058 | return self.relaxed().joined(separator: separator)
1059 | }
1060 | }
1061 | extension PrefixedArray where Element == String, RestElements == ArrayLongerThan1 {
1062 | public func joined(separator: String) -> String {
1063 | return self.relaxed().joined(separator: separator)
1064 | }
1065 | }
1066 |
1067 |
1068 | func zip(_ a: ArrayLongerThan0, _ b: ArrayLongerThan0) -> ArrayLongerThan0<(A, B)> {
1069 | return ArrayLongerThan0<(A, B)>(Array(zip(a.relaxed(), b.relaxed())))
1070 | }
1071 |
1072 |
1073 | func zip(_ a: ArrayLongerThan1, _ b: ArrayLongerThan1) -> ArrayLongerThan1<(A, B)> {
1074 | return ArrayLongerThan1<(A, B)>(
1075 | prefix: (a.first, b.first),
1076 | zip(a.rest, b.rest)
1077 | )
1078 | }
1079 | func zip(_ a: ArrayLongerThan2, _ b: ArrayLongerThan2) -> ArrayLongerThan2<(A, B)> {
1080 | return ArrayLongerThan2<(A, B)>(
1081 | prefix: (a.first, b.first),
1082 | zip(a.rest, b.rest)
1083 | )
1084 | }
1085 |
1086 |
1087 |
1088 | func zip(_ a: A, _ b: B, _ c: C) -> [(A.Element, B.Element, C.Element)] {
1089 | return zip(zip(a, b), c).map { ($0.0.0, $0.0.1, $0.1) }
1090 | }
1091 |
1092 |
1093 |
1094 | func zip(_ a: ArrayLongerThan0, _ b: ArrayLongerThan0, _ c: ArrayLongerThan0) -> ArrayLongerThan0<(A, B, C)> {
1095 | return ArrayLongerThan0<(A, B, C)>(Array(zip(a.relaxed(), b.relaxed(), c.relaxed())))
1096 | }
1097 |
1098 |
1099 |
1100 | func zip(_ a: ArrayLongerThan1, _ b: ArrayLongerThan1, _ c: ArrayLongerThan1) -> ArrayLongerThan1<(A, B, C)> {
1101 | return ArrayLongerThan1<(A, B, C)>(
1102 | prefix: (a.first, b.first, c.first),
1103 | zip(a.rest, b.rest, c.rest)
1104 | )
1105 | }
1106 | func zip(_ a: ArrayLongerThan2, _ b: ArrayLongerThan2, _ c: ArrayLongerThan2) -> ArrayLongerThan2<(A, B, C)> {
1107 | return ArrayLongerThan2<(A, B, C)>(
1108 | prefix: (a.first, b.first, c.first),
1109 | zip(a.rest, b.rest, c.rest)
1110 | )
1111 | }
1112 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/PrefixedArray.swift.gyb:
--------------------------------------------------------------------------------
1 | %{
2 | number_of_generated = 3
3 | }%
4 |
5 |
6 | public struct PrefixedArray