├── bin
├── gyb
│ ├── gyb
│ ├── gyb.pyc
│ └── gyb.py
└── update
├── .idea
├── MemoryLeakTestKit.iml
├── misc.xml
├── vcs.xml
├── xcode.xml
├── modules.xml
├── runConfigurations
│ ├── MemoryLeakTestKitTests.xml
│ └── MemoryLeakTestKit_Package.xml
└── codeStyles
│ └── Project.xml
├── Gemfile
├── Tests
├── LinuxMain.swift
└── MemoryLeakTestKitTests
│ ├── XCTestManifests.swift
│ ├── ReferenceIDTests.swift
│ ├── ReferencePathNormalizationTests.swift
│ └── MemoryLeakDetectorTests.swift
├── .circleci
└── config.yml
├── Sources
└── MemoryLeakTestKit
│ ├── Types.swift
│ ├── Weak.swift
│ ├── Arrays.swift
│ ├── ReferencePathComponent.swift
│ ├── NotNormalizedPathComponent.swift
│ ├── WeakOrNotReference.swift
│ ├── IdentifiableReferencePathComponent.swift
│ ├── MemoryLeakReport.swift
│ ├── ReferencePath.swift
│ ├── IdentifiableReferencePath.swift
│ ├── Reference.swift
│ ├── ReferenceID.swift
│ ├── MemoryLeakDetector.swift
│ ├── LeakedObject.swift
│ ├── ObjectTraverser.swift
│ ├── CircularReferencePath.swift
│ ├── PrettyPrint.swift
│ ├── ReferencePathNormalization.swift
│ ├── PrefixedArray.swift.gyb
│ └── PrefixedArray.generated.swift
├── MemoryLeakTestKit.podspec
├── LICENSE
├── Package.swift
├── README.md
├── Gemfile.lock
└── .gitignore
/bin/gyb/gyb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2.7
2 | import gyb
3 | gyb.main()
4 |
--------------------------------------------------------------------------------
/bin/gyb/gyb.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kuniwak/MemoryLeakTestKit/HEAD/bin/gyb/gyb.pyc
--------------------------------------------------------------------------------
/.idea/MemoryLeakTestKit.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import MemoryLeakTestKitTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += MemoryLeakTestKitTests.allTests()
7 | XCTMain(tests)
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/xcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/Types.swift:
--------------------------------------------------------------------------------
1 | public struct TypeName: Hashable {
2 | public let text: String
3 |
4 |
5 | public init(text: String) {
6 | self.text = text
7 | }
8 |
9 |
10 | public init(of any: Any) {
11 | self.init(text: "\(Mirror(reflecting: any).subjectType)")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Tests/MemoryLeakTestKitTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !os(macOS)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(MemoryLeakDetectorTests.allTests),
7 | testCase(ReferenceIDTests.allTests),
8 | testCase(ReferencePathNormalizationTests.allTests),
9 | ]
10 | }
11 | #endif
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/Weak.swift:
--------------------------------------------------------------------------------
1 | public class Weak {
2 | public private(set) weak var value: T?
3 |
4 |
5 | public init(_ value: T) {
6 | self.value = value
7 | }
8 |
9 |
10 | public init() {
11 | self.value = nil
12 | }
13 |
14 |
15 | public var isReleased: Bool {
16 | return self.value == nil
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eu
2 | set -o pipefail
3 |
4 |
5 | ROOT_DIR="$(cd "$(dirname "$0")/.."; pwd)"
6 | BIN_DIR="${ROOT_DIR}/bin"
7 | GYB="${BIN_DIR}/gyb/gyb"
8 |
9 | SOURCE_DIR="${ROOT_DIR}/Sources"
10 |
11 |
12 | for gyb_file in $(find "${SOURCE_DIR}" -name "*.gyb"); do
13 | out_file="$(dirname "${gyb_file}")/$(basename -s ".swift.gyb" "${gyb_file}").generated.swift"
14 | "${GYB}" "${gyb_file}" -o "${out_file}" --line-directive=""
15 | done
16 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/ReferencePathComponent.swift:
--------------------------------------------------------------------------------
1 | public enum ReferencePathComponent: Hashable {
2 | case label(String)
3 | case index(Int)
4 | case noLabel
5 |
6 |
7 | public var description: String {
8 | switch self {
9 | case .label(let label):
10 | return ".\(label)"
11 | case .index(let index):
12 | return "[\(index)]"
13 | case .noLabel:
14 | return "[unknown accessor]"
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/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/WeakOrNotReference.swift:
--------------------------------------------------------------------------------
1 | public enum WeakOrNotReferenceType {
2 | case weak(Weak)
3 | case notReferenceType
4 |
5 |
6 | public var weak: Weak? {
7 | switch self {
8 | case .weak(let container):
9 | return container
10 | case .notReferenceType:
11 | return nil
12 | }
13 | }
14 |
15 |
16 | public init(_ target: T) {
17 | switch Mirror(reflecting: target).displayStyle {
18 | case .some(.class):
19 | self = .weak(Weak(target as AnyObject))
20 | default:
21 | self = .notReferenceType
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/ReferencePath.swift:
--------------------------------------------------------------------------------
1 | public struct ReferencePath: Hashable {
2 | public let components: [ReferencePathComponent]
3 |
4 |
5 | public var count: Int {
6 | return self.components.count
7 | }
8 |
9 |
10 | public var description: String {
11 | return "(root)" + self.components
12 | .map { $0.description }
13 | .joined(separator: "")
14 | }
15 |
16 |
17 | public init(components: [ReferencePathComponent]) {
18 | self.components = components
19 | }
20 |
21 |
22 | public init(identifiablePath: IdentifiableReferencePath) {
23 | let hints = identifiablePath
24 | .idComponents
25 | .map { ReferencePathNormalization.Hint($0.noNormalizedComponent) }
26 |
27 | self.init(components: ReferencePathNormalization.normalize(hints: hints))
28 | }
29 |
30 |
31 | public static let root = ReferencePath(components: [])
32 | }
33 |
34 |
35 |
36 | extension ReferencePath: Comparable {
37 | public static func <(lhs: ReferencePath, rhs: ReferencePath) -> Bool {
38 | return lhs.count < rhs.count
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/MemoryLeakTestKitTests/ReferenceIDTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import MemoryLeakTestKit
3 |
4 |
5 |
6 | class ReferenceIDTests: XCTestCase {
7 | func testEquatable() {
8 | typealias TestCase = (
9 | ReferenceID,
10 | ReferenceID,
11 | expected: Bool
12 | )
13 |
14 | let testCases: [UInt: TestCase] = [
15 | #line: (
16 | ReferenceID(of: 0),
17 | ReferenceID(of: 0),
18 | expected: false
19 | ),
20 | #line: {
21 | let object1 = NSObject()
22 | let object2 = NSObject()
23 | return (
24 | ReferenceID(of: object1),
25 | ReferenceID(of: object2),
26 | expected: false
27 | )
28 | }(),
29 | #line: {
30 | let object = NSObject()
31 | return (
32 | ReferenceID(of: object),
33 | ReferenceID(of: object),
34 | expected: true
35 | )
36 | }()
37 | ]
38 |
39 | testCases.forEach { tuple in
40 | let (line, (a, b, expected: expected)) = tuple
41 |
42 | XCTAssertEqual(a == b, expected, line: line)
43 | }
44 | }
45 |
46 |
47 | static let allTests: [(String, (ReferenceIDTests) -> () throws -> Void)] = [
48 | ("testEquatable", testEquatable),
49 | ]
50 | }
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/Reference.swift:
--------------------------------------------------------------------------------
1 | public class Reference {
2 | private let value: WeakOrNotReferenceType
3 | private let id: ReferenceID
4 |
5 | public let destinationTypeName: TypeName
6 | public let destinationObjectDescription: String
7 | public var foundLocations: ArrayLongerThan1
8 |
9 |
10 | public init(
11 | value: WeakOrNotReferenceType,
12 | id: ReferenceID,
13 | typeName: TypeName,
14 | description: String,
15 | foundLocations: ArrayLongerThan1
16 | ) {
17 | self.value = value
18 | self.id = id
19 | self.destinationTypeName = typeName
20 | self.destinationObjectDescription = description
21 | self.foundLocations = foundLocations
22 | }
23 |
24 |
25 | public convenience init(_ target: Any, foundLocations: ArrayLongerThan1) {
26 | self.init(
27 | value: WeakOrNotReferenceType(target),
28 | id: ReferenceID(of: target),
29 | typeName: TypeName(of: target),
30 | description: "\(target)",
31 | foundLocations: foundLocations
32 | )
33 | }
34 |
35 |
36 | public var isReleased: Bool {
37 | guard let weak = self.value.weak else {
38 | return true
39 | }
40 |
41 | return weak.isReleased
42 | }
43 |
44 |
45 | public func found(location: IdentifiableReferencePath) {
46 | self.foundLocations.append(location)
47 | }
48 | }
49 |
50 |
51 |
52 | extension Reference: Hashable {
53 | public var hashValue: Int {
54 | return self.id.hashValue
55 | }
56 |
57 |
58 | public static func ==(lhs: Reference, rhs: Reference) -> Bool {
59 | return lhs.id == rhs.id
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/ReferenceID.swift:
--------------------------------------------------------------------------------
1 | public enum ReferenceID {
2 | case anyReferenceType(name: TypeName, objectIdentifier: ObjectIdentifier)
3 | case anyValueType(name: TypeName)
4 | case unknown(name: TypeName)
5 |
6 |
7 | public init(of target: T) {
8 | switch Mirror(reflecting: target).displayStyle {
9 | case .some(.class):
10 | let objectIdentifier = ObjectIdentifier(target as AnyObject)
11 | self = .anyReferenceType(
12 | name: TypeName(of: target),
13 | objectIdentifier: objectIdentifier
14 | )
15 |
16 | case .some(.tuple), .some(.struct), .some(.collection), .some(.dictionary),
17 | .some(.enum), .some(.optional), .some(.set):
18 | self = .anyValueType(name: TypeName(of: target))
19 |
20 | case .none:
21 | self = .unknown(name: TypeName(of: target))
22 | }
23 | }
24 | }
25 |
26 |
27 |
28 | extension ReferenceID: Equatable {
29 | public static func ==(lhs: ReferenceID, rhs: ReferenceID) -> Bool {
30 | switch (lhs, rhs) {
31 | case (.anyReferenceType(name: _, objectIdentifier: let l), .anyReferenceType(name: _, objectIdentifier: let r)):
32 | return l == r
33 |
34 | default:
35 | // NOTE: Any values should not be identical.
36 | return false
37 | }
38 | }
39 | }
40 |
41 |
42 |
43 | extension ReferenceID: Hashable {
44 | public var hashValue: Int {
45 | switch self {
46 | case .anyReferenceType(name: _, objectIdentifier: let objectIdentifier):
47 | return objectIdentifier.hashValue
48 |
49 | case .anyValueType:
50 | // NOTE: Any values should not be identical.
51 | return 0
52 |
53 | case .unknown:
54 | return 1
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/PrettyPrint.swift:
--------------------------------------------------------------------------------
1 | public protocol PrettyPrintable {
2 | var descriptionLines: [IndentedLine] { get }
3 | }
4 |
5 |
6 |
7 | extension PrettyPrintable {
8 | public var prettyDescription: String {
9 | return format(self.descriptionLines)
10 | }
11 | }
12 |
13 |
14 |
15 | public indirect enum IndentedLine: Equatable {
16 | case indent(IndentedLine)
17 | case content(String)
18 |
19 |
20 | public func format(indentWidth: Int) -> String {
21 | switch self {
22 | case .indent(let line):
23 | let indent = String(repeating: " ", count: indentWidth)
24 | return indent + line.format(indentWidth: indentWidth)
25 | case .content(let content):
26 | return content
27 | }
28 | }
29 | }
30 |
31 |
32 |
33 | public func format(
34 | _ lines: Lines,
35 | indentWidth: Int = 4
36 | ) -> String where Lines.Element == IndentedLine {
37 | return lines
38 | .map { line in line.format(indentWidth: indentWidth) }
39 | .joined(separator: "\n")
40 | }
41 |
42 |
43 |
44 | public func indent(
45 | _ lines: Lines
46 | ) -> [IndentedLine] where Lines.Element == IndentedLine {
47 | return lines.map { .indent($0) }
48 | }
49 |
50 |
51 |
52 | public func lines(
53 | _ lines: Lines
54 | ) -> [IndentedLine] where Lines.Element == String {
55 | return lines.map { .content($0) }
56 | }
57 |
58 |
59 |
60 | public func verticalPadding(
61 | _ lines: Lines
62 | ) -> [IndentedLine] where Lines.Element == IndentedLine {
63 | var result = [IndentedLine.content("")]
64 | result.append(contentsOf: lines)
65 | result.append(.content(""))
66 | return result
67 | }
68 |
69 |
70 |
71 | public func section(
72 | name: String,
73 | body: Lines
74 | ) -> [IndentedLine] where Lines.Element == IndentedLine {
75 | let bodyMustNotBeEmpty: [IndentedLine]
76 |
77 | if body.isEmpty {
78 | bodyMustNotBeEmpty = lines(["(empty)"])
79 | }
80 | else {
81 | bodyMustNotBeEmpty = Array(body)
82 | }
83 |
84 | return [.content("\(name):")] + indent(bodyMustNotBeEmpty)
85 | }
86 |
87 |
88 |
89 | public func sections(
90 | _ sectionsInfo: Sections
91 | ) -> [IndentedLine] where Sections.Element == (name: String, body: Lines), Lines.Element == IndentedLine {
92 | let sectionLines = sectionsInfo.map { sectionInfo in
93 | return section(name: sectionInfo.name, body: sectionInfo.body)
94 | }
95 |
96 | let verticalSpace = [IndentedLine.content("")]
97 |
98 | return intersperse(sectionLines, verticalSpace).flatMap { $0 }
99 | }
100 |
101 |
102 |
103 | public func sections(
104 | _ sectionsInfo: Sections
105 | ) -> [IndentedLine] where Sections.Element == Lines, Lines.Element == IndentedLine {
106 | return sections(sectionsInfo
107 | .enumerated()
108 | .map { (name: "\($0.0)", body: $0.1) })
109 | }
110 |
111 |
112 |
113 | public func descriptionList(
114 | _ items: Items
115 | ) -> [IndentedLine] where Items.Element == (label: String, description: String) {
116 | return items.map { item in .content("\(item.label): \(item.description)") }
117 | }
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/ReferencePathNormalization.swift:
--------------------------------------------------------------------------------
1 | // NOTE: Mirror.Child for values stored on lazy var become the following unreadable value:
2 | // (label: propertyName + ".storage", value: Optional.some(value))
3 | public enum ReferencePathNormalization {
4 | public static let lazyStorageLabelSuffix = ".storage"
5 | public static let optionalSomeLabel = "some"
6 |
7 |
8 | public static func normalize(
9 | _ noNormalizedComponents: [NotNormalizedReferencePathComponent]
10 | ) -> [ReferencePathComponent] {
11 | return self.normalize(hints: noNormalizedComponents.map { Hint($0) })
12 | }
13 |
14 |
15 | public static func normalize(
16 | _ noNormalizedComponents: ArrayLongerThan1
17 | ) -> ArrayLongerThan1 {
18 | return self.normalize(hints: noNormalizedComponents.map { Hint($0) })
19 | }
20 |
21 |
22 | public static func normalize(
23 | _ noNormalizedComponents: ArrayLongerThan2
24 | ) -> ArrayLongerThan1 {
25 | return self.normalize(hints: noNormalizedComponents.map { Hint($0) })
26 | }
27 |
28 |
29 | public static func normalize(
30 | hints: [Hint]
31 | ) -> [ReferencePathComponent] {
32 | guard let noEmptyHints = ArrayLongerThan1(hints) else {
33 | return []
34 | }
35 |
36 | return Array(self.normalize(hints: noEmptyHints).sequence())
37 | }
38 |
39 |
40 | public static func normalize(
41 | hints: ArrayLongerThan1
42 | ) -> ArrayLongerThan1 {
43 | guard let noEmptyHints = ArrayLongerThan2(hints) else {
44 | return ArrayLongerThan1(prefix: self.normalize(initialHint: hints.first, nextHintIfExists: nil), [])
45 | }
46 |
47 | return self.normalize(hints: noEmptyHints)
48 | }
49 |
50 |
51 | public static func normalize(
52 | hints: ArrayLongerThan2
53 | ) -> ArrayLongerThan1 {
54 | let prefix = self.contextDependedNormalize(currentHint: hints.prefix, nextHintIfExists: hints.rest.prefix)
55 | var suffixed = hints.map(Optional.some)
56 | suffixed.append(nil)
57 |
58 | // NOTE: Create a pair that represent a bidirectional contexts.
59 | // (prev, current, nextIfExists)
60 | // [0, 1] -> [(0, 1, nil)]
61 | // [0, 1, 2] -> [(0, 1, 2), (1, 2, nil)]
62 | let intermediates = zip(hints.relaxed().relaxed(), hints.dropFirst().relaxed(), suffixed.dropFirst().dropFirst())
63 | .compactMap { slided -> ReferencePathComponent? in
64 | let (previousHint, currentHint, nextHintIfExists) = slided
65 | return self.normalize(
66 | previousHint: previousHint,
67 | currentHint: currentHint,
68 | nextHintIfExists: nextHintIfExists
69 | )
70 | }
71 |
72 | return .init(prefix: prefix, intermediates)
73 | }
74 |
75 |
76 | public static func normalize(initialHint: Hint, nextHintIfExists: Hint?) -> ReferencePathComponent {
77 | return self.contextDependedNormalize(currentHint: initialHint, nextHintIfExists: nextHintIfExists)
78 | }
79 |
80 |
81 | public static func normalize(previousHint: Hint, currentHint: Hint, nextHintIfExists: Hint?) -> ReferencePathComponent? {
82 | switch (previousHint, currentHint, nextHintIfExists) {
83 | case (.hasLazyStorageSuffix, .isOptionalSome, _):
84 | return nil
85 | default:
86 | return self.contextDependedNormalize(currentHint: currentHint, nextHintIfExists: nextHintIfExists)
87 | }
88 | }
89 |
90 |
91 | // NOTE: This method can work properly only if the previous hint is not a lazy storage suffix.
92 | public static func contextDependedNormalize(currentHint: Hint, nextHintIfExists: Hint?) -> ReferencePathComponent {
93 | switch (currentHint, nextHintIfExists) {
94 | case (.hasLazyStorageSuffix(label: let label), .some(.isOptionalSome)):
95 | return .label(String(label.dropLast(self.lazyStorageLabelSuffix.count)))
96 | case (.hasLazyStorageSuffix(label: let label), _), (.none(.label(let label)), _):
97 | return .label(label)
98 | case (.none(.index(let index)), _):
99 | return .index(index)
100 | case (.none(.noLabel), _):
101 | return .noLabel
102 | case (.isOptionalSome, _):
103 | return .label(self.optionalSomeLabel)
104 | }
105 | }
106 |
107 |
108 |
109 | public enum Hint: Equatable {
110 | case none(NotNormalizedReferencePathComponent)
111 | case hasLazyStorageSuffix(label: String)
112 | case isOptionalSome
113 |
114 |
115 | public init(_ component: NotNormalizedReferencePathComponent) {
116 | switch component {
117 | case .label(let label):
118 | if label.hasSuffix(ReferencePathNormalization.lazyStorageLabelSuffix) {
119 | self = .hasLazyStorageSuffix(label: label)
120 | }
121 | else if (label == ReferencePathNormalization.optionalSomeLabel) {
122 | self = .isOptionalSome
123 | }
124 | else {
125 | self = .none(component)
126 | }
127 | case .noLabel, .index:
128 | self = .none(component)
129 | }
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Tests/MemoryLeakTestKitTests/ReferencePathNormalizationTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import MemoryLeakTestKit
3 |
4 |
5 |
6 | class ReferencePathNormalizationTests: XCTestCase {
7 | func testNormalize() {
8 | typealias TestCase = (
9 | input: [NotNormalizedReferencePathComponent],
10 | expected: [ReferencePathComponent]
11 | )
12 |
13 | let testCases: [UInt: TestCase] = [
14 | #line: (
15 | input: [],
16 | expected: []
17 | ),
18 | #line: (
19 | input: [
20 | .noLabel,
21 | ],
22 | expected: [
23 | .noLabel,
24 | ]
25 | ),
26 | #line: (
27 | input: [
28 | .label("label"),
29 | ],
30 | expected: [
31 | .label("label"),
32 | ]
33 | ),
34 | #line: (
35 | input: [
36 | .index(0),
37 | ],
38 | expected: [
39 | .index(0),
40 | ]
41 | ),
42 | #line: (
43 | input: [
44 | .label("label"),
45 | .index(1)
46 | ],
47 | expected: [
48 | .label("label"),
49 | .index(1)
50 | ]
51 | ),
52 | #line: (
53 | input: [
54 | .label("notLazy"),
55 | .label("some")
56 | ],
57 | expected: [
58 | .label("notLazy"),
59 | .label("some")
60 | ]
61 | ),
62 | #line: (
63 | input: [
64 | .label("some"),
65 | .label("some"),
66 | ],
67 | expected: [
68 | .label("some"),
69 | .label("some"),
70 | ]
71 | ),
72 |
73 |
74 | // IMPORTANT CASES:
75 | #line: (
76 | input: [
77 | .label("fake.storage"),
78 | .label("some"),
79 | ],
80 | expected: [
81 | .label("fake"),
82 | ]
83 | ),
84 | #line: (
85 | input: [
86 | .label("label"),
87 | .label("fake.storage"),
88 | .label("some"),
89 | ],
90 | expected: [
91 | .label("label"),
92 | .label("fake"),
93 | ]
94 | ),
95 | #line: (
96 | input: [
97 | .label("fake.storage"),
98 | .label("some"),
99 | .label("label"),
100 | ],
101 | expected: [
102 | .label("fake"),
103 | .label("label"),
104 | ]
105 | ),
106 | ]
107 |
108 | testCases.forEach { tuple in
109 | let (line, (input: input, expected: expected)) = tuple
110 |
111 | let actual = ReferencePathNormalization.normalize(input)
112 |
113 | XCTAssertEqual(
114 | actual, expected,
115 | difference(between: expected, and: actual),
116 | line: line
117 | )
118 | }
119 | }
120 |
121 |
122 | func testHint() {
123 | typealias TestCase = (
124 | input: NotNormalizedReferencePathComponent,
125 | expected: ReferencePathNormalization.Hint
126 | )
127 |
128 | let testCases: [UInt: TestCase] = [
129 | #line: (
130 | input: .noLabel,
131 | expected: .none(.noLabel)
132 | ),
133 | #line: (
134 | input: .index(0),
135 | expected: .none(.index(0))
136 | ),
137 | #line: (
138 | input: .label("label"),
139 | expected: .none(.label("label"))
140 | ),
141 | #line: (
142 | input: .label("label.storage"),
143 | expected: .hasLazyStorageSuffix(label: "label.storage")
144 | ),
145 | #line: (
146 | input: .label("some"),
147 | expected: .isOptionalSome
148 | ),
149 | #line: (
150 | input: .label("some.storage"),
151 | expected: .hasLazyStorageSuffix(label: "some.storage")
152 | ),
153 | ]
154 |
155 | testCases.forEach {
156 | let (line, (input: input, expected: expected)) = $0
157 |
158 | let actual = ReferencePathNormalization.Hint(input)
159 |
160 | XCTAssertEqual(actual, expected, line: line)
161 | }
162 | }
163 |
164 |
165 | private func difference(between a: [ReferencePathComponent], and b: [ReferencePathComponent]) -> String {
166 | return format(verticalPadding(sections([
167 | (name: "Expected", body: lines(a.map { $0.description })),
168 | (name: "Actual", body: lines(b.map { $0.description })),
169 | ])))
170 | }
171 |
172 |
173 | static let allTests: [(String, (ReferencePathNormalizationTests) -> () throws -> Void)] = [
174 | ("testNormalize", testNormalize),
175 | ("testHint", testHint),
176 | ]
177 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Tests/MemoryLeakTestKitTests/MemoryLeakDetectorTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 | import MemoryLeakTestKit
4 |
5 |
6 |
7 | class MemoryLeakDetectorTests: XCTestCase {
8 | func testMemoryLeak() {
9 | typealias TestCase = (
10 | build: () -> Any,
11 | expected: MemoryLeakReport
12 | )
13 |
14 | let testCases: [UInt: TestCase] = [
15 | #line: ( // No circulars
16 | build: { () -> Node in
17 | return Node(linkedNodes: [])
18 | },
19 | expected: MemoryLeakReport(
20 | leakedObjects: []
21 | )
22 | ),
23 | #line: ( // Single direct circular
24 | build: { () -> Node in
25 | let node = Node(linkedNodes: [])
26 | node.linkedNodes = [node]
27 | return node
28 | },
29 | expected: MemoryLeakReport(
30 | leakedObjects: [
31 | LeakedObject(
32 | objectDescription: "Node",
33 | typeName: TypeName(text: "Node"),
34 | location: ReferencePath.root,
35 | circularPaths: [
36 | CircularReferencePath(
37 | end: .root(TypeName(text: "Node")),
38 | components: ArrayLongerThan1([
39 | .label("linkedNodes"),
40 | .index(0),
41 | ])!
42 | ),
43 | ]
44 | ),
45 | ]
46 | )
47 | ),
48 | #line: ( // Single indirect circular
49 | build: { () -> Node in
50 | let indirectNode = Node(linkedNodes: [])
51 | let node = Node(linkedNodes: [indirectNode])
52 | indirectNode.linkedNodes = [node]
53 | return node
54 | },
55 | expected: MemoryLeakReport(
56 | leakedObjects: [
57 | LeakedObject(
58 | objectDescription: "Node",
59 | typeName: TypeName(text: "Node"),
60 | location: ReferencePath.root,
61 | circularPaths: [
62 | CircularReferencePath(
63 | end: .root(TypeName(text: "Node")),
64 | components: ArrayLongerThan1([
65 | .label("linkedNodes"),
66 | .index(0),
67 | .label("linkedNodes"),
68 | .index(0),
69 | ])!
70 | )
71 | ]
72 | ),
73 | LeakedObject(
74 | objectDescription: "Node",
75 | typeName: TypeName(text: "Node"),
76 | location: ReferencePath(components: [
77 | .label("linkedNodes"),
78 | .index(0),
79 | ]),
80 | circularPaths: []
81 | ),
82 | ]
83 | )
84 | ),
85 | #line: ( // Both direct and indirect circulars
86 | build: { () -> Node in
87 | let indirectNode = Node(linkedNodes: [])
88 | let node = Node(linkedNodes: [indirectNode])
89 | indirectNode.linkedNodes = [node, indirectNode]
90 | return node
91 | },
92 | expected: MemoryLeakReport(
93 | leakedObjects: [
94 | LeakedObject(
95 | objectDescription: "Node",
96 | typeName: TypeName(text: "Node"),
97 | location: ReferencePath.root,
98 | circularPaths: [
99 | CircularReferencePath(
100 | end: .root(TypeName(text: "Node")),
101 | components: ArrayLongerThan1([
102 | .label("linkedNodes"),
103 | .index(0),
104 | .label("linkedNodes"),
105 | .index(0),
106 | ])!
107 | ),
108 | ]
109 | ),
110 | LeakedObject(
111 | objectDescription: "Node",
112 | typeName: TypeName(text: "Node"),
113 | location: ReferencePath(components: [
114 | .label("linkedNodes"),
115 | .index(0),
116 | ]),
117 | circularPaths: [
118 | CircularReferencePath(
119 | end: .intermediate(TypeName(text: "Node")),
120 | components: ArrayLongerThan1([
121 | .label("linkedNodes"),
122 | .index(1),
123 | ])!
124 | ),
125 | ]
126 | ),
127 | ]
128 | )
129 | ),
130 | #line: ( // Lazy
131 | build: { () -> LazyCircularNode in
132 | let node = LazyCircularNode()
133 | _ = node.indirect
134 | return node
135 | },
136 | expected: MemoryLeakReport(
137 | leakedObjects: [
138 | LeakedObject(
139 | objectDescription: "LazyCircularNode",
140 | typeName: TypeName(text: "LazyCircularNode"),
141 | location: ReferencePath.root,
142 | circularPaths: [
143 | CircularReferencePath(
144 | end: .root(TypeName(text: "LazyCircularNode")),
145 | components: ArrayLongerThan1([
146 | .label("indirect"),
147 | .label("value"),
148 | ])!
149 | ),
150 | ]
151 | ),
152 | LeakedObject(
153 | objectDescription: "Indirect",
154 | typeName: TypeName(text: "Indirect"),
155 | location: ReferencePath(components: [
156 | .label("indirect"),
157 | ]),
158 | circularPaths: []
159 | ),
160 | ]
161 | )
162 | ),
163 | #line: ( // No circular references but outer owner exists
164 | build: { () -> Node in
165 | let node = Node(linkedNodes: [])
166 | var anonymous: (() -> Void)?
167 |
168 | anonymous = {
169 | _ = node
170 | anonymous!()
171 | }
172 |
173 | return node
174 | },
175 | expected: MemoryLeakReport(
176 | leakedObjects: [
177 | LeakedObject(
178 | objectDescription: "Node",
179 | typeName: TypeName(text: "Node"),
180 | location: ReferencePath.root,
181 | circularPaths: []
182 | )
183 | ]
184 | )
185 | ),
186 | #line: ( // Circular references but anonymous instances at end
187 | build: { () -> Node in
188 | let node = Node(linkedNodes: [])
189 |
190 | ReferenceOwner.global.own(node)
191 |
192 | return Node(linkedNodes: [node])
193 | },
194 | expected: MemoryLeakReport(
195 | leakedObjects: [
196 | LeakedObject(
197 | objectDescription: "Node",
198 | typeName: TypeName(text: "Node"),
199 | location: ReferencePath(components: [
200 | .label("linkedNodes"),
201 | .index(0),
202 | ]),
203 | circularPaths: []
204 | )
205 | ]
206 | )
207 | ),
208 | ]
209 |
210 | testCases.forEach { tuple in
211 | let (line, (build: build, expected: expected)) = tuple
212 |
213 | let memoryLeakHints = detectLeaks(by: build)
214 |
215 | XCTAssertEqual(
216 | memoryLeakHints, expected,
217 | differenceMemoryLeakReport(between: expected, and: memoryLeakHints),
218 | line: line
219 | )
220 | }
221 | }
222 |
223 |
224 |
225 | private final class Node: CustomStringConvertible {
226 | var linkedNodes: [Node]
227 |
228 |
229 | init(linkedNodes: [Node]) {
230 | self.linkedNodes = linkedNodes
231 | }
232 |
233 |
234 | var description: String {
235 | return "Node"
236 | }
237 | }
238 |
239 |
240 |
241 | private final class LazyCircularNode: CustomStringConvertible {
242 | lazy var indirect: Indirect = Indirect(value: self)
243 |
244 |
245 | final class Indirect: CustomStringConvertible {
246 | let value: LazyCircularNode
247 |
248 |
249 | init(value: LazyCircularNode) {
250 | self.value = value
251 | }
252 |
253 |
254 | var description: String {
255 | return "Indirect"
256 | }
257 | }
258 |
259 |
260 | var description: String {
261 | return "LazyCircularNode"
262 | }
263 | }
264 |
265 |
266 |
267 | public final class ReferenceOwner {
268 | private var owned: [Any] = []
269 | private init() {}
270 |
271 |
272 | public func own(_ any: Any) {
273 | self.owned.append(any)
274 | }
275 |
276 |
277 | public static let global = ReferenceOwner()
278 | }
279 |
280 |
281 |
282 | private func differenceMemoryLeakReport(between a: MemoryLeakReport, and b: MemoryLeakReport) -> String {
283 | let missingLeakedObjects = sections(a.leakedObjects.subtracting(b.leakedObjects)
284 | .map { $0.descriptionLines })
285 |
286 | let extraLeakedObjects = sections(b.leakedObjects.subtracting(a.leakedObjects)
287 | .map { $0.descriptionLines })
288 |
289 | return format(verticalPadding(sections([
290 | (name: "Missing leaked objects", body: missingLeakedObjects),
291 | (name: "Extra leaked objects", body: extraLeakedObjects),
292 | ])))
293 | }
294 |
295 |
296 | static let allTests: [(String, (MemoryLeakDetectorTests) -> () throws -> Void)] = [
297 | ("testMemoryLeak", testMemoryLeak),
298 | ]
299 | }
--------------------------------------------------------------------------------
/Sources/MemoryLeakTestKit/PrefixedArray.swift.gyb:
--------------------------------------------------------------------------------
1 | %{
2 | number_of_generated = 3
3 | }%
4 |
5 |
6 | public struct PrefixedArray {
7 | public let prefix: Element
8 | public let rest: RestElements
9 |
10 |
11 | public var first: Element {
12 | return self.prefix
13 | }
14 |
15 |
16 | public var firstAndRest: (Element, RestElements) {
17 | return (self.first, self.rest)
18 | }
19 |
20 |
21 | public init(prefix: Element, _ rest: RestElements) {
22 | self.prefix = prefix
23 | self.rest = rest
24 | }
25 |
26 |
27 | public func dropFirst() -> RestElements {
28 | return self.rest
29 | }
30 | }
31 |
32 |
33 |
34 | extension PrefixedArray: Equatable where Element: Equatable, RestElements: Equatable {
35 | public static func ==(lhs: PrefixedArray, rhs: PrefixedArray) -> Bool {
36 | return lhs.prefix == rhs.prefix
37 | && lhs.rest == rhs.rest
38 | }
39 | }
40 |
41 |
42 |
43 | extension PrefixedArray: Hashable where Element: Hashable, RestElements: Hashable {
44 | public func hash(into hasher: inout Hasher) {
45 | hasher.combine(self.prefix)
46 | hasher.combine(self.rest)
47 | }
48 | }
49 |
50 |
51 |
52 | % for i in range(1, number_of_generated):
53 | extension PrefixedArray where RestElements == ArrayLongerThan${i - 1} {
54 | public func sequence() -> ArrayLongerThan${i}Sequence {
55 | return ArrayLongerThan${i}Sequence(
56 | prefix: self.prefix,
57 | rest: self.rest.sequence()
58 | )
59 | }
60 | }
61 | % end
62 |
63 |
64 |
65 | public typealias ArrayLongerThan0Sequence = ArrayLongerThan0
66 | % for i in range(1, number_of_generated):
67 | public typealias ArrayLongerThan${i}Sequence = PrefixedArraySequence>
68 | % end
69 |
70 |
71 |
72 | public struct PrefixedArraySequence: Sequence where RestElements.Element == E {
73 | public typealias Element = E
74 |
75 |
76 | public let prefix: Element
77 | public let rest: RestElements
78 |
79 |
80 | public init(prefix: Element, rest: RestElements) {
81 | self.prefix = prefix
82 | self.rest = rest
83 | }
84 |
85 |
86 | public func makeIterator() -> PrefixedArrayIterator {
87 | return PrefixedArrayIterator(iterate: self)
88 | }
89 | }
90 |
91 |
92 |
93 | public class PrefixedArrayIterator: IteratorProtocol {
94 | public typealias Element = RestElements.Element
95 |
96 |
97 | private var nextIterator: AnyIterator? = nil
98 | private let array: PrefixedArraySequence
99 |
100 |
101 | public init(iterate array: PrefixedArraySequence) {
102 | self.array = array
103 | }
104 |
105 |
106 | public func next() -> Element? {
107 | guard let nextIterator = self.nextIterator else {
108 | self.nextIterator = AnyIterator(self.array.rest.makeIterator())
109 | return self.array.prefix
110 | }
111 |
112 | let result = nextIterator.next()
113 | self.nextIterator = nextIterator
114 | return result
115 | }
116 | }
117 |
118 |
119 |
120 | public struct PrefixedArrayEnd {
121 | private let array: AnyBidirectionalCollection
122 |
123 |
124 | public var startIndex: Int {
125 | return 0
126 | }
127 |
128 |
129 | public var endIndex: Int {
130 | return self.count - 1
131 | }
132 |
133 |
134 | public var count: Int {
135 | return self.array.count
136 | }
137 |
138 |
139 | public var first: Element? {
140 | return self.array.first
141 | }
142 |
143 |
144 | public var last: Element? {
145 | return self.array.last
146 | }
147 |
148 |
149 | public var firstAndRest: (Element, PrefixedArrayEnd)? {
150 | guard let first = self.first, let rest = self.dropFirst() else {
151 | return nil
152 | }
153 |
154 | return (first, rest)
155 | }
156 |
157 |
158 | public init(
159 | _ array: S
160 | ) where S.Element == Element {
161 | self.array = AnyBidirectionalCollection(array)
162 | }
163 |
164 |
165 | public init(
166 | prefix: Element,
167 | _ array: S
168 | ) where S.Element == Element {
169 | var newArray = Array(array)
170 | newArray.insert(prefix, at: 0)
171 | self.init(newArray)
172 | }
173 |
174 |
175 | public init(
176 | suffix: Element,
177 | _ array: S
178 | ) where S.Element == Element {
179 | guard let first = array.first else {
180 | self.init(prefix: suffix, [])
181 | return
182 | }
183 |
184 | var newArray = Array(array.dropFirst())
185 | newArray.append(suffix)
186 |
187 | self.init(prefix: first, newArray)
188 | }
189 |
190 |
191 | public init(_ array: ArrayLongerThan0) {
192 | self = array
193 | }
194 |
195 |
196 | % for j in range(1, number_of_generated):
197 | public init(_ array: ArrayLongerThan${j}) {
198 | self.init(array.relaxed())
199 | }
200 | % end
201 |
202 |
203 | public init(
204 | suffix: Element,
205 | _ array: ArrayLongerThan0
206 | ) {
207 | self.init(suffix: suffix, array.relaxed())
208 | }
209 |
210 |
211 | public subscript(index: Int) -> Element {
212 | let index = self.array.index(self.array.startIndex, offsetBy: index)
213 | return self.array[index]
214 | }
215 |
216 |
217 | public subscript(range: Range) -> ArrayLongerThan0 {
218 | let upperBound = self.array.index(self.array.startIndex, offsetBy: range.upperBound)
219 | let lowerBound = self.array.index(self.array.startIndex, offsetBy: range.lowerBound)
220 |
221 | return ArrayLongerThan0(self.array[lowerBound..(newArray)
229 | }
230 |
231 |
232 | public mutating func insert(contentsOf newElements: C, at i: Int) where C.Element == Element {
233 | var newArray = Array(self.array)
234 | newArray.insert(contentsOf: newElements, at: i)
235 | self = PrefixedArrayEnd(newArray)
236 | }
237 |
238 |
239 | % for i in range(0, number_of_generated):
240 | public mutating func insert(contentsOf newElements: ArrayLongerThan${i}, at i: Int) {
241 | self.insert(contentsOf: newElements.relaxed(), at: i)
242 | }
243 | % end
244 |
245 |
246 | public mutating func append(_ newElement: Element) {
247 | var newArray = Array(self.array)
248 | newArray.append(newElement)
249 | self = PrefixedArrayEnd(newArray)
250 | }
251 |
252 |
253 | public mutating func append(
254 | contentsOf newElements: S
255 | ) where S.Element == Element {
256 | var newArray = Array(self.array)
257 | newArray.append(contentsOf: newElements)
258 | self = PrefixedArrayEnd(newArray)
259 | }
260 |
261 |
262 | % for i in range(0, number_of_generated):
263 | public mutating func append(contentsOf newElements: ArrayLongerThan${i}) {
264 | self.append(contentsOf: newElements.relaxed())
265 | }
266 | % end
267 |
268 |
269 | public func dropFirst() -> PrefixedArrayEnd? {
270 | guard !self.array.isEmpty else {
271 | return nil
272 | }
273 |
274 | return PrefixedArrayEnd(self.array.dropFirst())
275 | }
276 |
277 |
278 | public func dropLast() -> AnyBidirectionalCollection? {
279 | guard !self.array.isEmpty else {
280 | return nil
281 | }
282 |
283 | return self.array.dropLast()
284 | }
285 |
286 |
287 | public func map(_ f: (Element) throws -> T) rethrows -> PrefixedArrayEnd {
288 | return PrefixedArrayEnd(try self.array.map(f))
289 | }
290 |
291 |
292 | public func compactMap(_ f: (Element) throws -> T?) rethrows -> PrefixedArrayEnd {
293 | return PrefixedArrayEnd(try self.array.compactMap(f))
294 | }
295 |
296 |
297 | public func flatMap(_ f: (Element) throws -> S) rethrows -> PrefixedArrayEnd where S.Element == T {
298 | return PrefixedArrayEnd(try self.array.flatMap(f))
299 | }
300 |
301 |
302 | public func enumerated() -> PrefixedArrayEnd<(Int, Element)> {
303 | return PrefixedArrayEnd<(Int, Element)>(Array(self.array.enumerated()))
304 | }
305 |
306 |
307 | % for i in range(0, number_of_generated):
308 | public func flatMap(_ f: (Element) throws -> ArrayLongerThan${i}) rethrows -> PrefixedArrayEnd {
309 | return try self.flatMap { try f($0).relaxed() }
310 | }
311 | % end
312 |
313 |
314 | public func filter(_ f: (Element) throws -> Bool) rethrows -> PrefixedArrayEnd {
315 | return PrefixedArrayEnd(try self.array.filter(f))
316 | }
317 |
318 |
319 | public func relaxed() -> AnyBidirectionalCollection {
320 | return self.array
321 | }
322 |
323 |
324 | public func sequence() -> ArrayLongerThan0Sequence {
325 | return self
326 | }
327 | }
328 |
329 |
330 |
331 | extension PrefixedArrayEnd: Equatable where Element: Equatable {
332 | public static func ==(lhs: PrefixedArrayEnd, rhs: PrefixedArrayEnd) -> Bool {
333 | guard lhs.count == rhs.count else { return false }
334 | return zip(lhs.array, rhs.array).allSatisfy { $0.0 == $0.1 }
335 | }
336 | }
337 |
338 |
339 |
340 | extension PrefixedArrayEnd: Hashable where Element: Hashable {
341 | public func hash(into hasher: inout Hasher) {
342 | self.array.forEach { hasher.combine($0) }
343 | }
344 | }
345 |
346 |
347 |
348 | extension PrefixedArrayEnd: Sequence {
349 | public typealias Iterator = PrefixedArrayEndIterator
350 |
351 |
352 | public func makeIterator() -> PrefixedArrayEndIterator {
353 | return Iterator(self)
354 | }
355 | }
356 |
357 |
358 |
359 | public struct PrefixedArrayEndIterator: IteratorProtocol {
360 | private let array: PrefixedArrayEnd
361 | private var position: Int
362 |
363 |
364 | public init(_ array: PrefixedArrayEnd) {
365 | self.init(array, at: array.startIndex)
366 | }
367 |
368 |
369 | public init(_ array: PrefixedArrayEnd, at position: Int) {
370 | self.array = array
371 | self.position = position
372 | }
373 |
374 |
375 | public mutating func next() -> Element? {
376 | guard self.position <= self.array.endIndex else {
377 | return nil
378 | }
379 |
380 | let result = self.array[self.position]
381 | self.position += 1
382 | return result
383 | }
384 | }
385 |
386 |
387 |
388 | public typealias ArrayLongerThan0 = PrefixedArrayEnd
389 | % for i in range(1, number_of_generated):
390 | public typealias ArrayLongerThan${i} = PrefixedArray>
391 | % end
392 |
393 |
394 |
395 | % for i in range(1, number_of_generated):
396 | extension PrefixedArray where RestElements == ArrayLongerThan${i - 1} {
397 | // ArrayLongerThan${i}
398 | public var count: Int {
399 | return self.rest.count + 1
400 | }
401 |
402 |
403 | // ArrayLongerThan${i}
404 | public var last: Element {
405 | % if i > 1:
406 | return self.rest.last
407 | % else:
408 | guard let last = self.rest.last else {
409 | return self.first
410 | }
411 | return last
412 | % end
413 | }
414 |
415 |
416 | // ArrayLongerThan${i}
417 | public init?(_ array: C) where C.Element == Element {
418 | guard let first = array.first else {
419 | return nil
420 | }
421 |
422 | % if i == 1:
423 | let restEnoughLength = PrefixedArrayEnd(array.dropFirst())
424 | % else:
425 | guard let restEnoughLength = ArrayLongerThan${i - 1}(array.dropFirst()) else { return nil }
426 | % end
427 |
428 | self.init(prefix: first, restEnoughLength)
429 | }
430 |
431 |
432 | // ArrayLongerThan${i}
433 | public init?(_ array: ArrayLongerThan0) {
434 | guard let (first, rest) = array.firstAndRest else {
435 | return nil
436 | }
437 |
438 | % if i == 1:
439 | let restEnoughLength = ArrayLongerThan0(rest)
440 | % else:
441 | guard let restEnoughLength = ArrayLongerThan${i - 1}(rest) else {
442 | return nil
443 | }
444 | % end
445 |
446 | self.init(prefix: first, restEnoughLength)
447 | }
448 |
449 |
450 | % for j in range(1, number_of_generated):
451 | % if j < i:
452 | // ArrayLongerThan${i}
453 | public init?(_ array: ArrayLongerThan${j}) {
454 | % if i == 1:
455 | let restEnoughLength = ArrayLongerThan0(array.rest)
456 | % else:
457 | guard let restEnoughLength = ArrayLongerThan${i - 1}(array.rest) else {
458 | return nil
459 | }
460 | % end
461 |
462 | self.init(prefix: array.first, restEnoughLength)
463 | }
464 | % elif j == i:
465 | // ArrayLongerThan${i}
466 | public init(_ array: ArrayLongerThan${j}) {
467 | self = array
468 | }
469 | % else:
470 | // ArrayLongerThan${i}
471 | public init(_ array: ArrayLongerThan${j}) {
472 | self.init(prefix: array.first, array.rest.relaxed())
473 | }
474 | % end
475 | % end
476 |
477 |
478 | % for j in range(i, number_of_generated):
479 | // ArrayLongerThan${i}
480 | public init(prefix: Element, _ array: ArrayLongerThan${j}) {
481 | self.init(prefix: prefix, array.relaxed())
482 | }
483 |
484 |
485 | // ArrayLongerThan${i}
486 | public init(suffix: Element, _ array: ArrayLongerThan${j}) {
487 | self.init(suffix: suffix, array.relaxed())
488 | }
489 | % end
490 |
491 |
492 | % if i == 1:
493 | // ArrayLongerThan${i}
494 | public init(suffix: Element, _ array: ArrayLongerThan0) {
495 | guard let (first, rest) = array.firstAndRest else {
496 | self.init(prefix: suffix, PrefixedArrayEnd([]))
497 | return
498 | }
499 |
500 | self.init(
501 | prefix: first,
502 | ArrayLongerThan${i - 1}(suffix: suffix, rest)
503 | )
504 | }
505 | % else:
506 | public init(suffix: Element, _ array: ArrayLongerThan${i - 1}) {
507 | self.init(
508 | prefix: array.first,
509 | ArrayLongerThan${i - 1}(suffix: suffix, array.rest)
510 | )
511 | }
512 | % end
513 |
514 |
515 | % if i == 1:
516 | // ArrayLongerThan${i}
517 | public init(prefix: Element, _ array: C) where C.Element == Element {
518 | self.init(prefix: prefix, PrefixedArrayEnd(array))
519 | }
520 | % else:
521 | // ArrayLongerThan${i}
522 | public init?(prefix: Element, _ array: C) where C.Element == Element {
523 | guard let rest = ArrayLongerThan${i - 1}(array) else {
524 | return nil
525 | }
526 |
527 | self.init(prefix: prefix, rest)
528 | }
529 | % end
530 |
531 |
532 | % if i == 1:
533 | // ArrayLongerThan${i}
534 | public init(suffix: Element, _ array: C) where C.Element == Element {
535 | guard let first = array.first else {
536 | self.init(prefix: suffix, PrefixedArrayEnd([]))
537 | return
538 | }
539 |
540 | var newRest = Array(array.dropFirst())
541 | newRest.append(suffix)
542 |
543 | self.init(prefix: first, PrefixedArrayEnd(newRest))
544 | }
545 | % else:
546 | // ArrayLongerThan${i}
547 | public init?(suffix: Element, _ array: C) where C.Element == Element {
548 | guard let head = ArrayLongerThan${i - 1}(array) else {
549 | return nil
550 | }
551 |
552 | self.init(suffix: suffix, head)
553 | }
554 | % end
555 |
556 |
557 | // ArrayLongerThan${i}
558 | public subscript(index: Int) -> Element {
559 | guard index != 0 else {
560 | return self.first
561 | }
562 | return self.rest[index - 1]
563 | }
564 |
565 |
566 | // ArrayLongerThan${i}
567 | public subscript(range: Range) -> ArrayLongerThan0 {
568 | return self.relaxed()[range]
569 | }
570 |
571 |
572 | // ArrayLongerThan${i}
573 | public mutating func insert(_ newElement: Element, at i: Int) {
574 | guard i > 0 else {
575 | self = ArrayLongerThan${i}(
576 | prefix: newElement,
577 | self.relaxed()
578 | )
579 | return
580 | }
581 |
582 | var newRest = self.rest
583 | newRest.insert(newElement, at: i - 1)
584 |
585 | self = ArrayLongerThan${i}(
586 | prefix: self.prefix,
587 | newRest
588 | )
589 | }
590 |
591 |
592 | % for j in range(0, number_of_generated):
593 | // ArrayLongerThan${i}
594 | public mutating func insert(contentsOf newElements: ArrayLongerThan${j}, at i: Int) {
595 | // TODO: Check the standard behavior to handle negative values.
596 | guard i > 0 else {
597 | % if j == 0:
598 | guard let (first, rest) = newElements.firstAndRest else { return }
599 | % else:
600 | let (first, rest) = newElements.firstAndRest
601 | %end
602 |
603 | self = ArrayLongerThan${i}(
604 | prefix: first,
605 | rest + ArrayLongerThan${max(i - j, 0)}(self) // NOTE: Avoid to use exceeded length types.
606 | )
607 | return
608 | }
609 |
610 | var newRest = self.rest
611 | newRest.insert(contentsOf: newElements, at: i - 1)
612 |
613 | self = ArrayLongerThan${i}(
614 | prefix: self.prefix,
615 | newRest
616 | )
617 | }
618 | % end
619 |
620 |
621 | // ArrayLongerThan${i}
622 | public mutating func append(_ newElement: Element) {
623 | var newRest = self.rest
624 | newRest.append(newElement)
625 | self = ArrayLongerThan${i}(
626 | prefix: self.prefix,
627 | newRest
628 | )
629 | }
630 |
631 |
632 | % for j in range(0, number_of_generated):
633 | // ArrayLongerThan${i}
634 | public mutating func append(contentsOf newElements: ArrayLongerThan${j}) {
635 | var newRest = self.rest
636 | newRest.append(contentsOf: newElements)
637 | self = ArrayLongerThan${i}(
638 | prefix: self.prefix,
639 | newRest
640 | )
641 | }
642 | % end
643 |
644 |
645 | % if i == 1:
646 | // ArrayLongerThan${i}
647 | public func dropLast() -> PrefixedArrayEnd {
648 | guard let rest = self.rest.dropLast() else {
649 | return PrefixedArrayEnd([])
650 | }
651 |
652 | return PrefixedArrayEnd(
653 | prefix: self.first,
654 | rest
655 | )
656 | }
657 | % else:
658 | // ArrayLongerThan${i}
659 | public func dropLast() -> ArrayLongerThan${i - 1} {
660 | return ArrayLongerThan${i - 1}(
661 | prefix: self.first,
662 | rest.dropLast()
663 | )
664 | }
665 | % end
666 |
667 |
668 | // ArrayLongerThan${i}
669 | public func map(_ f: (Element) throws -> T) rethrows -> ArrayLongerThan${i} {
670 | return ArrayLongerThan${i}(
671 | prefix: try f(self.first),
672 | try self.rest.map(f)
673 | )
674 | }
675 |
676 |
677 | // ArrayLongerThan${i}
678 | public func compactMap(_ f: (Element) throws -> T?) rethrows -> PrefixedArrayEnd {
679 | return try self.relaxed().compactMap(f)
680 | }
681 |
682 |
683 | // ArrayLongerThan${i}
684 | public func filter(_ f: (Element) throws -> Bool) rethrows -> PrefixedArrayEnd {
685 | return try self.relaxed().filter(f)
686 | }
687 |
688 |
689 | // ArrayLongerThan${i}
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 | // ArrayLongerThan${i}
696 | public func enumerated() -> ArrayLongerThan${i}<(Int, Element)> {
697 | return ArrayLongerThan${i}<(Int, Element)>(
698 | prefix: (0, self.first),
699 | self.rest.enumerated().map { ($0.0 + 1, $0.1) }
700 | )
701 | }
702 |
703 |
704 | // ArrayLongerThan${i}
705 | public func relaxed() -> ArrayLongerThan${i - 1} {
706 | return ArrayLongerThan${i - 1}(
707 | prefix: self.prefix,
708 | self.rest.relaxed()
709 | )
710 | }
711 | }
712 | % end
713 |
714 |
715 | % for l in range(0, number_of_generated):
716 | public func +(lhs: ArrayLongerThan${l}, rhs: PrefixedArrayEnd) -> ArrayLongerThan${l} {
717 | var result = lhs
718 | result.append(contentsOf: rhs)
719 | return result
720 | }
721 | % for r in range(1, number_of_generated):
722 | % if l + r in range(0, number_of_generated):
723 | public func +(lhs: ArrayLongerThan${l}, rhs: ArrayLongerThan${r}) -> ArrayLongerThan${l + r} {
724 | return ArrayLongerThan${l + r}(suffix: rhs.last, lhs + rhs.dropLast())
725 | }
726 | % end
727 | % end
728 | % end
729 |
730 |
731 |
732 | extension PrefixedArrayEnd where Element: Sequence {
733 | public func joined() -> PrefixedArrayEnd {
734 | return PrefixedArrayEnd(Array(self.array.joined()))
735 | }
736 | }
737 |
738 |
739 |
740 | % for i in range(1, number_of_generated):
741 | extension PrefixedArray where Element: Sequence, RestElements == ArrayLongerThan${i - 1} {
742 | public func joined() -> PrefixedArrayEnd {
743 | return self.relaxed().joined()
744 | }
745 | }
746 | % end
747 |
748 |
749 |
750 | extension PrefixedArrayEnd where Element == String {
751 | public func joined(separator: String) -> String {
752 | return self.array.joined(separator: separator)
753 | }
754 | }
755 |
756 |
757 |
758 | % for i in range(1, number_of_generated):
759 | extension PrefixedArray where Element == String, RestElements == ArrayLongerThan${i - 1} {
760 | public func joined(separator: String) -> String {
761 | return self.relaxed().joined(separator: separator)
762 | }
763 | }
764 | % end
765 |
766 |
767 | func zip(_ a: ArrayLongerThan0, _ b: ArrayLongerThan0) -> ArrayLongerThan0<(A, B)> {
768 | return ArrayLongerThan0<(A, B)>(Array(zip(a.relaxed(), b.relaxed())))
769 | }
770 |
771 |
772 | % for i in range(1, number_of_generated):
773 | func zip(_ a: ArrayLongerThan${i}, _ b: ArrayLongerThan${i}) -> ArrayLongerThan${i}<(A, B)> {
774 | return ArrayLongerThan${i}<(A, B)>(
775 | prefix: (a.first, b.first),
776 | zip(a.rest, b.rest)
777 | )
778 | }
779 | % end
780 |
781 |
782 |
783 | func zip(_ a: A, _ b: B, _ c: C) -> [(A.Element, B.Element, C.Element)] {
784 | return zip(zip(a, b), c).map { ($0.0.0, $0.0.1, $0.1) }
785 | }
786 |
787 |
788 |
789 | func zip(_ a: ArrayLongerThan0, _ b: ArrayLongerThan0, _ c: ArrayLongerThan0) -> ArrayLongerThan0<(A, B, C)> {
790 | return ArrayLongerThan0<(A, B, C)>(Array(zip(a.relaxed(), b.relaxed(), c.relaxed())))
791 | }
792 |
793 |
794 |
795 | % for i in range(1, number_of_generated):
796 | func zip(_ a: ArrayLongerThan${i}, _ b: ArrayLongerThan${i}, _ c: ArrayLongerThan${i}) -> ArrayLongerThan${i}<(A, B, C)> {
797 | return ArrayLongerThan${i}<(A, B, C)>(
798 | prefix: (a.first, b.first, c.first),
799 | zip(a.rest, b.rest, c.rest)
800 | )
801 | }
802 | % end
803 |
--------------------------------------------------------------------------------
/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