├── Dangerfile
├── resources
├── result.png
└── xcuitests.png
├── LeaksDetector
├── leaksdetector
├── .gitignore
├── Sources
│ └── LeaksDetector
│ │ ├── CommandSteps
│ │ ├── RunCommandStep.swift
│ │ ├── CleanUp.swift
│ │ ├── PrepareStep.swift
│ │ ├── Report.swift
│ │ ├── SimulateUIFlow.swift
│ │ ├── GenerateMemgraphFile.swift
│ │ └── CheckLeaks.swift
│ │ ├── Helpers
│ │ ├── String+.swift
│ │ ├── Constants.swift
│ │ └── Helpers.swift
│ │ ├── LeaksDetector.swift
│ │ ├── Executor
│ │ ├── MaestroExecutor.swift
│ │ ├── Executor.swift
│ │ └── XCUITestExecutor.swift
│ │ └── Commands
│ │ ├── MaestroCommand.swift
│ │ ├── Processor.swift
│ │ └── XCUITestCommand.swift
├── Tests
│ └── LeaksDetectorTests
│ │ └── ProcessorTests.swift
├── Package.resolved
├── Package.swift
├── README.md
└── .swiftpm
│ └── xcode
│ └── xcshareddata
│ └── xcschemes
│ └── LeaksDetector.xcscheme
├── Gemfile
├── MemoryLeaksCheck
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── AppDelegate.swift
├── Scenarios
│ ├── AbandonedMemoryExampleVC.swift
│ └── LeaksExampleVC.swift
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── ViewController.swift
├── MemoryLeaksCheck.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ ├── tuanhoang.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ │ └── hoanganhtuan.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcshareddata
│ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ ├── xcbaselines
│ │ └── 4CBC59372AC3299B00D83CA5.xcbaseline
│ │ │ ├── A467896A-0CFB-4361-8284-254E52BCD3B2.plist
│ │ │ └── Info.plist
│ └── xcschemes
│ │ ├── LeaksCheckerUITests.xcscheme
│ │ └── MemoryLeaksCheck.xcscheme
├── xcuserdata
│ ├── hoanganhtuan.xcuserdatad
│ │ ├── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ └── tuanhoang.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── .github
├── ISSUE_TEMPLATE
│ ├── improve.md
│ ├── feature-request.md
│ └── bug-report.md
├── pull_request_template.md
└── workflows
│ └── check-leaks.yml
├── maestro
└── leaksCheckFlow.yaml
├── Dangerfile.leaksReport
├── Docs
├── Subcommand.md
├── Report.md
└── XCUITests.md
├── LeaksCheckerUITests
└── LeaksCheckerUITests.swift
├── Gemfile.lock
└── README.md
/Dangerfile:
--------------------------------------------------------------------------------
1 | essage("Hello")
--------------------------------------------------------------------------------
/resources/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangatuan/MemoryLeaksCheck/HEAD/resources/result.png
--------------------------------------------------------------------------------
/resources/xcuitests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangatuan/MemoryLeaksCheck/HEAD/resources/xcuitests.png
--------------------------------------------------------------------------------
/LeaksDetector/leaksdetector:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangatuan/MemoryLeaksCheck/HEAD/LeaksDetector/leaksdetector
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # gem "rails"
6 |
7 | gem 'danger'
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LeaksDetector/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcuserdata/hoanganhtuan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/RunCommandStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol RunCommandStep {
11 | func run() throws
12 | }
13 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/project.xcworkspace/xcuserdata/tuanhoang.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangatuan/MemoryLeaksCheck/HEAD/MemoryLeaksCheck.xcodeproj/project.xcworkspace/xcuserdata/tuanhoang.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/project.xcworkspace/xcuserdata/hoanganhtuan.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangatuan/MemoryLeaksCheck/HEAD/MemoryLeaksCheck.xcodeproj/project.xcworkspace/xcuserdata/hoanganhtuan.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/improve.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Improve a Feature
3 | about: Create a report to help us improve an existing feature
4 | title: IMPROVE -
5 | labels: improvement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe the feature you want to improve
11 |
12 |
13 | ## Describe alternatives you've considered
14 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Helpers/String+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | func matches(_ regex: String) -> Bool {
12 | return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/maestro/leaksCheckFlow.yaml:
--------------------------------------------------------------------------------
1 | appId: Hoang-Anh-Tuan.MemoryLeaksCheck
2 | ---
3 | - launchApp
4 | - tapOn: "Abandoned Memory Example"
5 | - tapOn: "Scenarios"
6 | - tapOn: "Leaks Memory Example"
7 | - tapOn: "Simulate Logout then Login Action"
8 | - tapOn: "Simulate Logout then Login Action"
9 | - tapOn: "Simulate Logout then Login Action"
10 | - tapOn: "Simulate Logout then Login Action"
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "snapkit",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/SnapKit/SnapKit",
7 | "state" : {
8 | "branch" : "develop",
9 | "revision" : "58320fe80522414bf3a7e24c88123581dc586752"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/CleanUp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 | import ShellOut
10 |
11 | struct CleanUp: RunCommandStep {
12 | let executor: Executor
13 |
14 | func run() throws {
15 | log(message: "Cleaning... 🧹")
16 | _ = try? shellOut(to: "rm \(Constants.leaksReportFileName)")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/PrepareStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang Anh on 18/8/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct PrepareStep: RunCommandStep {
11 |
12 | func run() throws {
13 | try? FileManager.default.removeItem(at: Constants.memgraphsFolder)
14 | try FileManager.default.createDirectory(at: Constants.memgraphsFolder, withIntermediateDirectories: true)
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Dangerfile.leaksReport:
--------------------------------------------------------------------------------
1 | # Function to read a message from a file
2 | def read_message_from_file(file_path)
3 | if File.exist?(file_path)
4 | File.read(file_path).strip
5 | else
6 | nil
7 | end
8 | end
9 |
10 | # Define the path to the file containing the message
11 | message_file = "./temporary.txt"
12 |
13 | # Read the message from the file
14 | message = read_message_from_file(message_file)
15 |
16 | # Check if the message is not empty
17 | if message && !message.empty?
18 | fail("Leaks detected:\n #{message}")
19 | else
20 | nil
21 | end
--------------------------------------------------------------------------------
/Docs/Subcommand.md:
--------------------------------------------------------------------------------
1 |
2 | # How to use specific subcommand
3 |
4 | 1. Maestro
5 |
6 | ## Maestro
7 |
8 | To use Maestro, you need to follow these steps:
9 |
10 | 1. Install Maestro. Learn more about `Maestro` [here](https://maestro.mobile.dev/)
11 | 2. Create a maestro flow to run simulate the flow in your app.
12 | 3. In your ci workflow, just call:
13 | ```bash
14 | leaksdetector maestro -p $YOUR_APP_NAME -maestro-flow-path $YOUR_MAESTRO_FILE_PATH
15 | ```
16 |
17 | 4. If you want to report the result to your Pull request/Slack/..., please check out the Report section.
18 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/LeaksDetector.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import ArgumentParser
4 | import ShellOut
5 | import Darwin
6 |
7 | @main
8 | struct LeaksDetector: ParsableCommand {
9 | static let configuration = CommandConfiguration(
10 | commandName: "leaksdetector",
11 | abstract: "This program wraps up the logic integrate leaks checking with your CI workflow",
12 | subcommands: [
13 | MaestroCommand.self,
14 | XCUITestCommand.self
15 | ],
16 | defaultSubcommand: MaestroCommand.self
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Suggest an idea for this project
4 | title: FEATURE -
5 | labels: feature request
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe the feature you want the app to have that it currently doesn't
11 | * A clear and concise description of what you want to happen.
12 |
13 | ## Describe alternatives you've considered
14 | * A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | ## Additional context
17 | * Add any other context or screenshots about the feature request here.
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Helpers/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 |
10 | enum Constants {
11 | static let leaksReportFileName = "leaksReport.txt"
12 | static var memgraphsFolder: URL {
13 | let currentDirectory = FileManager.default.currentDirectoryPath
14 | let memgraphsFolder = URL(fileURLWithPath: currentDirectory).appendingPathComponent("memgraphs")
15 | print("Memgraphs folder: \(memgraphsFolder.path)")
16 | return memgraphsFolder
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/Report.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 | import ShellOut
10 |
11 | struct DangerReportStep: RunCommandStep {
12 | let dangerFilePath: String
13 |
14 | func run() throws {
15 | do {
16 | try shellOut(to: "bundle exec danger --dangerfile=\(dangerFilePath) --danger_id=LeaksReport")
17 | } catch let error {
18 | log(message: "❌ Can not execute Danger", color: .red)
19 | throw error
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Executor/MaestroExecutor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang Anh on 18/8/24.
6 | //
7 |
8 | import Foundation
9 | import ShellOut
10 |
11 | final class MaestroExecutor: DefaultExecutor {
12 |
13 | /// Maestro needs a flow.yaml to start simulating UI
14 | private let flowPath: String
15 |
16 | init(flowPath: String) {
17 | self.flowPath = flowPath
18 | super.init()
19 | }
20 |
21 | override func simulateUI() throws {
22 | try shellOut(to: "maestro test \(flowPath)")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Docs/Report.md:
--------------------------------------------------------------------------------
1 |
2 | # Supported report process
3 |
4 | 1. Danger ✅
5 | 2. Slack (In progress)
6 |
7 | ## Danger
8 |
9 | 1. Create a new Dangerfile to handle the process checking for leaks.
10 | You can copy `Dangerfile.leaksReport` in the example project.
11 |
12 | 2. You can customize the Dangerfile to do your own things.
13 |
14 | ### Why do I need to create a separated Dangerfile?
15 |
16 | Because the process checking for leaks will trigger Danger to run.
17 | If you combine the leaks checking Dangerfile and your project Dangerfile to 1 Dangerfile only, the leaks process will execute your Danger logic as well.
18 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # What it Does
2 | * Closes #issueNumber
3 | * Describe what your change does
4 |
5 | # How I Tested
6 | * Add a list of steps to show the functionality of your feature
7 | For example:
8 | * Run the application
9 | * Tap the (+) button
10 | * See this screen
11 | etc...
12 |
13 | # Notes
14 | * Anything else that should be noted about how you implemented this feature?
15 |
16 | # Screenshot
17 | * Add a screenshot of your new feature! OR show a screen recording of it in action. (On the simulator press Cmd + R to record, and the stop button when done). **This video should be no longer than 30 seconds.**
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/SimulateUIFlow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import ShellOut
9 | import Foundation
10 |
11 | struct SimulateUIFlow: RunCommandStep {
12 | let executor: Executor
13 |
14 | func run() throws {
15 | log(message: "Start running ui flow... 🎥")
16 | do {
17 | try executor.simulateUI()
18 | } catch let error {
19 | let error = error as! ShellOutError
20 | log(message: "❌ Something went wrong when trying to capture ui flow. \(error.message)", color: .red)
21 | throw error
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/GenerateMemgraphFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct GenerateMemgraphFile: RunCommandStep {
11 | let executor: Executor
12 | let processName: String
13 |
14 | func run() throws {
15 | do {
16 | try executor.generateMemgraph(for: processName)
17 | log(message: "Generate memgraph successfully for process 🚀", color: .green)
18 | } catch let error {
19 | log(message: "❌ Can not find any process with name: \(processName)", color: .red)
20 | throw error
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Helpers/Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Hoang Anh Tuan on 30/09/2023.
6 | //
7 |
8 | import Foundation
9 | import TSCBasic
10 | import TSCUtility
11 |
12 | let terminalController = TerminalController(stream: stdoutStream)
13 |
14 | func log(
15 | message: @autoclosure () -> String,
16 | color: TerminalController.Color = .noColor,
17 | needEndline: Bool = true,
18 | isBold: Bool = false
19 | ) {
20 | #if DEBUG
21 | debugPrint(message())
22 | #else
23 | terminalController?.write(message(), inColor: color, bold: isBold)
24 |
25 | if needEndline {
26 | terminalController?.endLine()
27 | }
28 | #endif
29 | }
30 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MemoryLeaksCheck
4 | //
5 | // Created by Hoang Anh Tuan on 23/09/2023.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 |
17 | window = UIWindow(frame: UIScreen.main.bounds)
18 | window?.rootViewController = UINavigationController(rootViewController: ViewController())
19 | window?.makeKeyAndVisible()
20 |
21 | return true
22 | }
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Scenarios/AbandonedMemoryExampleVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbandonedMemoryLeaksVC.swift
3 | // MemoryLeaksCheck
4 | //
5 | // Created by Hoang Anh Tuan on 23/09/2023.
6 | //
7 |
8 | import UIKit
9 |
10 | class Person {
11 | var apartment: Apartment?
12 | }
13 |
14 | class Apartment {
15 | var person: Person?
16 | }
17 |
18 | final class AbandonedMemoryVC: UIViewController {
19 |
20 | let apartment = Apartment()
21 | let person = Person()
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | title = "Abandoned Memory Example"
27 | view.backgroundColor = .white
28 |
29 | apartment.person = person
30 | person.apartment = apartment
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Create a report about a feature that isn't working properly
4 | title: BUG -
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe the bug
11 | * A clear and concise description of what the bug is.
12 |
13 | ## To Reproduce
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Tap on this button '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | ## Expected behavior
21 | * A clear and concise description of what you expected to happen.
22 |
23 | ## Screenshots
24 | * If applicable, add screenshots to help explain your problem.
25 |
26 | ## Device Info (please complete the following information):
27 | * Device: [e.g. iPhone14]
28 | * OS: [e.g. iOS 16.0]
29 | * App Version [e.g. v1.5]
30 |
31 | ## Additional Context
32 | * Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcshareddata/xcbaselines/4CBC59372AC3299B00D83CA5.xcbaseline/A467896A-0CFB-4361-8284-254E52BCD3B2.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | LeaksCheckerUITests
8 |
9 | testExample()
10 |
11 | com.apple.dt.XCTMetric_Memory-Hoang-Anh-Tuan.MemoryLeaksCheck.physical
12 |
13 | baselineAverage
14 | 0.000000
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 |
18 | com.apple.dt.XCTMetric_Memory-Hoang-Anh-Tuan.MemoryLeaksCheck.physical_peak
19 |
20 | baselineAverage
21 | 0.000000
22 | baselineIntegrationDisplayName
23 | Local Baseline
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcuserdata/tuanhoang.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | LeaksCheckerUITests.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | MemoryLeaksCheck.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 | SnapKitPlayground (Playground) 1.xcscheme
18 |
19 | isShown
20 |
21 | orderHint
22 | 3
23 |
24 | SnapKitPlayground (Playground) 2.xcscheme
25 |
26 | isShown
27 |
28 | orderHint
29 | 4
30 |
31 | SnapKitPlayground (Playground).xcscheme
32 |
33 | isShown
34 |
35 | orderHint
36 | 2
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcuserdata/hoanganhtuan.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MemoryLeaksCheck.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | SnapKitPlayground (Playground) 1.xcscheme
13 |
14 | isShown
15 |
16 | orderHint
17 | 2
18 |
19 | SnapKitPlayground (Playground) 2.xcscheme
20 |
21 | isShown
22 |
23 | orderHint
24 | 3
25 |
26 | SnapKitPlayground (Playground).xcscheme
27 |
28 | isShown
29 |
30 | orderHint
31 | 1
32 |
33 |
34 | SuppressBuildableAutocreation
35 |
36 | 4C28E3C92ABEDFFB0004394E
37 |
38 | primary
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcshareddata/xcbaselines/4CBC59372AC3299B00D83CA5.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | A467896A-0CFB-4361-8284-254E52BCD3B2
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Dual-Core Intel Core i5
17 | cpuSpeedInMHz
18 | 2300
19 | logicalCPUCoresPerPackage
20 | 4
21 | modelCode
22 | MacBookPro14,1
23 | physicalCPUCoresPerPackage
24 | 2
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone15,2
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/LeaksCheckerUITests/LeaksCheckerUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeaksCheckerUITests.swift
3 | // LeaksCheckerUITests
4 | //
5 | // Created by Hoang Anh Tuan on 26/09/2023.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 |
11 | final class LeaksCheckerUITests: XCTestCase {
12 |
13 | var app: XCUIApplication!
14 |
15 | override func tearDownWithError() throws {
16 | app = nil
17 | super.tearDown()
18 | }
19 |
20 | /// This test will generate memgraph via command line. However, it only works on physical device, not simulator.
21 | /// For more info, please read README.
22 | func testMemoryLeaks1() throws {
23 | let app = XCUIApplication()
24 | let options = XCTMeasureOptions()
25 | options.iterationCount = 1
26 |
27 | measure(
28 | metrics: [XCTMemoryMetric(application: app)],
29 | options: options
30 | ) {
31 | app.launch()
32 | startMeasuring()
33 |
34 | app.staticTexts["Leaks Memory Example"].tap()
35 |
36 | let simulateLogoutThenLoginActionButton = app.buttons["Simulate Logout then Login Action"]
37 | simulateLogoutThenLoginActionButton.tap()
38 | simulateLogoutThenLoginActionButton.tap()
39 | simulateLogoutThenLoginActionButton.tap()
40 | simulateLogoutThenLoginActionButton.tap()
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LeaksDetector/Tests/LeaksDetectorTests/ProcessorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessorTests.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | //import XCTest
9 | //@testable import LeaksDetector
10 | //
11 | //final class ProcessorTests: XCTestCase {
12 | //
13 | // func testGenerateStepsCorrectly() {
14 | // let sut = Processor(
15 | // configuration: .init(processName: "", dangerFilePath: "A"),
16 | // executor: MaestroExecutor(flowPath: "abc")
17 | // )
18 | //
19 | // XCTAssertTypeEqual(sut.steps[0], PrepareStep.self)
20 | // XCTAssertTypeEqual(sut.steps[1], SimulateUIFlow.self)
21 | // XCTAssertTypeEqual(sut.steps[2], GenerateMemgraphFile.self)
22 | // XCTAssertTypeEqual(sut.steps[3], CheckLeaks.self)
23 | // XCTAssertTypeEqual(sut.steps[4], DangerReportStep.self)
24 | // XCTAssertTypeEqual(sut.steps[5], CleanUp.self)
25 | //
26 | // XCTAssertEqual(sut.steps.count, 6)
27 | // }
28 | //}
29 | //
30 | //public func XCTAssertTypeEqual(
31 | // _ lhs: Any?,
32 | // _ rhs: (some Any).Type,
33 | // file: StaticString = #filePath,
34 | // line: UInt = #line
35 | //) {
36 | // guard let lhs else {
37 | // return XCTFail("First argument should not be nil", file: file, line: line)
38 | // }
39 | //
40 | // if type(of: lhs) != rhs {
41 | // XCTFail("Expected \(rhs), got \(type(of: lhs))", file: file, line: line)
42 | // }
43 | //}
44 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Commands/MaestroCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import ArgumentParser
9 | import Foundation
10 |
11 | struct MaestroCommand: ParsableCommand {
12 | static let configuration = CommandConfiguration(
13 | commandName: "maestro",
14 | abstract: "Perform memory leaks check by using Maestro testing tool"
15 | )
16 |
17 | #if DEBUG
18 | private var processName = "MemoryLeaksCheck"
19 | private var maestroFlowPath: String = "/Users/hoanganhtuan/Desktop/MemoryLeaksCheck/maestro/leaksCheckFlow.yaml"
20 | private var dangerFilePath: String = "Dangerfile.leaksReport"
21 | #else
22 | @Option(name: .shortAndLong, help: "The name of the running process")
23 | private var processName: String
24 |
25 | @Option(name: .long, help: "The path to the maestro ui testing yaml file")
26 | private var maestroFlowPath: String
27 |
28 | @Option(name: .long, help: "The path to the Dangerfile")
29 | private var dangerFilePath: String?
30 | #endif
31 |
32 | public func run() throws {
33 | let executor = MaestroExecutor(flowPath: maestroFlowPath)
34 | let processor = Processor(
35 | configuration: .init(
36 | processName: processName,
37 | dangerFilePath: dangerFilePath
38 | ),
39 | executor: executor
40 | )
41 |
42 | processor.start()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Executor/Executor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Hoang Anh Tuan on 30/09/2023.
6 | //
7 |
8 | import Foundation
9 | import ShellOut
10 | import ArgumentParser
11 |
12 | protocol Executor {
13 | func simulateUI() throws
14 | func generateMemgraph(for processName: String) throws
15 | func memgraphPaths() -> [String]
16 | }
17 |
18 | class DefaultExecutor: Executor {
19 | init() { }
20 |
21 | func simulateUI() throws {
22 | fatalError("Need to override this func")
23 | }
24 |
25 | func generateMemgraph(for processName: String) throws {
26 | let memgraphPath = Constants.memgraphsFolder.path() + "/\(UUID().uuidString).memgraph"
27 | try shellOut(to: "leaks \(processName) --outputGraph=\(memgraphPath)")
28 | }
29 |
30 | func memgraphPaths() -> [String] {
31 | let fileManager = FileManager.default
32 | var memgraphFiles: [URL] = []
33 |
34 | // Create a directory enumerator to recursively go through the folder
35 | if let enumerator = fileManager.enumerator(at: Constants.memgraphsFolder, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) {
36 | // Iterate through the files and directories
37 | for case let fileURL as URL in enumerator {
38 | if fileURL.pathExtension == "memgraph" {
39 | memgraphFiles.append(fileURL)
40 | }
41 | }
42 | }
43 |
44 | return memgraphFiles.map { $0.path() }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Commands/Processor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Configuration {
11 | let processName: String
12 | let dangerFilePath: String?
13 | }
14 |
15 | struct Processor {
16 | let configuration: Configuration
17 | let executor: Executor
18 | let steps: [RunCommandStep]
19 |
20 | init(configuration: Configuration, executor: Executor) {
21 | self.configuration = configuration
22 | self.executor = executor
23 | var steps: [RunCommandStep] = [
24 | PrepareStep(),
25 | SimulateUIFlow(executor: executor),
26 | GenerateMemgraphFile(executor: executor, processName: configuration.processName),
27 | CheckLeaks(executor: executor)
28 | ]
29 |
30 | if let dangerFilePath = configuration.dangerFilePath {
31 | steps.append(DangerReportStep(dangerFilePath: dangerFilePath))
32 | }
33 |
34 | steps.append(CleanUp(executor: executor))
35 |
36 | self.steps = steps
37 | }
38 |
39 | private var processName: String {
40 | configuration.processName
41 | }
42 |
43 | func start() {
44 | do {
45 | try steps.forEach { step in
46 | try step.run()
47 | }
48 | log(message: "✅ The process finish successfully", color: .green)
49 | } catch {
50 | log(message: "❌ End the process with error", color: .red)
51 | Darwin.exit(EXIT_FAILURE)
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/LeaksDetector/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "shellout",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/JohnSundell/ShellOut.git",
7 | "state" : {
8 | "revision" : "e1577acf2b6e90086d01a6d5e2b8efdaae033568",
9 | "version" : "2.3.0"
10 | }
11 | },
12 | {
13 | "identity" : "swift-argument-parser",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/apple/swift-argument-parser",
16 | "state" : {
17 | "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
18 | "version" : "1.2.3"
19 | }
20 | },
21 | {
22 | "identity" : "swift-system",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/apple/swift-system.git",
25 | "state" : {
26 | "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496",
27 | "version" : "1.2.1"
28 | }
29 | },
30 | {
31 | "identity" : "swift-tools-support-core",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/apple/swift-tools-support-core.git",
34 | "state" : {
35 | "revision" : "3b13e439a341bbbfe0f710c7d1be37221745ef1a",
36 | "version" : "0.6.1"
37 | }
38 | },
39 | {
40 | "identity" : "xcresultkit",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/davidahouse/XCResultKit.git",
43 | "state" : {
44 | "revision" : "63a93e454e30084b8948cba0ee42112fca1f6010",
45 | "version" : "1.2.0"
46 | }
47 | }
48 | ],
49 | "version" : 2
50 | }
51 |
--------------------------------------------------------------------------------
/LeaksDetector/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
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: "LeaksDetector",
8 | platforms: [
9 | .macOS(.v13),
10 | .iOS(.v15)
11 | ],
12 | products: [
13 | .executable(name: "leaksdetector", targets: ["LeaksDetector"])
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
17 | .package(url: "https://github.com/JohnSundell/ShellOut.git", from: "2.0.0"),
18 | .package(url: "https://github.com/apple/swift-tools-support-core", from: "0.6.0"),
19 | .package(url: "https://github.com/davidahouse/XCResultKit.git", exact: "1.2.0"),
20 | ],
21 | targets: [
22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
23 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
24 | .executableTarget(
25 | name: "LeaksDetector",
26 | dependencies: [
27 | .product(name: "ArgumentParser", package: "swift-argument-parser"),
28 | "ShellOut",
29 | .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
30 | .product(name: "XCResultKit", package: "XCResultKit"),
31 | ]
32 | ),
33 | .testTarget(
34 | name: "LeaksDetectorTests",
35 | dependencies: ["LeaksDetector"]
36 | ),
37 | ]
38 | )
39 |
--------------------------------------------------------------------------------
/LeaksDetector/README.md:
--------------------------------------------------------------------------------
1 | # LeaksDetector
2 |
3 | A executable program to checks for leaks in a running program
4 |
5 | ## How it works?
6 |
7 | 1. It run `maestro` to capture memory graph. Find out about Maestro here: https://maestro.mobile.dev/
8 | 2. It use `leaks` tool by Apple to generate a `memgraph` file, which contains all the leaks info if any.
9 | 3. Check for leaks
10 | 4. If there are any leaks, use `Danger` to post a message & mark a PR as failed.
11 |
12 | ## How to execute?
13 |
14 | Just need to run the script:
15 |
16 | ```bash
17 | $ leaksdetector maestro -p $PROGRAM_NAME -maestroFlowPath $MAESTRO_PATH_FILE -dangerFilePath $DANGER_FILE_PATH
18 | ```
19 |
20 | ## How to run local
21 |
22 | 1. Open `MaestroCommand.swift`, change `processName`, `maestroFlowPath`, `dangerPath` to a hardcoded value
23 | 2. Run:
24 |
25 | ```bash
26 | $ swift run
27 | ```
28 |
29 | ## How to release new executable file?
30 |
31 | ```bash
32 | $ swift build -c release
33 | $ cd .build/release
34 | $ cp -f leaksdetector ./../../../
35 | ```
36 |
37 | ### References:
38 |
39 | - https://www.fivestars.blog/articles/ultimate-guide-swift-executables/
40 | - https://www.fivestars.blog/articles/a-look-into-argument-parser/
41 | - https://www.swiftbysundell.com/articles/building-a-command-line-tool-using-the-swift-package-manager/
42 | - https://www.avanderlee.com/swift/command-line-tool-package-manager/
43 | - Enum arguments: https://swiftinit.org/docs/swift-argument-parser/argumentparser/customizingcompletions
44 | - Split to multiple Danger instance: https://www.jessesquires.com/blog/2020/12/15/running-multiple-dangers/
45 | - Passing params to Danger: https://github.com/danger/swift/issues/213
46 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | addressable (2.8.5)
5 | public_suffix (>= 2.0.2, < 6.0)
6 | base64 (0.1.1)
7 | claide (1.1.0)
8 | claide-plugins (0.9.2)
9 | cork
10 | nap
11 | open4 (~> 1.3)
12 | colored2 (3.1.2)
13 | cork (0.3.0)
14 | colored2 (~> 3.1)
15 | danger (9.3.2)
16 | claide (~> 1.0)
17 | claide-plugins (>= 0.9.2)
18 | colored2 (~> 3.1)
19 | cork (~> 0.1)
20 | faraday (>= 0.9.0, < 3.0)
21 | faraday-http-cache (~> 2.0)
22 | git (~> 1.13)
23 | kramdown (~> 2.3)
24 | kramdown-parser-gfm (~> 1.0)
25 | no_proxy_fix
26 | octokit (~> 6.0)
27 | terminal-table (>= 1, < 4)
28 | faraday (2.7.11)
29 | base64
30 | faraday-net_http (>= 2.0, < 3.1)
31 | ruby2_keywords (>= 0.0.4)
32 | faraday-http-cache (2.5.0)
33 | faraday (>= 0.8)
34 | faraday-net_http (3.0.2)
35 | git (1.18.0)
36 | addressable (~> 2.8)
37 | rchardet (~> 1.8)
38 | kramdown (2.4.0)
39 | rexml
40 | kramdown-parser-gfm (1.1.0)
41 | kramdown (~> 2.0)
42 | nap (1.1.0)
43 | no_proxy_fix (0.1.2)
44 | octokit (6.1.1)
45 | faraday (>= 1, < 3)
46 | sawyer (~> 0.9)
47 | open4 (1.3.4)
48 | public_suffix (5.0.3)
49 | rchardet (1.8.0)
50 | rexml (3.2.6)
51 | ruby2_keywords (0.0.5)
52 | sawyer (0.9.2)
53 | addressable (>= 2.3.5)
54 | faraday (>= 0.17.3, < 3)
55 | terminal-table (3.0.2)
56 | unicode-display_width (>= 1.1.1, < 3)
57 | unicode-display_width (2.4.2)
58 |
59 | PLATFORMS
60 | ruby
61 |
62 | DEPENDENCIES
63 | danger
64 |
65 | BUNDLED WITH
66 | 2.1.4
67 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.github/workflows/check-leaks.yml:
--------------------------------------------------------------------------------
1 | # How to setup danger with github actions: https://www.jessesquires.com/blog/2020/04/10/running-danger-on-github-actions/
2 |
3 | name: Check leaks
4 | run-name: ${{ github.actor }} is checking for leaks 🚀
5 | on: [pull_request]
6 | jobs:
7 | Explore-Github-Actions:
8 | runs-on: self-hosted
9 | steps:
10 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
11 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
12 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
13 |
14 | - name: Check out repository code
15 | uses: actions/checkout@v4
16 |
17 | # - uses: maxim-lobanov/setup-xcode@v1.6.0
18 | # with:
19 | # xcode-version: 14.2.0
20 |
21 | - name: ruby versions
22 | run: |
23 | ruby --version
24 | gem --version
25 | bundler --version
26 |
27 | # Should help this step to install dependencies. Since this is example project, I will skip this step.
28 | # - name: ruby setup
29 | # uses: ruby/setup-ruby@v1
30 | # with:
31 | # ruby-version: 2.7
32 | # bundler-cache: true
33 |
34 | # - run: xcodebuild -project MemoryLeaksCheck.xcodeproj -scheme MemoryLeaksCheck -destination 'platform=iOS Simulator,name=iPhone 14 Pro' CONFIGURATION_BUILD_DIR=$PWD/build
35 |
36 | - name: Check for leaks
37 | env:
38 | DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
39 | run: ./LeaksDetector/leaksdetector --process-name MemoryLeaksCheck --executor-type maestro --maestro-flow-path ./maestro/leaksCheckFlow.yaml -d ./Dangerfile.leaksReport
40 |
41 | - run: echo "🍏 This job's status is ${{ job.status }}."
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Commands/XCUITestCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang Anh on 18/8/24.
6 | //
7 |
8 | import ArgumentParser
9 | import Foundation
10 |
11 | struct XCUITestCommand: ParsableCommand {
12 |
13 | static let configuration = CommandConfiguration(
14 | commandName: "xcuitest",
15 | abstract: "Perform memory leaks check by using XCUITest"
16 | )
17 |
18 | #if DEBUG
19 | private var project: String? = "/Users/tuanhoang/Documents/MemoryLeaksCheck/MemoryLeaksCheck.xcodeproj"
20 | private var workspace: String? = nil
21 | private var scheme: String = "MemoryLeaksCheck"
22 | private var destination: String = "'platform=iOS,name=Tuan iPhone'"
23 | #else
24 | @Option(name: .shortAndLong, help: "build the project NAME")
25 | private var project: String?
26 |
27 | @Option(name: .shortAndLong, help: "build the workspace NAME")
28 | private var workspace: String
29 |
30 | @Option(name: .long, help: "build the scheme NAME")
31 | private var scheme: String
32 |
33 | @Option(name: .long, help: "use the destination described by DESTINATIONSPECIFIER (a comma-separated set of key=value pairs describing the destination to use. Please note that the destination must be real iOS device")
34 | private var destination: String
35 | #endif
36 |
37 | public func run() throws {
38 | let executor = try XCUITestExecutor(
39 | project: project,
40 | workspace: workspace,
41 | scheme: scheme,
42 | destination: destination
43 | )
44 |
45 | let processor = Processor(
46 | configuration: .init(
47 | processName: "",
48 | dangerFilePath: nil
49 | ),
50 | executor: executor
51 | )
52 |
53 | processor.start()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Scenarios/LeaksExampleVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeaksExample.swift
3 | // MemoryLeaksCheck
4 | //
5 | // Created by Hoang Anh Tuan on 23/09/2023.
6 | //
7 |
8 | import UIKit
9 |
10 | final class HomeViewController: UIViewController {
11 |
12 | let viewModel = HomeViewModel()
13 |
14 | private let randomNumberLabel = UILabel()
15 | private let changeRootVCButton: UIButton = {
16 | let button = UIButton(type: .system)
17 | button.setTitle("Simulate Logout then Login Action", for: .normal)
18 | button.addTarget(self, action: #selector(simulateLogOutThenLogInBehaviour), for: .touchUpInside)
19 | return button
20 | }()
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | title = "Memory Leaks Example"
26 | view.backgroundColor = .white
27 |
28 | let randomInt = Int.random(in: 1..<1000)
29 | randomNumberLabel.text = "Random number: \(randomInt)"
30 |
31 | setupViews()
32 | }
33 |
34 | private func setupViews() {
35 | view.addSubview(randomNumberLabel)
36 | view.addSubview(changeRootVCButton)
37 |
38 | randomNumberLabel.snp.makeConstraints {
39 | $0.centerX.centerY.equalToSuperview()
40 | }
41 |
42 | changeRootVCButton.snp.makeConstraints {
43 | $0.top.equalTo(randomNumberLabel.snp.bottom)
44 | $0.centerX.equalToSuperview()
45 | }
46 | }
47 |
48 | @objc
49 | private func simulateLogOutThenLogInBehaviour() {
50 | print("Simulate logout then login action")
51 | guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
52 | appDelegate.window?.rootViewController = HomeViewController()
53 | }
54 | }
55 |
56 | final class HomeViewModel {
57 |
58 | var callback: ((Int) -> Void)?
59 | var number: Int = 5
60 |
61 | init() {
62 | callback = {
63 | self.number = $0
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/Docs/XCUITests.md:
--------------------------------------------------------------------------------
1 |
2 | # XCUITests
3 |
4 | After the tests finish execution, the essential thing that we need is the simulator needs to stay alive.
5 | The problem with `XCUITests` is that after testing finish execution, it quit the simulator.
6 |
7 | # Things I've tried:
8 |
9 | 1. Trying to find a way to preserve the running program after tests finish running
10 |
11 | - I can't find any way to preserve the running program.
12 |
13 | 2. At the end of a test, run shell script to generate `memgraph` before the running program quit
14 |
15 | For this idea, I put a breakpoint before the test finish execution. Then we can custom that breakpoint to execute a shell script command.
16 | However, when running on CI, we will execute test using script, not from Xcode. So, using breakpoint to execute shell script will not work for CI
17 |
18 | => Only work on Xcode
19 |
20 | 3. From Xcode13, Apple provide `-enablePerformanceTestsDiagnostics` to generate memgraph after a test finish executing.
21 |
22 | ```bash
23 | xcodebuild test -project MemoryLeaksCheck.xcodeproj \
24 | -scheme LeaksCheckerUITests \
25 | -destination platform=iOS,name="Tuan iPhone" \
26 | -enablePerformanceTestsDiagnostics YES
27 | ```
28 |
29 | > Note: In the scheme configuration, open `Options` under `Test`, unselect "Delete if test succeeds" for Attachments.
30 |
31 |
32 |
33 | However, **this only works for real device, not for simulator.**
34 |
35 | Based on [Apple docs](https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes)
36 |
37 | > xcodebuild has a new option -enablePerformanceTestsDiagnostics YES that collects diagnostics for Performance XCTests. The option collects a ktrace file for non-XCTMemoryMetrics, and a series of memory graphs for XCTMemoryMetrics. xcodebuild attaches diagnostics to the generated xcresult bundle. **Note that memory graph collection isn’t available in simulated devices. (64495534)**
38 |
39 | => Only work on local with real device, doesn't work on CI
40 |
41 | # Conclusion
42 |
43 | XCUITests is not appropriate for this approach. (for now)
44 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // MemoryLeaksCheck
4 | //
5 | // Created by Hoang Anh Tuan on 23/09/2023.
6 | //
7 |
8 | import UIKit
9 | import SnapKit
10 |
11 | enum ScenarioType: String {
12 | case abandoned = "Abandoned Memory Example"
13 | case leaks = "Leaks Memory Example"
14 | }
15 |
16 | final class ViewController: UIViewController {
17 |
18 | private let tableView = UITableView(frame: .zero, style: .grouped)
19 | private let scenarios: [ScenarioType] = [.abandoned, .leaks]
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | setupViews()
24 |
25 | self.title = "Scenarios"
26 | view.backgroundColor = .white
27 | navigationController?.navigationBar.prefersLargeTitles = true
28 | navigationController?.navigationBar.isTranslucent = true
29 | }
30 |
31 | private func setupViews() {
32 | view.addSubview(tableView)
33 |
34 | tableView.snp.makeConstraints {
35 | $0.top.equalToSuperview()
36 | $0.leading.trailing.bottom.equalToSuperview()
37 | }
38 |
39 | tableView.dataSource = self
40 | tableView.delegate = self
41 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CELL")
42 | tableView.rowHeight = UITableView.automaticDimension
43 | }
44 | }
45 |
46 | extension ViewController: UITableViewDataSource, UITableViewDelegate {
47 |
48 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
49 | scenarios.count
50 | }
51 |
52 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
53 | let cell = tableView.dequeueReusableCell(withIdentifier: "CELL", for: indexPath)
54 | cell.selectionStyle = .none
55 | cell.textLabel?.text = scenarios[indexPath.row].rawValue
56 | return cell
57 | }
58 |
59 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
60 | let scenario = scenarios[indexPath.row]
61 | switch scenario {
62 | case .abandoned:
63 | let vc = AbandonedMemoryVC()
64 | navigationController?.pushViewController(vc, animated: true)
65 | case .leaks:
66 | guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
67 | let vc = HomeViewController()
68 | appDelegate.window?.rootViewController = vc
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcshareddata/xcschemes/LeaksCheckerUITests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
16 |
17 |
21 |
22 |
26 |
27 |
28 |
29 |
31 |
37 |
38 |
39 |
40 |
41 |
51 |
52 |
56 |
57 |
61 |
62 |
63 |
64 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/CommandSteps/CheckLeaks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang on 6/1/24.
6 | //
7 |
8 | import Foundation
9 | import ShellOut
10 |
11 | enum LeaksCheckError: Error {
12 | case leaksCommandFailed
13 | case incorectOutputFormat
14 | }
15 |
16 | struct CheckLeaks: RunCommandStep {
17 | let executor: Executor
18 |
19 | private let regex: String = ".*(\\d+) leaks for (\\d+) total leaked bytes.*"
20 |
21 | func run() throws {
22 |
23 | if executor.memgraphPaths().isEmpty {
24 | log(message: "❌ Can not find any memgraphs!", color: .red)
25 | return
26 | }
27 |
28 | for memgraphPath in executor.memgraphPaths() {
29 | do {
30 | log(message: "Start checking for leaks... ⚙️")
31 |
32 | /// Running this script always throw error (somehow the leak tool throw error here) => So we need to process the memgraph in the `catch` block.
33 | try shellOut(to: "leaks", arguments: ["\(memgraphPath) -q"])
34 | } catch {
35 | let error = error as! ShellOutError
36 | if error.output.isEmpty {
37 | log(message: "❌ Leaks command run failed! Please open an issue on Github for me to check!", color: .red)
38 | throw LeaksCheckError.leaksCommandFailed
39 | }
40 |
41 | let inputs = error.output.components(separatedBy: "\n")
42 | guard let numberOfLeaksMessage = inputs.first(where: { $0.matches(regex) }) else {
43 | log(message: "❌ Generated leaks output is incorrect! Please open an issue on Github for me to check!", color: .red)
44 | throw LeaksCheckError.incorectOutputFormat
45 | }
46 |
47 | let numberOfLeaks = getNumberOfLeaks(from: numberOfLeaksMessage)
48 |
49 | if numberOfLeaks < 1 {
50 | log(message: "Scan successfully. Don't find any leaks in the program! ✅", color: .green)
51 | return
52 | }
53 |
54 | for message in inputs {
55 | let updatedMessage = "\"\(message)\""
56 | try shellOut(to: "echo \(updatedMessage) >> \(Constants.leaksReportFileName)")
57 | }
58 |
59 | log(message: "Founded leaks. Generating reports... ⚙️")
60 | }
61 | }
62 | }
63 |
64 | private func getMemgraphsURLs() -> [URL] {
65 | let fileManager = FileManager.default
66 | var memgraphFiles: [URL] = []
67 |
68 | do {
69 | // Get the URLs of all items in the folder
70 | let files = try fileManager.contentsOfDirectory(at: Constants.memgraphsFolder, includingPropertiesForKeys: nil, options: [])
71 |
72 | // Filter the files that end with ".memgraph"
73 | memgraphFiles = files.filter { $0.pathExtension == "memgraph" }
74 | } catch {
75 | print("Failed to read contents of directory: \(error.localizedDescription)")
76 | }
77 |
78 | return memgraphFiles
79 | }
80 |
81 | private func getNumberOfLeaks(from message: String) -> Int {
82 | if let regex = try? NSRegularExpression(pattern: regex, options: []) {
83 | // Find the first match in the input string
84 | if let match = regex.firstMatch(in: message, options: [], range: NSRange(message.startIndex..., in: message)) {
85 | // Extract the "d" value from the first capture group
86 | if let dRange = Range(match.range(at: 1), in: message), let dValue = Int(message[dRange]) {
87 | return dValue
88 | }
89 | }
90 | }
91 |
92 | return 0
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 🔎 CILeaksDetector
3 |
4 | 
5 |
6 | This is a simple tool to help you checking for leaks in your program automatically in the most accurate way, then it will send the report directly to your development workflow (Pull request/Slack/...).
7 | And even better, you can easily integrate this tool to your CI workflow.
8 |
9 |
10 |
11 | # Table of Contents
12 |
13 | - [Installation](#installation)
14 | - [Usage](#usage)
15 | - [Current supported testing frameworks](#current-supported-testing-frameworks)
16 | - [How?](#how)
17 | - [How to support your testing framework](#how-to-support-your-testing-framework)
18 | - [Publication](#publication)
19 |
20 | ## Installation
21 |
22 | You can go to [GitHub Releases](https://github.com/hoangatuan/MemoryLeaksCheck/releases) page to download release executable program.
23 |
24 | ## Usage
25 |
26 | The most simple way to run the leaks checking process is:
27 |
28 | ```bash
29 | leaksdetector $subcommand -p $YOUR_APP_NAME
30 | ```
31 |
32 | ### How to use subcommands
33 |
34 | Check out [this document](./Docs/Report.md) for how to use specific subcommands
35 |
36 | ### How to report the result to your development workflow
37 |
38 | Check out [this document](./Docs/Report.md) for how to customize the process to send the result to your workflow
39 |
40 | ## Current supported testing frameworks
41 |
42 | - [Maestro](https://maestro.mobile.dev/) ✅
43 | - [XCUITest](https://developer.apple.com/documentation/xctest) ✅ (Only support real device. Read more [here](./Docs/XCUITests.md))
44 | - [Appium](http://appium.io/) 🚧 (In progress)
45 |
46 | ### Why I used Maestro?
47 |
48 | 1. I need a testing tool which doesn't kill the program after the testing finished execution. And Maestro support that. Also Maestro is very easy to integrate & use.
49 | 2. XCUITest can not preserve running program after test execution on Simulator. Read more at [here](./Docs/XCUITests.md). However, Xcode support generate memgraph file for XCUITest when running test on real device => XCUITest is a great match for company uses real device farm for testing.
50 |
51 | ## How it works
52 |
53 | 1. Use a testing tool to simulate the UI flow in your app.
54 |
55 | 2. Generate `memgraph` using `leaks` tool provided by Apple.
56 | Find more about `leaks` tool and `memgraph` [here](https://developer.apple.com/videos/play/wwdc2018/416/)
57 |
58 | 3. Use `leaksdetector` program to proceed the `memgraph` file. If any leaks founded, it will use Danger to post a message to your PR/slack, ...
59 |
60 | ## How to support your testing framework
61 |
62 | If you're using another UI testing framework which also support preserve the execution of the program after finish testing, you can create another PR to update the `leaksdetector`.
63 | It's easy to do that, just need to follow these steps:
64 |
65 | 1. Open `Executor.swift`, create a new instance of your testing frameworks. Your new instance needs to conform to `Executor` protocol.
66 |
67 | ```swift
68 |
69 | struct XCUITestExecutor: Executor {
70 |
71 | func simulateUI() throws {
72 | // Custom logic to start simulating UI
73 | }
74 |
75 | func generateMemgraph(for processName: String) throws {
76 | // Custom logic to start generating memgraph for a `processName`
77 | }
78 |
79 | func memgraphPaths() -> [String]
80 | // return the path to the generated memgraphs
81 | }
82 | }
83 |
84 | ```
85 |
86 | 2. Go to `Commands` folder to create a new command for your testing framework. Please refer to `MaestroCommand`.
87 |
88 | ## Publication
89 |
90 | I've published an article about this on Medium. You can take a look at [here](https://medium.com/gitconnected/automating-memory-leak-detection-with-ci-integration-for-ios-380f08a55f0b)
91 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/xcshareddata/xcschemes/MemoryLeaksCheck.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
35 |
36 |
40 |
41 |
42 |
43 |
45 |
51 |
52 |
53 |
54 |
55 |
65 |
67 |
73 |
74 |
75 |
76 |
80 |
81 |
85 |
86 |
87 |
88 |
94 |
96 |
102 |
103 |
104 |
105 |
107 |
108 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/LeaksDetector/.swiftpm/xcode/xcshareddata/xcschemes/LeaksDetector.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
82 |
84 |
90 |
91 |
92 |
93 |
99 |
101 |
107 |
108 |
109 |
110 |
112 |
113 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/LeaksDetector/Sources/LeaksDetector/Executor/XCUITestExecutor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Tuan Hoang Anh on 18/8/24.
6 | //
7 |
8 | import Foundation
9 | import ShellOut
10 | import XCResultKit
11 |
12 | final class XCUITestExecutor: Executor {
13 |
14 | private let xcResultPath = "./TestResults"
15 |
16 | enum ProjectType {
17 | case workspace(path: String)
18 | case project(path: String)
19 |
20 | var argument: String {
21 | switch self {
22 | case .workspace(let path):
23 | return "-workspace \(path)"
24 | case .project(let path):
25 | return "-project \(path)"
26 | }
27 | }
28 | }
29 |
30 | let projectType: ProjectType
31 | let scheme: String
32 | let destination: String
33 |
34 | init(
35 | project: String?,
36 | workspace: String?,
37 | scheme: String,
38 | destination: String
39 | ) throws {
40 |
41 | if let project = project {
42 | self.projectType = .project(path: project)
43 | } else if let workspace = workspace {
44 | self.projectType = .workspace(path: workspace)
45 | } else {
46 | throw NSError(domain: "XCUITestExecutor", code: 0, userInfo: ["message": "Either project or workspace must be provided"])
47 | }
48 |
49 | self.scheme = scheme
50 | self.destination = destination
51 | }
52 |
53 | func simulateUI() throws {
54 | try shellOut(
55 | to: "xcodebuild",
56 | arguments: [
57 | "test",
58 | projectType.argument,
59 | "-scheme",
60 | scheme,
61 | "-destination",
62 | destination,
63 | "-enablePerformanceTestsDiagnostics",
64 | "YES",
65 | "-derivedDataPath",
66 | "./derived_data",
67 | "-resultBundlePath",
68 | xcResultPath,
69 | ]
70 | )
71 | }
72 |
73 | /// XCUITest already generate memgraph by default, so in this step we just need to copy the memgraph file to the correct folder
74 | func generateMemgraph(for processName: String) throws {
75 | /// https://www.polpiella.dev/automatically-detect-memory-leaks-using-ui-tests/?utm_campaign=iOS%20CI%20Newsletter&utm_medium=email&utm_source=iOS%20CI%20Newsletter%20Issue%2048&utm_content=aug_11_24
76 | guard let url = URL(string: xcResultPath) else { return }
77 | let result = XCResultFile(url: url)
78 | guard let invocationRecord = result.getInvocationRecord() else { return }
79 |
80 | let testBundles = invocationRecord
81 | .actions
82 | .compactMap { action -> ActionTestPlanRunSummaries? in
83 | guard let id = action.actionResult.testsRef?.id, let summaries = result.getTestPlanRunSummaries(id: id) else {
84 | return nil
85 | }
86 |
87 | return summaries
88 | }
89 | .flatMap(\.summaries)
90 | .flatMap(\.testableSummaries)
91 |
92 | let allTests = testBundles
93 | .flatMap(\.tests)
94 | .flatMap(\.subtestGroups)
95 | .flatMap(\.subtestGroups)
96 | .flatMap(\.subtests)
97 |
98 | let memoryGraphAttachments = allTests
99 | .compactMap { test -> ActionTestSummary? in
100 | guard let id = test.summaryRef?.id else { return nil }
101 | return result.getActionTestSummary(id: id)
102 | }
103 | .flatMap(\.activitySummaries)
104 | .filter { $0.title.contains("Added attachment named") && $0.title.contains(".memgraphset.zip") }
105 | .flatMap(\.attachments)
106 |
107 | for attachment in memoryGraphAttachments {
108 | let url = Constants.memgraphsFolder
109 | let filePath = url.appending(path: attachment.filename ?? "")
110 | result.exportAttachment(attachment: attachment, outputPath: url.path(percentEncoded: false))
111 |
112 | try shellOut(
113 | to: "tar",
114 | arguments: [
115 | "-zxvf",
116 | "\"\(filePath.path(percentEncoded: false))\"",
117 | "-C",
118 | url.path(percentEncoded: false)
119 | ]
120 | )
121 | }
122 | }
123 |
124 | func memgraphPaths() -> [String] {
125 | let fileManager = FileManager.default
126 | var memgraphFiles: [URL] = []
127 |
128 | // Create a directory enumerator to recursively go through the folder
129 | if let enumerator = fileManager.enumerator(at: Constants.memgraphsFolder, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) {
130 | // Iterate through the files and directories
131 | for case let fileURL as URL in enumerator {
132 | if fileURL.pathExtension == "memgraph" {
133 | memgraphFiles.append(fileURL)
134 | }
135 | }
136 | }
137 |
138 | let postMemgraphs: [String] = memgraphFiles
139 | .map {
140 | let unzippedAndEscaped = ($0.path(percentEncoded: false) as NSString)
141 | .replacingOccurrences(of: "(", with: "\\(")
142 | .replacingOccurrences(of: ")", with: "\\)")
143 | .replacingOccurrences(of: "[", with: "\\[")
144 | .replacingOccurrences(of: "]", with: "\\]")
145 |
146 | return unzippedAndEscaped
147 | }
148 | .filter {
149 | $0.contains("post_")
150 | }
151 |
152 | return postMemgraphs
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/MemoryLeaksCheck.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4C28E3CE2ABEDFFB0004394E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28E3CD2ABEDFFB0004394E /* AppDelegate.swift */; };
11 | 4C28E3D22ABEDFFB0004394E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28E3D12ABEDFFB0004394E /* ViewController.swift */; };
12 | 4C28E3D52ABEDFFB0004394E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4C28E3D32ABEDFFB0004394E /* Main.storyboard */; };
13 | 4C28E3D72ABEE0030004394E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4C28E3D62ABEE0030004394E /* Assets.xcassets */; };
14 | 4C28E3DA2ABEE0030004394E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4C28E3D82ABEE0030004394E /* LaunchScreen.storyboard */; };
15 | 4C28E3E32ABEE2D90004394E /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4C28E3E22ABEE2D90004394E /* SnapKit */; };
16 | 4C28E3E62ABEE50B0004394E /* AbandonedMemoryExampleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28E3E52ABEE50B0004394E /* AbandonedMemoryExampleVC.swift */; };
17 | 4C28E3E82ABF1DED0004394E /* LeaksExampleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28E3E72ABF1DED0004394E /* LeaksExampleVC.swift */; };
18 | 4CBC593B2AC3299B00D83CA5 /* LeaksCheckerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBC593A2AC3299B00D83CA5 /* LeaksCheckerUITests.swift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXContainerItemProxy section */
22 | 4CBC593E2AC3299B00D83CA5 /* PBXContainerItemProxy */ = {
23 | isa = PBXContainerItemProxy;
24 | containerPortal = 4C28E3C22ABEDFFB0004394E /* Project object */;
25 | proxyType = 1;
26 | remoteGlobalIDString = 4C28E3C92ABEDFFB0004394E;
27 | remoteInfo = MemoryLeaksCheck;
28 | };
29 | /* End PBXContainerItemProxy section */
30 |
31 | /* Begin PBXFileReference section */
32 | 4C28E3CA2ABEDFFB0004394E /* MemoryLeaksCheck.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MemoryLeaksCheck.app; sourceTree = BUILT_PRODUCTS_DIR; };
33 | 4C28E3CD2ABEDFFB0004394E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
34 | 4C28E3D12ABEDFFB0004394E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
35 | 4C28E3D42ABEDFFB0004394E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
36 | 4C28E3D62ABEE0030004394E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
37 | 4C28E3D92ABEE0030004394E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
38 | 4C28E3DB2ABEE0030004394E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
39 | 4C28E3E52ABEE50B0004394E /* AbandonedMemoryExampleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbandonedMemoryExampleVC.swift; sourceTree = ""; };
40 | 4C28E3E72ABF1DED0004394E /* LeaksExampleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaksExampleVC.swift; sourceTree = ""; };
41 | 4CBC59382AC3299B00D83CA5 /* LeaksCheckerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LeaksCheckerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
42 | 4CBC593A2AC3299B00D83CA5 /* LeaksCheckerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaksCheckerUITests.swift; sourceTree = ""; };
43 | /* End PBXFileReference section */
44 |
45 | /* Begin PBXFrameworksBuildPhase section */
46 | 4C28E3C72ABEDFFB0004394E /* Frameworks */ = {
47 | isa = PBXFrameworksBuildPhase;
48 | buildActionMask = 2147483647;
49 | files = (
50 | 4C28E3E32ABEE2D90004394E /* SnapKit in Frameworks */,
51 | );
52 | runOnlyForDeploymentPostprocessing = 0;
53 | };
54 | 4CBC59352AC3299B00D83CA5 /* Frameworks */ = {
55 | isa = PBXFrameworksBuildPhase;
56 | buildActionMask = 2147483647;
57 | files = (
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | /* End PBXFrameworksBuildPhase section */
62 |
63 | /* Begin PBXGroup section */
64 | 4C28E3C12ABEDFFB0004394E = {
65 | isa = PBXGroup;
66 | children = (
67 | 4C28E3CC2ABEDFFB0004394E /* MemoryLeaksCheck */,
68 | 4CBC59392AC3299B00D83CA5 /* LeaksCheckerUITests */,
69 | 4C28E3CB2ABEDFFB0004394E /* Products */,
70 | );
71 | sourceTree = "";
72 | };
73 | 4C28E3CB2ABEDFFB0004394E /* Products */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 4C28E3CA2ABEDFFB0004394E /* MemoryLeaksCheck.app */,
77 | 4CBC59382AC3299B00D83CA5 /* LeaksCheckerUITests.xctest */,
78 | );
79 | name = Products;
80 | sourceTree = "";
81 | };
82 | 4C28E3CC2ABEDFFB0004394E /* MemoryLeaksCheck */ = {
83 | isa = PBXGroup;
84 | children = (
85 | 4C28E3E42ABEE4FE0004394E /* Scenarios */,
86 | 4C28E3CD2ABEDFFB0004394E /* AppDelegate.swift */,
87 | 4C28E3D12ABEDFFB0004394E /* ViewController.swift */,
88 | 4C28E3D32ABEDFFB0004394E /* Main.storyboard */,
89 | 4C28E3D62ABEE0030004394E /* Assets.xcassets */,
90 | 4C28E3D82ABEE0030004394E /* LaunchScreen.storyboard */,
91 | 4C28E3DB2ABEE0030004394E /* Info.plist */,
92 | );
93 | path = MemoryLeaksCheck;
94 | sourceTree = "";
95 | };
96 | 4C28E3E42ABEE4FE0004394E /* Scenarios */ = {
97 | isa = PBXGroup;
98 | children = (
99 | 4C28E3E52ABEE50B0004394E /* AbandonedMemoryExampleVC.swift */,
100 | 4C28E3E72ABF1DED0004394E /* LeaksExampleVC.swift */,
101 | );
102 | path = Scenarios;
103 | sourceTree = "";
104 | };
105 | 4CBC59392AC3299B00D83CA5 /* LeaksCheckerUITests */ = {
106 | isa = PBXGroup;
107 | children = (
108 | 4CBC593A2AC3299B00D83CA5 /* LeaksCheckerUITests.swift */,
109 | );
110 | path = LeaksCheckerUITests;
111 | sourceTree = "";
112 | };
113 | /* End PBXGroup section */
114 |
115 | /* Begin PBXNativeTarget section */
116 | 4C28E3C92ABEDFFB0004394E /* MemoryLeaksCheck */ = {
117 | isa = PBXNativeTarget;
118 | buildConfigurationList = 4C28E3DE2ABEE0030004394E /* Build configuration list for PBXNativeTarget "MemoryLeaksCheck" */;
119 | buildPhases = (
120 | 4C28E3C62ABEDFFB0004394E /* Sources */,
121 | 4C28E3C72ABEDFFB0004394E /* Frameworks */,
122 | 4C28E3C82ABEDFFB0004394E /* Resources */,
123 | );
124 | buildRules = (
125 | );
126 | dependencies = (
127 | );
128 | name = MemoryLeaksCheck;
129 | packageProductDependencies = (
130 | 4C28E3E22ABEE2D90004394E /* SnapKit */,
131 | );
132 | productName = MemoryLeaksCheck;
133 | productReference = 4C28E3CA2ABEDFFB0004394E /* MemoryLeaksCheck.app */;
134 | productType = "com.apple.product-type.application";
135 | };
136 | 4CBC59372AC3299B00D83CA5 /* LeaksCheckerUITests */ = {
137 | isa = PBXNativeTarget;
138 | buildConfigurationList = 4CBC59402AC3299B00D83CA5 /* Build configuration list for PBXNativeTarget "LeaksCheckerUITests" */;
139 | buildPhases = (
140 | 4CBC59342AC3299B00D83CA5 /* Sources */,
141 | 4CBC59352AC3299B00D83CA5 /* Frameworks */,
142 | 4CBC59362AC3299B00D83CA5 /* Resources */,
143 | );
144 | buildRules = (
145 | );
146 | dependencies = (
147 | 4CBC593F2AC3299B00D83CA5 /* PBXTargetDependency */,
148 | );
149 | name = LeaksCheckerUITests;
150 | productName = LeaksCheckerUITests;
151 | productReference = 4CBC59382AC3299B00D83CA5 /* LeaksCheckerUITests.xctest */;
152 | productType = "com.apple.product-type.bundle.ui-testing";
153 | };
154 | /* End PBXNativeTarget section */
155 |
156 | /* Begin PBXProject section */
157 | 4C28E3C22ABEDFFB0004394E /* Project object */ = {
158 | isa = PBXProject;
159 | attributes = {
160 | BuildIndependentTargetsInParallel = 1;
161 | LastSwiftUpdateCheck = 1420;
162 | LastUpgradeCheck = 1420;
163 | TargetAttributes = {
164 | 4C28E3C92ABEDFFB0004394E = {
165 | CreatedOnToolsVersion = 14.2;
166 | };
167 | 4CBC59372AC3299B00D83CA5 = {
168 | CreatedOnToolsVersion = 14.2;
169 | TestTargetID = 4C28E3C92ABEDFFB0004394E;
170 | };
171 | };
172 | };
173 | buildConfigurationList = 4C28E3C52ABEDFFB0004394E /* Build configuration list for PBXProject "MemoryLeaksCheck" */;
174 | compatibilityVersion = "Xcode 14.0";
175 | developmentRegion = en;
176 | hasScannedForEncodings = 0;
177 | knownRegions = (
178 | en,
179 | Base,
180 | );
181 | mainGroup = 4C28E3C12ABEDFFB0004394E;
182 | packageReferences = (
183 | 4C28E3E12ABEE2D90004394E /* XCRemoteSwiftPackageReference "SnapKit" */,
184 | );
185 | productRefGroup = 4C28E3CB2ABEDFFB0004394E /* Products */;
186 | projectDirPath = "";
187 | projectRoot = "";
188 | targets = (
189 | 4C28E3C92ABEDFFB0004394E /* MemoryLeaksCheck */,
190 | 4CBC59372AC3299B00D83CA5 /* LeaksCheckerUITests */,
191 | );
192 | };
193 | /* End PBXProject section */
194 |
195 | /* Begin PBXResourcesBuildPhase section */
196 | 4C28E3C82ABEDFFB0004394E /* Resources */ = {
197 | isa = PBXResourcesBuildPhase;
198 | buildActionMask = 2147483647;
199 | files = (
200 | 4C28E3DA2ABEE0030004394E /* LaunchScreen.storyboard in Resources */,
201 | 4C28E3D72ABEE0030004394E /* Assets.xcassets in Resources */,
202 | 4C28E3D52ABEDFFB0004394E /* Main.storyboard in Resources */,
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | };
206 | 4CBC59362AC3299B00D83CA5 /* Resources */ = {
207 | isa = PBXResourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | };
213 | /* End PBXResourcesBuildPhase section */
214 |
215 | /* Begin PBXSourcesBuildPhase section */
216 | 4C28E3C62ABEDFFB0004394E /* Sources */ = {
217 | isa = PBXSourcesBuildPhase;
218 | buildActionMask = 2147483647;
219 | files = (
220 | 4C28E3E82ABF1DED0004394E /* LeaksExampleVC.swift in Sources */,
221 | 4C28E3D22ABEDFFB0004394E /* ViewController.swift in Sources */,
222 | 4C28E3E62ABEE50B0004394E /* AbandonedMemoryExampleVC.swift in Sources */,
223 | 4C28E3CE2ABEDFFB0004394E /* AppDelegate.swift in Sources */,
224 | );
225 | runOnlyForDeploymentPostprocessing = 0;
226 | };
227 | 4CBC59342AC3299B00D83CA5 /* Sources */ = {
228 | isa = PBXSourcesBuildPhase;
229 | buildActionMask = 2147483647;
230 | files = (
231 | 4CBC593B2AC3299B00D83CA5 /* LeaksCheckerUITests.swift in Sources */,
232 | );
233 | runOnlyForDeploymentPostprocessing = 0;
234 | };
235 | /* End PBXSourcesBuildPhase section */
236 |
237 | /* Begin PBXTargetDependency section */
238 | 4CBC593F2AC3299B00D83CA5 /* PBXTargetDependency */ = {
239 | isa = PBXTargetDependency;
240 | target = 4C28E3C92ABEDFFB0004394E /* MemoryLeaksCheck */;
241 | targetProxy = 4CBC593E2AC3299B00D83CA5 /* PBXContainerItemProxy */;
242 | };
243 | /* End PBXTargetDependency section */
244 |
245 | /* Begin PBXVariantGroup section */
246 | 4C28E3D32ABEDFFB0004394E /* Main.storyboard */ = {
247 | isa = PBXVariantGroup;
248 | children = (
249 | 4C28E3D42ABEDFFB0004394E /* Base */,
250 | );
251 | name = Main.storyboard;
252 | sourceTree = "";
253 | };
254 | 4C28E3D82ABEE0030004394E /* LaunchScreen.storyboard */ = {
255 | isa = PBXVariantGroup;
256 | children = (
257 | 4C28E3D92ABEE0030004394E /* Base */,
258 | );
259 | name = LaunchScreen.storyboard;
260 | sourceTree = "";
261 | };
262 | /* End PBXVariantGroup section */
263 |
264 | /* Begin XCBuildConfiguration section */
265 | 4C28E3DC2ABEE0030004394E /* Debug */ = {
266 | isa = XCBuildConfiguration;
267 | buildSettings = {
268 | ALWAYS_SEARCH_USER_PATHS = NO;
269 | CLANG_ANALYZER_NONNULL = YES;
270 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
272 | CLANG_ENABLE_MODULES = YES;
273 | CLANG_ENABLE_OBJC_ARC = YES;
274 | CLANG_ENABLE_OBJC_WEAK = YES;
275 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
276 | CLANG_WARN_BOOL_CONVERSION = YES;
277 | CLANG_WARN_COMMA = YES;
278 | CLANG_WARN_CONSTANT_CONVERSION = YES;
279 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
280 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
281 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
282 | CLANG_WARN_EMPTY_BODY = YES;
283 | CLANG_WARN_ENUM_CONVERSION = YES;
284 | CLANG_WARN_INFINITE_RECURSION = YES;
285 | CLANG_WARN_INT_CONVERSION = YES;
286 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
287 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
290 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
292 | CLANG_WARN_STRICT_PROTOTYPES = YES;
293 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
294 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
295 | CLANG_WARN_UNREACHABLE_CODE = YES;
296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
297 | COPY_PHASE_STRIP = NO;
298 | DEBUG_INFORMATION_FORMAT = dwarf;
299 | ENABLE_STRICT_OBJC_MSGSEND = YES;
300 | ENABLE_TESTABILITY = YES;
301 | GCC_C_LANGUAGE_STANDARD = gnu11;
302 | GCC_DYNAMIC_NO_PIC = NO;
303 | GCC_NO_COMMON_BLOCKS = YES;
304 | GCC_OPTIMIZATION_LEVEL = 0;
305 | GCC_PREPROCESSOR_DEFINITIONS = (
306 | "DEBUG=1",
307 | "$(inherited)",
308 | );
309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
311 | GCC_WARN_UNDECLARED_SELECTOR = YES;
312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
313 | GCC_WARN_UNUSED_FUNCTION = YES;
314 | GCC_WARN_UNUSED_VARIABLE = YES;
315 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
316 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
317 | MTL_FAST_MATH = YES;
318 | ONLY_ACTIVE_ARCH = YES;
319 | SDKROOT = iphoneos;
320 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
321 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
322 | };
323 | name = Debug;
324 | };
325 | 4C28E3DD2ABEE0030004394E /* Release */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | ALWAYS_SEARCH_USER_PATHS = NO;
329 | CLANG_ANALYZER_NONNULL = YES;
330 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
332 | CLANG_ENABLE_MODULES = YES;
333 | CLANG_ENABLE_OBJC_ARC = YES;
334 | CLANG_ENABLE_OBJC_WEAK = YES;
335 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
336 | CLANG_WARN_BOOL_CONVERSION = YES;
337 | CLANG_WARN_COMMA = YES;
338 | CLANG_WARN_CONSTANT_CONVERSION = YES;
339 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
341 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
342 | CLANG_WARN_EMPTY_BODY = YES;
343 | CLANG_WARN_ENUM_CONVERSION = YES;
344 | CLANG_WARN_INFINITE_RECURSION = YES;
345 | CLANG_WARN_INT_CONVERSION = YES;
346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
347 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
348 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
350 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
352 | CLANG_WARN_STRICT_PROTOTYPES = YES;
353 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
354 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
355 | CLANG_WARN_UNREACHABLE_CODE = YES;
356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
357 | COPY_PHASE_STRIP = NO;
358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
359 | ENABLE_NS_ASSERTIONS = NO;
360 | ENABLE_STRICT_OBJC_MSGSEND = YES;
361 | GCC_C_LANGUAGE_STANDARD = gnu11;
362 | GCC_NO_COMMON_BLOCKS = YES;
363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
365 | GCC_WARN_UNDECLARED_SELECTOR = YES;
366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
367 | GCC_WARN_UNUSED_FUNCTION = YES;
368 | GCC_WARN_UNUSED_VARIABLE = YES;
369 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
370 | MTL_ENABLE_DEBUG_INFO = NO;
371 | MTL_FAST_MATH = YES;
372 | SDKROOT = iphoneos;
373 | SWIFT_COMPILATION_MODE = wholemodule;
374 | SWIFT_OPTIMIZATION_LEVEL = "-O";
375 | VALIDATE_PRODUCT = YES;
376 | };
377 | name = Release;
378 | };
379 | 4C28E3DF2ABEE0030004394E /* Debug */ = {
380 | isa = XCBuildConfiguration;
381 | buildSettings = {
382 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
383 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
384 | CODE_SIGN_STYLE = Automatic;
385 | CURRENT_PROJECT_VERSION = 1;
386 | DEVELOPMENT_TEAM = RCQA85T6C2;
387 | GENERATE_INFOPLIST_FILE = YES;
388 | INFOPLIST_FILE = MemoryLeaksCheck/Info.plist;
389 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
390 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
391 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
392 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
393 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
394 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
395 | LD_RUNPATH_SEARCH_PATHS = (
396 | "$(inherited)",
397 | "@executable_path/Frameworks",
398 | );
399 | MARKETING_VERSION = 1.0;
400 | PRODUCT_BUNDLE_IDENTIFIER = "Hoang-Anh-Tuan.MemoryLeaksCheck";
401 | PRODUCT_NAME = "$(TARGET_NAME)";
402 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
403 | SUPPORTS_MACCATALYST = NO;
404 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
405 | SWIFT_EMIT_LOC_STRINGS = YES;
406 | SWIFT_VERSION = 5.0;
407 | TARGETED_DEVICE_FAMILY = 1;
408 | };
409 | name = Debug;
410 | };
411 | 4C28E3E02ABEE0030004394E /* Release */ = {
412 | isa = XCBuildConfiguration;
413 | buildSettings = {
414 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
415 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
416 | CODE_SIGN_STYLE = Automatic;
417 | CURRENT_PROJECT_VERSION = 1;
418 | DEVELOPMENT_TEAM = RCQA85T6C2;
419 | GENERATE_INFOPLIST_FILE = YES;
420 | INFOPLIST_FILE = MemoryLeaksCheck/Info.plist;
421 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
422 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
423 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
424 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
425 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
426 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
427 | LD_RUNPATH_SEARCH_PATHS = (
428 | "$(inherited)",
429 | "@executable_path/Frameworks",
430 | );
431 | MARKETING_VERSION = 1.0;
432 | PRODUCT_BUNDLE_IDENTIFIER = "Hoang-Anh-Tuan.MemoryLeaksCheck";
433 | PRODUCT_NAME = "$(TARGET_NAME)";
434 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
435 | SUPPORTS_MACCATALYST = NO;
436 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
437 | SWIFT_EMIT_LOC_STRINGS = YES;
438 | SWIFT_VERSION = 5.0;
439 | TARGETED_DEVICE_FAMILY = 1;
440 | };
441 | name = Release;
442 | };
443 | 4CBC59412AC3299B00D83CA5 /* Debug */ = {
444 | isa = XCBuildConfiguration;
445 | buildSettings = {
446 | CODE_SIGN_STYLE = Automatic;
447 | CURRENT_PROJECT_VERSION = 1;
448 | DEVELOPMENT_TEAM = RCQA85T6C2;
449 | GENERATE_INFOPLIST_FILE = YES;
450 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
451 | MARKETING_VERSION = 1.0;
452 | PRODUCT_BUNDLE_IDENTIFIER = "Hoang-Anh-Tuan.LeaksCheckerUITests";
453 | PRODUCT_NAME = "$(TARGET_NAME)";
454 | PROVISIONING_PROFILE = "";
455 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
456 | SUPPORTS_MACCATALYST = NO;
457 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
458 | SWIFT_EMIT_LOC_STRINGS = NO;
459 | SWIFT_VERSION = 5.0;
460 | TARGETED_DEVICE_FAMILY = 1;
461 | TEST_TARGET_NAME = MemoryLeaksCheck;
462 | };
463 | name = Debug;
464 | };
465 | 4CBC59422AC3299B00D83CA5 /* Release */ = {
466 | isa = XCBuildConfiguration;
467 | buildSettings = {
468 | CODE_SIGN_STYLE = Automatic;
469 | CURRENT_PROJECT_VERSION = 1;
470 | DEVELOPMENT_TEAM = RCQA85T6C2;
471 | GENERATE_INFOPLIST_FILE = YES;
472 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
473 | MARKETING_VERSION = 1.0;
474 | PRODUCT_BUNDLE_IDENTIFIER = "Hoang-Anh-Tuan.LeaksCheckerUITests";
475 | PRODUCT_NAME = "$(TARGET_NAME)";
476 | PROVISIONING_PROFILE = "";
477 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
478 | SUPPORTS_MACCATALYST = NO;
479 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
480 | SWIFT_EMIT_LOC_STRINGS = NO;
481 | SWIFT_VERSION = 5.0;
482 | TARGETED_DEVICE_FAMILY = 1;
483 | TEST_TARGET_NAME = MemoryLeaksCheck;
484 | };
485 | name = Release;
486 | };
487 | /* End XCBuildConfiguration section */
488 |
489 | /* Begin XCConfigurationList section */
490 | 4C28E3C52ABEDFFB0004394E /* Build configuration list for PBXProject "MemoryLeaksCheck" */ = {
491 | isa = XCConfigurationList;
492 | buildConfigurations = (
493 | 4C28E3DC2ABEE0030004394E /* Debug */,
494 | 4C28E3DD2ABEE0030004394E /* Release */,
495 | );
496 | defaultConfigurationIsVisible = 0;
497 | defaultConfigurationName = Release;
498 | };
499 | 4C28E3DE2ABEE0030004394E /* Build configuration list for PBXNativeTarget "MemoryLeaksCheck" */ = {
500 | isa = XCConfigurationList;
501 | buildConfigurations = (
502 | 4C28E3DF2ABEE0030004394E /* Debug */,
503 | 4C28E3E02ABEE0030004394E /* Release */,
504 | );
505 | defaultConfigurationIsVisible = 0;
506 | defaultConfigurationName = Release;
507 | };
508 | 4CBC59402AC3299B00D83CA5 /* Build configuration list for PBXNativeTarget "LeaksCheckerUITests" */ = {
509 | isa = XCConfigurationList;
510 | buildConfigurations = (
511 | 4CBC59412AC3299B00D83CA5 /* Debug */,
512 | 4CBC59422AC3299B00D83CA5 /* Release */,
513 | );
514 | defaultConfigurationIsVisible = 0;
515 | defaultConfigurationName = Release;
516 | };
517 | /* End XCConfigurationList section */
518 |
519 | /* Begin XCRemoteSwiftPackageReference section */
520 | 4C28E3E12ABEE2D90004394E /* XCRemoteSwiftPackageReference "SnapKit" */ = {
521 | isa = XCRemoteSwiftPackageReference;
522 | repositoryURL = "https://github.com/SnapKit/SnapKit";
523 | requirement = {
524 | branch = develop;
525 | kind = branch;
526 | };
527 | };
528 | /* End XCRemoteSwiftPackageReference section */
529 |
530 | /* Begin XCSwiftPackageProductDependency section */
531 | 4C28E3E22ABEE2D90004394E /* SnapKit */ = {
532 | isa = XCSwiftPackageProductDependency;
533 | package = 4C28E3E12ABEE2D90004394E /* XCRemoteSwiftPackageReference "SnapKit" */;
534 | productName = SnapKit;
535 | };
536 | /* End XCSwiftPackageProductDependency section */
537 | };
538 | rootObject = 4C28E3C22ABEDFFB0004394E /* Project object */;
539 | }
540 |
--------------------------------------------------------------------------------