├── Demo
├── ReactiveTimelaneDemo
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── ReactiveTimelaneDemo.entitlements
│ ├── App
│ │ ├── App.swift
│ │ ├── SceneDelegate.swift
│ │ └── AppDelegate.swift
│ ├── Info.plist
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ └── ViewController.swift
└── ReactiveTimelaneDemo.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
│ └── project.pbxproj
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── PULL_REQUEST_TEMPLATE.md
├── Tests
├── LinuxMain.swift
└── ReactiveTimelaneTests
│ ├── XCTestManifests.swift
│ ├── LifetimeTests.swift
│ ├── SignalProducerTests.swift
│ └── SignalTests.swift
├── .github
├── workflows
│ └── ci.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── Package.resolved
├── Package.swift
├── LICENSE
├── Sources
└── ReactiveTimelane
│ ├── Lifetime+Lane.swift
│ ├── SignalProducer+Lane.swift
│ └── Signal+Lane.swift
├── .gitignore
└── README.md
/Demo/ReactiveTimelaneDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 |
4 |
5 | ## Related issues
6 |
7 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import ReactiveTimelaneTests
3 |
4 | var tests = [XCTestCaseEntry]()
5 | tests += SignalTests.allTests()
6 | tests += SignalProducerTests.allTests()
7 | tests += LifetimeTests.allTests()
8 | XCTMain(tests)
9 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/ReactiveTimelaneDemo.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | runs-on: macOS-latest
6 | steps:
7 |
8 | - name: Checkout
9 | uses: actions/checkout@v1
10 |
11 | - name: Build
12 | run: swift build -v
13 |
14 | - name: Test
15 | run: swift test -v
16 |
--------------------------------------------------------------------------------
/Tests/ReactiveTimelaneTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(SignalTests.allTests),
7 | testCase(SignalProducerTests.allTests),
8 | testCase(LifetimeTests.allTests),
9 | ]
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/App/App.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | struct App {
4 | static func show(in window: UIWindow) {
5 | let viewController = ViewController()
6 | let navigationController = UINavigationController(rootViewController: viewController)
7 | navigationController.navigationBar.prefersLargeTitles = true
8 | viewController.title = "ReactiveTimelane"
9 | navigationController.title = "ReactiveTimelane"
10 | window.rootViewController = navigationController
11 | window.makeKeyAndVisible()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/App/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @available(iOS 13.0, *)
4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
5 |
6 | var window: UIWindow?
7 |
8 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
9 | options connectionOptions: UIScene.ConnectionOptions) {
10 | guard let windowScene = (scene as? UIWindowScene) else { return }
11 | let window = UIWindow(windowScene: windowScene)
12 | self.window = window
13 |
14 | App.show(in: window)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Is your feature request related to a problem?
11 |
12 |
13 | ## Describe the solution you'd like
14 |
15 |
16 | ## Describe alternatives you've considered
17 |
18 |
19 | ## Additional context
20 |
21 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "ReactiveSwift",
6 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "3f4351d04115fd8797802d9b2d17b812cd761602",
10 | "version": "6.3.0"
11 | }
12 | },
13 | {
14 | "package": "TimelaneCore",
15 | "repositoryURL": "https://github.com/icanzilb/TimelaneCore",
16 | "state": {
17 | "branch": null,
18 | "revision": "343792b6960f0bec2c7a8a84a4027506eddaf440",
19 | "version": "1.0.10"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe the bug
11 |
12 |
13 | ## To Reproduce
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | ## Expected behavior
21 |
22 |
23 | ## Screenshots
24 |
25 |
26 | ## Platform
27 |
28 | - Device:
29 | - OS:
30 | - Version of this library:
31 |
32 | ## Additional context
33 |
34 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "ReactiveTimelane",
6 | platforms: [
7 | .macOS(.v10_10), .iOS(.v9), .tvOS(.v9), .watchOS(.v2)
8 | ],
9 | products: [
10 | .library(name: "ReactiveTimelane", targets: ["ReactiveTimelane"]),
11 | ],
12 | dependencies: [
13 | .package(url: "https://github.com/icanzilb/TimelaneCore", from: "1.0.9"),
14 | .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", from: "6.3.0")
15 | ],
16 | targets: [
17 | .target(name: "ReactiveTimelane",
18 | dependencies: ["ReactiveSwift", "TimelaneCore"]),
19 | .testTarget(name: "ReactiveTimelaneTests",
20 | dependencies: [
21 | "ReactiveTimelane",
22 | "ReactiveSwift",
23 | .product(name: "TimelaneCoreTestUtils", package: "TimelaneCore")]),
24 | ],
25 | swiftLanguageVersions: [.v5]
26 | )
27 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @UIApplicationMain
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 |
6 | var window: UIWindow?
7 |
8 | func application(_ application: UIApplication,
9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
10 | guard #available(iOS 13.0, *) else {
11 | let window = UIWindow(frame: UIScreen.main.bounds)
12 | self.window = window
13 | App.show(in: window)
14 | return true
15 | }
16 | return true
17 | }
18 |
19 | @available(iOS 13.0, *)
20 | func application(_ application: UIApplication,
21 | configurationForConnecting connectingSceneSession: UISceneSession,
22 | options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
24 | configuration.delegateClass = SceneDelegate.self
25 | return configuration
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Niclas Kristek
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Tests/ReactiveTimelaneTests/LifetimeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import TimelaneCore
3 | import TimelaneCoreTestUtils
4 | import ReactiveSwift
5 | @testable import ReactiveTimelane
6 |
7 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)
8 | final class LifetimeTests: XCTestCase {
9 | override func setUp() {
10 | continueAfterFailure = false
11 | super.setUp()
12 | }
13 |
14 | // MARK: - Subscription
15 |
16 | func testSubscription() {
17 | let recorder = TestLog()
18 | Timelane.Subscription.didEmitVersion = true
19 |
20 | let (lifetime, token) = Lifetime.make()
21 | lifetime
22 | .lane("Test Subscription", logger: recorder.log)
23 |
24 | XCTAssertEqual(recorder.logged.count, 1)
25 | XCTAssertEqual(recorder.logged[0].signpostType, "begin")
26 | XCTAssertEqual(recorder.logged[0].subscribe, "Test Subscription")
27 |
28 | token.dispose()
29 |
30 | XCTAssertEqual(recorder.logged[1].signpostType, "end")
31 | }
32 |
33 | // MARK: - All tests
34 |
35 | static var allTests = [
36 | ("testSubscription", testSubscription),
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/ReactiveTimelane/Lifetime+Lane.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 | import TimelaneCore
4 |
5 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)
6 | public extension Lifetime {
7 | /**
8 | This operator logs the lifetime to the Timelane Instrument.
9 |
10 | - Note: You can download the Timelane Instrument from [timelane.tools](http://timelane.tools).
11 |
12 | - Parameter name: A name for the lane when visualized in Instruments.
13 |
14 | - Parameter file: The name of the current file.
15 |
16 | - Parameter function: The name of the current function.
17 |
18 | - Parameter line: The number of the current line.
19 |
20 | - Parameter logger: A logger which should be used to log the lifetime.
21 | */
22 | func lane(_ name: String,
23 | file: StaticString = #file,
24 | function: StaticString = #function,
25 | line: UInt = #line,
26 | logger: @escaping Timelane.Logger = Timelane.defaultLogger) {
27 | let fileName = file.description.components(separatedBy: "/").last!
28 | let source = "\(fileName):\(line) - \(function)"
29 | let subscription = Timelane.Subscription(name: name, logger: logger)
30 | subscription.begin(source: source)
31 | self += observeEnded {
32 | subscription.end(state: .completed)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/ReactiveTimelane/SignalProducer+Lane.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 | import TimelaneCore
4 |
5 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)
6 | public extension SignalProducer {
7 | /**
8 | This operator logs the lifetime of the subscription to the `SignalProducer` and its events to the Timelane Instrument.
9 |
10 | - Note: You can download the Timelane Instrument from [timelane.tools](http://timelane.tools).
11 |
12 | - Parameter name: A name for the lane when visualized in Instruments.
13 |
14 | - Parameter filter: Determines which metrics should be logged.
15 |
16 | - Parameter file: The name of the current file.
17 |
18 | - Parameter function: The name of the current function.
19 |
20 | - Parameter line: The number of the current line.
21 |
22 | - Parameter transformValue: An optional closure to format the subscription values for displaying in Instruments.
23 |
24 | - Parameter logger: A logger which should be used to log the specified metrics.
25 |
26 | - Returns: A `SignalProducer` where the specified metrics are logged for the Timelane Instrument.
27 | */
28 | func lane(_ name: String,
29 | filter: Timelane.LaneTypeOptions = .all,
30 | file: StaticString = #file,
31 | function: StaticString = #function,
32 | line: UInt = #line,
33 | transformValue transform: @escaping (Value) -> String = String.init(describing:),
34 | logger: @escaping Timelane.Logger = Timelane.defaultLogger) -> SignalProducer {
35 | lift { $0.lane(name, filter: filter, transformValue: transform, logger: logger) }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIRequiredDeviceCapabilities
31 |
32 | armv7
33 |
34 | UISupportedInterfaceOrientations
35 |
36 | UIInterfaceOrientationPortrait
37 | UIInterfaceOrientationLandscapeLeft
38 | UIInterfaceOrientationLandscapeRight
39 |
40 | UISupportedInterfaceOrientations~ipad
41 |
42 | UIInterfaceOrientationPortrait
43 | UIInterfaceOrientationPortraitUpsideDown
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/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 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Nimble",
6 | "repositoryURL": "https://github.com/Quick/Nimble.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "72f5a90d573f7f7d70aa6b8ad84b3e1e02eabb4d",
10 | "version": "8.0.9"
11 | }
12 | },
13 | {
14 | "package": "Quick",
15 | "repositoryURL": "https://github.com/Quick/Quick.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "33682c2f6230c60614861dfc61df267e11a1602f",
19 | "version": "2.2.0"
20 | }
21 | },
22 | {
23 | "package": "ReactiveCocoa",
24 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveCocoa",
25 | "state": {
26 | "branch": null,
27 | "revision": "8be399d8df3ca96e54f94c2a5091ce07c2cfe93c",
28 | "version": "10.3.0"
29 | }
30 | },
31 | {
32 | "package": "ReactiveSwift",
33 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift",
34 | "state": {
35 | "branch": null,
36 | "revision": "3f4351d04115fd8797802d9b2d17b812cd761602",
37 | "version": "6.3.0"
38 | }
39 | },
40 | {
41 | "package": "ReactiveTimelane",
42 | "repositoryURL": "https://github.com/nkristek/ReactiveTimelane",
43 | "state": {
44 | "branch": null,
45 | "revision": "3882e77ca7d0a6d0947b25cba02ae6658a5ec7b1",
46 | "version": "1.0.1"
47 | }
48 | },
49 | {
50 | "package": "TimelaneCore",
51 | "repositoryURL": "https://github.com/icanzilb/TimelaneCore",
52 | "state": {
53 | "branch": null,
54 | "revision": "343792b6960f0bec2c7a8a84a4027506eddaf440",
55 | "version": "1.0.10"
56 | }
57 | }
58 | ]
59 | },
60 | "version": 1
61 | }
62 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ReactiveTimelane
2 | [](https://github.com/nkristek/ReactiveTimelane/actions)
3 |
4 | **ReactiveTimelane** provides operators for `Signal`, `SignalProducer` and `Lifetime` in [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift) for profiling streams and lifetimes with the Timelane Instrument.
5 |
6 | #### Contents:
7 |
8 | - [Usage](#usage)
9 | - [API Reference](#api-reference)
10 | - [Installation](#installation)
11 | - [Contribution](#contribution)
12 |
13 | ## Usage
14 |
15 | > Before making use of ReactiveTimelane, you need to install the Timelane Instrument from http://timelane.tools
16 |
17 | Import the `ReactiveTimelane` framework in your code:
18 |
19 | ```swift
20 | import ReactiveTimelane
21 | ```
22 |
23 | Use the `lane(_:)` operator to profile a subscription via the TimelaneInstrument. Insert `lane(_:)` at the precise spot in your code you'd like to profile like so:
24 |
25 | ```swift
26 | let producer: SignalProducer = SignalProducer(value: ())
27 | producer
28 | .lane("Void producer")
29 | .start()
30 | ```
31 |
32 | Then profile your project by clicking **Product > Profile** in Xcode's main menu.
33 |
34 | For a more detailed walkthrough go to [http://timelane.tools](http://timelane.tools).
35 |
36 | ## API Reference
37 |
38 | ### `lane(_:filter:)`
39 |
40 | Use `lane("Lane name")` to send data to both the subscriptions and events lanes in the Timelane Instrument.
41 |
42 | `lane("Lane name", filter: .subscription)` sends begin/completion events to the Subscriptions lane. Use this syntax if you only want to observe the lifetime of the `Signal` / `SignalProducer`.
43 |
44 | `lane("Lane name", filter: .event)` sends events and values to the Events lane. Use this filter if you are only interested in the values the `Signal` / `SignalProducer` emits.
45 |
46 | Additionally you can transform the values logged in Timelane by using the optional `transformValue` trailing closure:
47 |
48 | ```swift
49 | lane("Lane name", transformValue: { "Value is \($0)" })
50 | ```
51 |
52 | ## Installation
53 |
54 | ### Swift Package Manager
55 |
56 | #### Automatically in Xcode:
57 |
58 | - Click **File > Swift Packages > Add Package Dependency...**
59 | - Use the package URL `https://github.com/nkristek/ReactiveTimelane` to add ReactiveTimelane to your project.
60 |
61 | #### Manually in your `Package.swift` file:
62 |
63 | ```swift
64 | .package(url: "https://github.com/nkristek/ReactiveTimelane", from: "1.1.0")
65 | ```
66 |
67 | ## Contribution
68 |
69 | If you find a bug feel free to open an issue. Contributions are also appreciated.
70 |
--------------------------------------------------------------------------------
/Sources/ReactiveTimelane/Signal+Lane.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 | import TimelaneCore
4 |
5 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)
6 | public extension Signal {
7 | /**
8 | This operator logs the lifetime of the subscription to the `Signal` and its events to the Timelane Instrument.
9 |
10 | - Note: You can download the Timelane Instrument from [timelane.tools](http://timelane.tools).
11 |
12 | - Parameter name: A name for the lane when visualized in Instruments.
13 |
14 | - Parameter filter: Determines which metrics should be logged.
15 |
16 | - Parameter file: The name of the current file.
17 |
18 | - Parameter function: The name of the current function.
19 |
20 | - Parameter line: The number of the current line.
21 |
22 | - Parameter transformValue: An optional closure to format the subscription values for displaying in Instruments.
23 |
24 | - Parameter logger: A logger which should be used to log the specified metrics.
25 |
26 | - Returns: A `Signal` where the specified metrics are logged for the Timelane Instrument.
27 | */
28 | func lane(_ name: String,
29 | filter: Timelane.LaneTypeOptions = .all,
30 | file: StaticString = #file,
31 | function: StaticString = #function,
32 | line: UInt = #line,
33 | transformValue transform: @escaping (Value) -> String = String.init(describing:),
34 | logger: @escaping Timelane.Logger = Timelane.defaultLogger) -> Signal {
35 | let fileName = file.description.components(separatedBy: "/").last!
36 | let source = "\(fileName):\(line) - \(function)"
37 | let subscription = Timelane.Subscription(name: name, logger: logger)
38 |
39 | if filter.contains(.subscription) {
40 | subscription.begin(source: source)
41 | }
42 |
43 | return on(
44 | failed: { error in
45 | if filter.contains(.subscription) {
46 | subscription.end(state: .error(error.localizedDescription))
47 | }
48 |
49 | if filter.contains(.event) {
50 | subscription.event(value: .error(error.localizedDescription), source: source)
51 | }
52 | }, completed: {
53 | if filter.contains(.subscription) {
54 | subscription.end(state: .completed)
55 | }
56 |
57 | if filter.contains(.event) {
58 | subscription.event(value: .completion, source: source)
59 | }
60 | }, interrupted: {
61 | if filter.contains(.subscription) {
62 | subscription.end(state: .cancelled)
63 | }
64 |
65 | if filter.contains(.event) {
66 | subscription.event(value: .cancelled, source: source)
67 | }
68 | }, value: { value in
69 | if filter.contains(.event) {
70 | subscription.event(value: .value(transform(value)), source: source)
71 | }
72 | })
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import ReactiveTimelane
5 |
6 | final class ViewController: UIViewController {
7 |
8 | // MARK: - Properties
9 |
10 | private let timedValueProducer: SignalProducer<(), Never> = {
11 | SignalProducer(value: ())
12 | .delay(5, on: QueueScheduler.main)
13 | .lane("Timed value producer")
14 | }()
15 |
16 | fileprivate enum Errors: Error {
17 | case somethingWentWrong
18 | }
19 |
20 | private let timedErrorProducer: SignalProducer<(), Errors> = {
21 | SignalProducer(value: ())
22 | .delay(5, on: QueueScheduler.main)
23 | .flatMap(.latest) { SignalProducer(error: .somethingWentWrong).lane("Inner error producer") }
24 | .lane("Timed error producer")
25 | }()
26 |
27 | private var (subscriptionLifetime, subscriptionToken) = Lifetime.make()
28 |
29 | // MARK: - Lifecycle
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 | setUpView()
34 | setUpBindings()
35 | }
36 |
37 | private func setUpBindings() {
38 | reactive.lifetime += startValueProducerButton.reactive
39 | .mapControlEvents(.touchUpInside, { _ in () })
40 | .observeValues { [weak self] in
41 | guard let strongSelf = self else { return }
42 | strongSelf.subscriptionLifetime += strongSelf.timedValueProducer.start()
43 | }
44 |
45 | reactive.lifetime += startErrorProducerButton.reactive
46 | .mapControlEvents(.touchUpInside, { _ in () })
47 | .observeValues { [weak self] in
48 | guard let strongSelf = self else { return }
49 | strongSelf.subscriptionLifetime += strongSelf.timedErrorProducer.start()
50 | }
51 |
52 | reactive.lifetime += stopButton.reactive
53 | .mapControlEvents(.touchUpInside, { _ in () })
54 | .observeValues { [weak self] in
55 | guard let strongSelf = self else { return }
56 | (strongSelf.subscriptionLifetime, strongSelf.subscriptionToken) = Lifetime.make()
57 | }
58 | }
59 |
60 | // MARK: - View
61 |
62 | private lazy var buttonStack: UIStackView = {
63 | let stack = UIStackView(arrangedSubviews: [
64 | startValueProducerButton,
65 | startErrorProducerButton,
66 | stopButton
67 | ])
68 | stack.axis = .vertical
69 | stack.spacing = 24
70 | return stack
71 | }()
72 |
73 | private let startValueProducerButton: UIButton = {
74 | let button = UIButton(type: .system)
75 | button.setTitle("Start value producer", for: .normal)
76 | return button
77 | }()
78 |
79 | private let startErrorProducerButton: UIButton = {
80 | let button = UIButton(type: .system)
81 | button.setTitle("Start error producer", for: .normal)
82 | return button
83 | }()
84 |
85 | private let stopButton: UIButton = {
86 | let button = UIButton(type: .system)
87 | button.setTitle("Stop all", for: .normal)
88 | return button
89 | }()
90 |
91 | private func setUpView() {
92 | if #available(iOS 13.0, *) {
93 | view.backgroundColor = .systemBackground
94 | } else {
95 | view.backgroundColor = .white
96 | }
97 | view.addSubview(buttonStack)
98 | buttonStack.translatesAutoresizingMaskIntoConstraints = false
99 | NSLayoutConstraint.activate([
100 | buttonStack.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
101 | buttonStack.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
102 | ])
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Tests/ReactiveTimelaneTests/SignalProducerTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import TimelaneCore
3 | import TimelaneCoreTestUtils
4 | import ReactiveSwift
5 | @testable import ReactiveTimelane
6 |
7 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)
8 | final class SignalProducerTests: XCTestCase {
9 | override func setUp() {
10 | continueAfterFailure = false
11 | super.setUp()
12 | }
13 |
14 | // MARK: - Events
15 |
16 | func testCompletedEvent() {
17 | let recorder = TestLog()
18 | Timelane.Subscription.didEmitVersion = true
19 |
20 | SignalProducer { observer, _ in observer.sendCompleted() }
21 | .lane("Test Subscription", filter: .event, logger: recorder.log)
22 | .start()
23 |
24 | XCTAssertEqual(recorder.logged.count, 1)
25 | XCTAssertEqual(recorder.logged[0].type, "Completed")
26 | XCTAssertEqual(recorder.logged[0].subscription, "Test Subscription")
27 | }
28 |
29 | func testValueEvents() {
30 | let recorder = TestLog()
31 | Timelane.Subscription.didEmitVersion = true
32 |
33 | SignalProducer(values: 1, 2, 3)
34 | .lane("Test Subscription", filter: .event, logger: recorder.log)
35 | .start()
36 |
37 | XCTAssertEqual(recorder.logged.count, 4)
38 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, 1")
39 | XCTAssertEqual(recorder.logged[1].outputTldr, "Output, Test Subscription, 2")
40 | XCTAssertEqual(recorder.logged[2].outputTldr, "Output, Test Subscription, 3")
41 | XCTAssertEqual(recorder.logged[3].type, "Completed")
42 | XCTAssertEqual(recorder.logged[3].subscription, "Test Subscription")
43 | }
44 |
45 | func testFormatting() {
46 | let recorder = TestLog()
47 | Timelane.Subscription.didEmitVersion = true
48 |
49 | SignalProducer(value: 1)
50 | .lane("Test Subscription",
51 | filter: .event,
52 | transformValue: { "TEST \($0)" },
53 | logger: recorder.log)
54 | .start()
55 | XCTAssertEqual(recorder.logged.count, 2)
56 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, TEST 1")
57 | }
58 |
59 | enum TestError: LocalizedError {
60 | case somethingWentWrong
61 | var errorDescription: String? { "Error description" }
62 | }
63 |
64 | func testErrorEvent() {
65 | let recorder = TestLog()
66 | Timelane.Subscription.didEmitVersion = true
67 |
68 | SignalProducer(error: .somethingWentWrong)
69 | .lane("Test Subscription", filter: .event, logger: recorder.log)
70 | .start()
71 |
72 | XCTAssertEqual(recorder.logged.count, 1)
73 | XCTAssertEqual(recorder.logged[0].type, "Error")
74 | XCTAssertEqual(recorder.logged[0].value, TestError.somethingWentWrong.errorDescription)
75 | }
76 |
77 | func testErrorEventAfterValues() {
78 | let recorder = TestLog()
79 | Timelane.Subscription.didEmitVersion = true
80 |
81 | SignalProducer(values: 1, 2, 3)
82 | .concat(error: .somethingWentWrong)
83 | .lane("Test Subscription", filter: .event, logger: recorder.log)
84 | .start()
85 |
86 | XCTAssertEqual(recorder.logged.count, 4)
87 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, 1")
88 | XCTAssertEqual(recorder.logged[1].outputTldr, "Output, Test Subscription, 2")
89 | XCTAssertEqual(recorder.logged[2].outputTldr, "Output, Test Subscription, 3")
90 | XCTAssertEqual(recorder.logged[3].type, "Error")
91 | XCTAssertEqual(recorder.logged[3].value, TestError.somethingWentWrong.errorDescription)
92 | }
93 |
94 | func testCancelledEvent() {
95 | let recorder = TestLog()
96 | Timelane.Subscription.didEmitVersion = true
97 |
98 | let property = MutableProperty(0)
99 | let disposable = property.producer
100 | .lane("Test Subscription", filter: .event, logger: recorder.log)
101 | .start()
102 |
103 | XCTAssertEqual(recorder.logged.count, 1)
104 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, 0")
105 |
106 | disposable.dispose()
107 |
108 | XCTAssertEqual(recorder.logged.count, 2)
109 | XCTAssertEqual(recorder.logged[1].type, "Cancelled")
110 | }
111 |
112 | // MARK: - Subscription
113 |
114 | func testSubscription() {
115 | let recorder = TestLog()
116 | Timelane.Subscription.didEmitVersion = true
117 |
118 | let property = MutableProperty(0)
119 | let disposable = property.producer
120 | .lane("Test Subscription", filter: .subscription, logger: recorder.log)
121 | .start()
122 |
123 | XCTAssertEqual(recorder.logged.count, 1)
124 | XCTAssertEqual(recorder.logged[0].signpostType, "begin")
125 | XCTAssertEqual(recorder.logged[0].subscribe, "Test Subscription")
126 |
127 | disposable.dispose()
128 |
129 | XCTAssertEqual(recorder.logged[1].signpostType, "end")
130 | }
131 |
132 | // MARK: - All tests
133 |
134 | static var allTests = [
135 | ("testCompletedEvent", testCompletedEvent),
136 | ("testValueEvents", testValueEvents),
137 | ("testFormatting", testFormatting),
138 | ("testErrorEvent", testErrorEvent),
139 | ("testErrorEventAfterValues", testErrorEventAfterValues),
140 | ("testCancelledEvent", testCancelledEvent),
141 | ("testSubscription", testSubscription),
142 | ]
143 | }
144 |
--------------------------------------------------------------------------------
/Tests/ReactiveTimelaneTests/SignalTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import TimelaneCore
3 | import TimelaneCoreTestUtils
4 | import ReactiveSwift
5 | @testable import ReactiveTimelane
6 |
7 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)
8 | final class SignalTests: XCTestCase {
9 | override func setUp() {
10 | continueAfterFailure = false
11 | super.setUp()
12 | }
13 |
14 | // MARK: - Events
15 |
16 | func testCompletedEvent() {
17 | let recorder = TestLog()
18 | Timelane.Subscription.didEmitVersion = true
19 |
20 | let (signal, observer) = Signal.pipe()
21 | signal
22 | .lane("Test Subscription", filter: .event, logger: recorder.log)
23 | .observe { _ in }
24 | observer.sendCompleted()
25 |
26 | XCTAssertEqual(recorder.logged.count, 1)
27 | XCTAssertEqual(recorder.logged[0].type, "Completed")
28 | XCTAssertEqual(recorder.logged[0].subscription, "Test Subscription")
29 | }
30 |
31 | func testValueEvents() {
32 | let recorder = TestLog()
33 | Timelane.Subscription.didEmitVersion = true
34 |
35 | let (signal, observer) = Signal.pipe()
36 | signal
37 | .lane("Test Subscription", filter: .event, logger: recorder.log)
38 | .observe { _ in }
39 | observer.send(value: 1)
40 | observer.send(value: 2)
41 | observer.send(value: 3)
42 | observer.sendCompleted()
43 |
44 | XCTAssertEqual(recorder.logged.count, 4)
45 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, 1")
46 | XCTAssertEqual(recorder.logged[1].outputTldr, "Output, Test Subscription, 2")
47 | XCTAssertEqual(recorder.logged[2].outputTldr, "Output, Test Subscription, 3")
48 | XCTAssertEqual(recorder.logged[3].type, "Completed")
49 | XCTAssertEqual(recorder.logged[3].subscription, "Test Subscription")
50 | }
51 |
52 | func testFormatting() {
53 | let recorder = TestLog()
54 | Timelane.Subscription.didEmitVersion = true
55 |
56 | let (signal, observer) = Signal.pipe()
57 | signal
58 | .lane("Test Subscription",
59 | filter: .event,
60 | transformValue: { "TEST \($0)" },
61 | logger: recorder.log)
62 | .observe { _ in }
63 | observer.send(value: 1)
64 | observer.sendCompleted()
65 | XCTAssertEqual(recorder.logged.count, 2)
66 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, TEST 1")
67 | }
68 |
69 | enum TestError: LocalizedError {
70 | case somethingWentWrong
71 | var errorDescription: String? { "Error description" }
72 | }
73 |
74 | func testErrorEvent() {
75 | let recorder = TestLog()
76 | Timelane.Subscription.didEmitVersion = true
77 |
78 | let (signal, observer) = Signal.pipe()
79 | signal
80 | .lane("Test Subscription", filter: .event, logger: recorder.log)
81 | .observe { _ in }
82 | observer.send(error: .somethingWentWrong)
83 | XCTAssertEqual(recorder.logged.count, 1)
84 | XCTAssertEqual(recorder.logged[0].type, "Error")
85 | XCTAssertEqual(recorder.logged[0].value, TestError.somethingWentWrong.errorDescription)
86 | }
87 |
88 | func testErrorEventAfterValues() {
89 | let recorder = TestLog()
90 | Timelane.Subscription.didEmitVersion = true
91 |
92 | let (signal, observer) = Signal.pipe()
93 | signal
94 | .lane("Test Subscription", filter: .event, logger: recorder.log)
95 | .observe { _ in }
96 | observer.send(value: 1)
97 | observer.send(value: 2)
98 | observer.send(value: 3)
99 | observer.send(error: .somethingWentWrong)
100 |
101 | XCTAssertEqual(recorder.logged.count, 4)
102 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, 1")
103 | XCTAssertEqual(recorder.logged[1].outputTldr, "Output, Test Subscription, 2")
104 | XCTAssertEqual(recorder.logged[2].outputTldr, "Output, Test Subscription, 3")
105 | XCTAssertEqual(recorder.logged[3].type, "Error")
106 | XCTAssertEqual(recorder.logged[3].value, TestError.somethingWentWrong.errorDescription)
107 | }
108 |
109 | func testCancelledEvent() {
110 | let recorder = TestLog()
111 | Timelane.Subscription.didEmitVersion = true
112 |
113 | let (signal, observer) = Signal.pipe()
114 | signal
115 | .lane("Test Subscription", filter: .event, logger: recorder.log)
116 | .observe { _ in }
117 |
118 | observer.send(value: 0)
119 |
120 | XCTAssertEqual(recorder.logged.count, 1)
121 | XCTAssertEqual(recorder.logged[0].outputTldr, "Output, Test Subscription, 0")
122 |
123 | observer.sendInterrupted()
124 |
125 | XCTAssertEqual(recorder.logged.count, 2)
126 | XCTAssertEqual(recorder.logged[1].type, "Cancelled")
127 | }
128 |
129 | // MARK: - Subscription
130 |
131 | func testSubscription() {
132 | let recorder = TestLog()
133 | Timelane.Subscription.didEmitVersion = true
134 |
135 | let (signal, observer) = Signal.pipe()
136 | signal
137 | .lane("Test Subscription", filter: .subscription, logger: recorder.log)
138 | .observe { _ in }
139 |
140 | XCTAssertEqual(recorder.logged.count, 1)
141 | XCTAssertEqual(recorder.logged[0].signpostType, "begin")
142 | XCTAssertEqual(recorder.logged[0].subscribe, "Test Subscription")
143 |
144 | observer.sendCompleted()
145 |
146 | XCTAssertEqual(recorder.logged[1].signpostType, "end")
147 | }
148 |
149 | // MARK: - All tests
150 |
151 | static var allTests = [
152 | ("testCompletedEvent", testCompletedEvent),
153 | ("testValueEvents", testValueEvents),
154 | ("testFormatting", testFormatting),
155 | ("testErrorEvent", testErrorEvent),
156 | ("testErrorEventAfterValues", testErrorEventAfterValues),
157 | ("testCancelledEvent", testCancelledEvent),
158 | ("testSubscription", testSubscription),
159 | ]
160 | }
161 |
--------------------------------------------------------------------------------
/Demo/ReactiveTimelaneDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5A076A49247E57000002B523 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A076A48247E57000002B523 /* AppDelegate.swift */; };
11 | 5A076A4B247E57000002B523 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A076A4A247E57000002B523 /* SceneDelegate.swift */; };
12 | 5A076A4F247E57020002B523 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5A076A4E247E57020002B523 /* Assets.xcassets */; };
13 | 5A076A55247E57020002B523 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A076A53247E57020002B523 /* LaunchScreen.storyboard */; };
14 | 5A076A5E247E58000002B523 /* ReactiveTimelane in Frameworks */ = {isa = PBXBuildFile; productRef = 5A076A5D247E58000002B523 /* ReactiveTimelane */; };
15 | 5A076A60247E581A0002B523 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A076A5F247E581A0002B523 /* ViewController.swift */; };
16 | 5A076A63247E5BE40002B523 /* ReactiveCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 5A076A62247E5BE40002B523 /* ReactiveCocoa */; };
17 | 5A076A65247E5C5C0002B523 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A076A64247E5C5C0002B523 /* App.swift */; };
18 | 5A076A6C247E5E8E0002B523 /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5A076A6B247E5E8E0002B523 /* ReactiveSwift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXFileReference section */
22 | 5A076A45247E57000002B523 /* ReactiveTimelaneDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactiveTimelaneDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
23 | 5A076A48247E57000002B523 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
24 | 5A076A4A247E57000002B523 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
25 | 5A076A4E247E57020002B523 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
26 | 5A076A54247E57020002B523 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
27 | 5A076A56247E57020002B523 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
28 | 5A076A5F247E581A0002B523 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
29 | 5A076A64247E5C5C0002B523 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; };
30 | 5A076A6D247E6D560002B523 /* ReactiveTimelaneDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ReactiveTimelaneDemo.entitlements; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 5A076A42247E57000002B523 /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | 5A076A5E247E58000002B523 /* ReactiveTimelane in Frameworks */,
39 | 5A076A6C247E5E8E0002B523 /* ReactiveSwift in Frameworks */,
40 | 5A076A63247E5BE40002B523 /* ReactiveCocoa in Frameworks */,
41 | );
42 | runOnlyForDeploymentPostprocessing = 0;
43 | };
44 | /* End PBXFrameworksBuildPhase section */
45 |
46 | /* Begin PBXGroup section */
47 | 5A076A3C247E57000002B523 = {
48 | isa = PBXGroup;
49 | children = (
50 | 5A076A47247E57000002B523 /* ReactiveTimelaneDemo */,
51 | 5A076A46247E57000002B523 /* Products */,
52 | );
53 | sourceTree = "";
54 | };
55 | 5A076A46247E57000002B523 /* Products */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 5A076A45247E57000002B523 /* ReactiveTimelaneDemo.app */,
59 | );
60 | name = Products;
61 | sourceTree = "";
62 | };
63 | 5A076A47247E57000002B523 /* ReactiveTimelaneDemo */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 5A076A6D247E6D560002B523 /* ReactiveTimelaneDemo.entitlements */,
67 | 5A076A66247E5D490002B523 /* App */,
68 | 5A076A69247E5D860002B523 /* Resources */,
69 | 5A076A5F247E581A0002B523 /* ViewController.swift */,
70 | );
71 | path = ReactiveTimelaneDemo;
72 | sourceTree = "";
73 | };
74 | 5A076A66247E5D490002B523 /* App */ = {
75 | isa = PBXGroup;
76 | children = (
77 | 5A076A64247E5C5C0002B523 /* App.swift */,
78 | 5A076A48247E57000002B523 /* AppDelegate.swift */,
79 | 5A076A4A247E57000002B523 /* SceneDelegate.swift */,
80 | );
81 | path = App;
82 | sourceTree = "";
83 | };
84 | 5A076A69247E5D860002B523 /* Resources */ = {
85 | isa = PBXGroup;
86 | children = (
87 | 5A076A4E247E57020002B523 /* Assets.xcassets */,
88 | 5A076A53247E57020002B523 /* LaunchScreen.storyboard */,
89 | 5A076A56247E57020002B523 /* Info.plist */,
90 | );
91 | name = Resources;
92 | sourceTree = "";
93 | };
94 | /* End PBXGroup section */
95 |
96 | /* Begin PBXNativeTarget section */
97 | 5A076A44247E57000002B523 /* ReactiveTimelaneDemo */ = {
98 | isa = PBXNativeTarget;
99 | buildConfigurationList = 5A076A59247E57020002B523 /* Build configuration list for PBXNativeTarget "ReactiveTimelaneDemo" */;
100 | buildPhases = (
101 | 5A076A41247E57000002B523 /* Sources */,
102 | 5A076A42247E57000002B523 /* Frameworks */,
103 | 5A076A43247E57000002B523 /* Resources */,
104 | );
105 | buildRules = (
106 | );
107 | dependencies = (
108 | );
109 | name = ReactiveTimelaneDemo;
110 | packageProductDependencies = (
111 | 5A076A5D247E58000002B523 /* ReactiveTimelane */,
112 | 5A076A62247E5BE40002B523 /* ReactiveCocoa */,
113 | 5A076A6B247E5E8E0002B523 /* ReactiveSwift */,
114 | );
115 | productName = ReactiveTimelaneDemo;
116 | productReference = 5A076A45247E57000002B523 /* ReactiveTimelaneDemo.app */;
117 | productType = "com.apple.product-type.application";
118 | };
119 | /* End PBXNativeTarget section */
120 |
121 | /* Begin PBXProject section */
122 | 5A076A3D247E57000002B523 /* Project object */ = {
123 | isa = PBXProject;
124 | attributes = {
125 | LastSwiftUpdateCheck = 1140;
126 | LastUpgradeCheck = 1140;
127 | ORGANIZATIONNAME = "Niclas Kristek";
128 | TargetAttributes = {
129 | 5A076A44247E57000002B523 = {
130 | CreatedOnToolsVersion = 11.4.1;
131 | };
132 | };
133 | };
134 | buildConfigurationList = 5A076A40247E57000002B523 /* Build configuration list for PBXProject "ReactiveTimelaneDemo" */;
135 | compatibilityVersion = "Xcode 9.3";
136 | developmentRegion = en;
137 | hasScannedForEncodings = 0;
138 | knownRegions = (
139 | en,
140 | Base,
141 | );
142 | mainGroup = 5A076A3C247E57000002B523;
143 | packageReferences = (
144 | 5A076A5C247E58000002B523 /* XCRemoteSwiftPackageReference "ReactiveTimelane" */,
145 | 5A076A61247E5BE40002B523 /* XCRemoteSwiftPackageReference "ReactiveCocoa" */,
146 | 5A076A6A247E5E8E0002B523 /* XCRemoteSwiftPackageReference "ReactiveSwift" */,
147 | );
148 | productRefGroup = 5A076A46247E57000002B523 /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 5A076A44247E57000002B523 /* ReactiveTimelaneDemo */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 5A076A43247E57000002B523 /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 5A076A55247E57020002B523 /* LaunchScreen.storyboard in Resources */,
163 | 5A076A4F247E57020002B523 /* Assets.xcassets in Resources */,
164 | );
165 | runOnlyForDeploymentPostprocessing = 0;
166 | };
167 | /* End PBXResourcesBuildPhase section */
168 |
169 | /* Begin PBXSourcesBuildPhase section */
170 | 5A076A41247E57000002B523 /* Sources */ = {
171 | isa = PBXSourcesBuildPhase;
172 | buildActionMask = 2147483647;
173 | files = (
174 | 5A076A49247E57000002B523 /* AppDelegate.swift in Sources */,
175 | 5A076A65247E5C5C0002B523 /* App.swift in Sources */,
176 | 5A076A4B247E57000002B523 /* SceneDelegate.swift in Sources */,
177 | 5A076A60247E581A0002B523 /* ViewController.swift in Sources */,
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | };
181 | /* End PBXSourcesBuildPhase section */
182 |
183 | /* Begin PBXVariantGroup section */
184 | 5A076A53247E57020002B523 /* LaunchScreen.storyboard */ = {
185 | isa = PBXVariantGroup;
186 | children = (
187 | 5A076A54247E57020002B523 /* Base */,
188 | );
189 | name = LaunchScreen.storyboard;
190 | sourceTree = "";
191 | };
192 | /* End PBXVariantGroup section */
193 |
194 | /* Begin XCBuildConfiguration section */
195 | 5A076A57247E57020002B523 /* Debug */ = {
196 | isa = XCBuildConfiguration;
197 | buildSettings = {
198 | ALWAYS_SEARCH_USER_PATHS = NO;
199 | CLANG_ANALYZER_NONNULL = YES;
200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
202 | CLANG_CXX_LIBRARY = "libc++";
203 | CLANG_ENABLE_MODULES = YES;
204 | CLANG_ENABLE_OBJC_ARC = YES;
205 | CLANG_ENABLE_OBJC_WEAK = YES;
206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
207 | CLANG_WARN_BOOL_CONVERSION = YES;
208 | CLANG_WARN_COMMA = YES;
209 | CLANG_WARN_CONSTANT_CONVERSION = YES;
210 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
212 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
213 | CLANG_WARN_EMPTY_BODY = YES;
214 | CLANG_WARN_ENUM_CONVERSION = YES;
215 | CLANG_WARN_INFINITE_RECURSION = YES;
216 | CLANG_WARN_INT_CONVERSION = YES;
217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
218 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
222 | CLANG_WARN_STRICT_PROTOTYPES = YES;
223 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
225 | CLANG_WARN_UNREACHABLE_CODE = YES;
226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
227 | COPY_PHASE_STRIP = NO;
228 | DEBUG_INFORMATION_FORMAT = dwarf;
229 | ENABLE_STRICT_OBJC_MSGSEND = YES;
230 | ENABLE_TESTABILITY = YES;
231 | GCC_C_LANGUAGE_STANDARD = gnu11;
232 | GCC_DYNAMIC_NO_PIC = NO;
233 | GCC_NO_COMMON_BLOCKS = YES;
234 | GCC_OPTIMIZATION_LEVEL = 0;
235 | GCC_PREPROCESSOR_DEFINITIONS = (
236 | "DEBUG=1",
237 | "$(inherited)",
238 | );
239 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
240 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
241 | GCC_WARN_UNDECLARED_SELECTOR = YES;
242 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
243 | GCC_WARN_UNUSED_FUNCTION = YES;
244 | GCC_WARN_UNUSED_VARIABLE = YES;
245 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
246 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
247 | MTL_FAST_MATH = YES;
248 | ONLY_ACTIVE_ARCH = YES;
249 | SDKROOT = iphoneos;
250 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
251 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
252 | };
253 | name = Debug;
254 | };
255 | 5A076A58247E57020002B523 /* Release */ = {
256 | isa = XCBuildConfiguration;
257 | buildSettings = {
258 | ALWAYS_SEARCH_USER_PATHS = NO;
259 | CLANG_ANALYZER_NONNULL = YES;
260 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
261 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
262 | CLANG_CXX_LIBRARY = "libc++";
263 | CLANG_ENABLE_MODULES = YES;
264 | CLANG_ENABLE_OBJC_ARC = YES;
265 | CLANG_ENABLE_OBJC_WEAK = YES;
266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
267 | CLANG_WARN_BOOL_CONVERSION = YES;
268 | CLANG_WARN_COMMA = YES;
269 | CLANG_WARN_CONSTANT_CONVERSION = YES;
270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
273 | CLANG_WARN_EMPTY_BODY = YES;
274 | CLANG_WARN_ENUM_CONVERSION = YES;
275 | CLANG_WARN_INFINITE_RECURSION = YES;
276 | CLANG_WARN_INT_CONVERSION = YES;
277 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
278 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
279 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
281 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
282 | CLANG_WARN_STRICT_PROTOTYPES = YES;
283 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
284 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
285 | CLANG_WARN_UNREACHABLE_CODE = YES;
286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
287 | COPY_PHASE_STRIP = NO;
288 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
289 | ENABLE_NS_ASSERTIONS = NO;
290 | ENABLE_STRICT_OBJC_MSGSEND = YES;
291 | GCC_C_LANGUAGE_STANDARD = gnu11;
292 | GCC_NO_COMMON_BLOCKS = YES;
293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
295 | GCC_WARN_UNDECLARED_SELECTOR = YES;
296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
297 | GCC_WARN_UNUSED_FUNCTION = YES;
298 | GCC_WARN_UNUSED_VARIABLE = YES;
299 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
300 | MTL_ENABLE_DEBUG_INFO = NO;
301 | MTL_FAST_MATH = YES;
302 | SDKROOT = iphoneos;
303 | SWIFT_COMPILATION_MODE = wholemodule;
304 | SWIFT_OPTIMIZATION_LEVEL = "-O";
305 | VALIDATE_PRODUCT = YES;
306 | };
307 | name = Release;
308 | };
309 | 5A076A5A247E57020002B523 /* Debug */ = {
310 | isa = XCBuildConfiguration;
311 | buildSettings = {
312 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
313 | CODE_SIGN_ENTITLEMENTS = ReactiveTimelaneDemo/ReactiveTimelaneDemo.entitlements;
314 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
315 | CODE_SIGN_STYLE = Automatic;
316 | DEVELOPMENT_TEAM = "";
317 | "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
318 | ENABLE_PREVIEWS = YES;
319 | INFOPLIST_FILE = ReactiveTimelaneDemo/Info.plist;
320 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
321 | LD_RUNPATH_SEARCH_PATHS = (
322 | "$(inherited)",
323 | "@executable_path/Frameworks",
324 | );
325 | PRODUCT_BUNDLE_IDENTIFIER = com.nkristek.ReactiveTimelaneDemo;
326 | PRODUCT_NAME = "$(TARGET_NAME)";
327 | SUPPORTS_MACCATALYST = NO;
328 | SWIFT_VERSION = 5.0;
329 | TARGETED_DEVICE_FAMILY = "1,2";
330 | };
331 | name = Debug;
332 | };
333 | 5A076A5B247E57020002B523 /* Release */ = {
334 | isa = XCBuildConfiguration;
335 | buildSettings = {
336 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
337 | CODE_SIGN_ENTITLEMENTS = ReactiveTimelaneDemo/ReactiveTimelaneDemo.entitlements;
338 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
339 | CODE_SIGN_STYLE = Automatic;
340 | DEVELOPMENT_TEAM = "";
341 | "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
342 | ENABLE_PREVIEWS = YES;
343 | INFOPLIST_FILE = ReactiveTimelaneDemo/Info.plist;
344 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
345 | LD_RUNPATH_SEARCH_PATHS = (
346 | "$(inherited)",
347 | "@executable_path/Frameworks",
348 | );
349 | PRODUCT_BUNDLE_IDENTIFIER = com.nkristek.ReactiveTimelaneDemo;
350 | PRODUCT_NAME = "$(TARGET_NAME)";
351 | SUPPORTS_MACCATALYST = NO;
352 | SWIFT_VERSION = 5.0;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Release;
356 | };
357 | /* End XCBuildConfiguration section */
358 |
359 | /* Begin XCConfigurationList section */
360 | 5A076A40247E57000002B523 /* Build configuration list for PBXProject "ReactiveTimelaneDemo" */ = {
361 | isa = XCConfigurationList;
362 | buildConfigurations = (
363 | 5A076A57247E57020002B523 /* Debug */,
364 | 5A076A58247E57020002B523 /* Release */,
365 | );
366 | defaultConfigurationIsVisible = 0;
367 | defaultConfigurationName = Release;
368 | };
369 | 5A076A59247E57020002B523 /* Build configuration list for PBXNativeTarget "ReactiveTimelaneDemo" */ = {
370 | isa = XCConfigurationList;
371 | buildConfigurations = (
372 | 5A076A5A247E57020002B523 /* Debug */,
373 | 5A076A5B247E57020002B523 /* Release */,
374 | );
375 | defaultConfigurationIsVisible = 0;
376 | defaultConfigurationName = Release;
377 | };
378 | /* End XCConfigurationList section */
379 |
380 | /* Begin XCRemoteSwiftPackageReference section */
381 | 5A076A5C247E58000002B523 /* XCRemoteSwiftPackageReference "ReactiveTimelane" */ = {
382 | isa = XCRemoteSwiftPackageReference;
383 | repositoryURL = "https://github.com/nkristek/ReactiveTimelane";
384 | requirement = {
385 | kind = upToNextMajorVersion;
386 | minimumVersion = 1.0.1;
387 | };
388 | };
389 | 5A076A61247E5BE40002B523 /* XCRemoteSwiftPackageReference "ReactiveCocoa" */ = {
390 | isa = XCRemoteSwiftPackageReference;
391 | repositoryURL = "https://github.com/ReactiveCocoa/ReactiveCocoa";
392 | requirement = {
393 | kind = upToNextMajorVersion;
394 | minimumVersion = 10.3.0;
395 | };
396 | };
397 | 5A076A6A247E5E8E0002B523 /* XCRemoteSwiftPackageReference "ReactiveSwift" */ = {
398 | isa = XCRemoteSwiftPackageReference;
399 | repositoryURL = "https://github.com/ReactiveCocoa/ReactiveSwift";
400 | requirement = {
401 | kind = upToNextMajorVersion;
402 | minimumVersion = 6.3.0;
403 | };
404 | };
405 | /* End XCRemoteSwiftPackageReference section */
406 |
407 | /* Begin XCSwiftPackageProductDependency section */
408 | 5A076A5D247E58000002B523 /* ReactiveTimelane */ = {
409 | isa = XCSwiftPackageProductDependency;
410 | package = 5A076A5C247E58000002B523 /* XCRemoteSwiftPackageReference "ReactiveTimelane" */;
411 | productName = ReactiveTimelane;
412 | };
413 | 5A076A62247E5BE40002B523 /* ReactiveCocoa */ = {
414 | isa = XCSwiftPackageProductDependency;
415 | package = 5A076A61247E5BE40002B523 /* XCRemoteSwiftPackageReference "ReactiveCocoa" */;
416 | productName = ReactiveCocoa;
417 | };
418 | 5A076A6B247E5E8E0002B523 /* ReactiveSwift */ = {
419 | isa = XCSwiftPackageProductDependency;
420 | package = 5A076A6A247E5E8E0002B523 /* XCRemoteSwiftPackageReference "ReactiveSwift" */;
421 | productName = ReactiveSwift;
422 | };
423 | /* End XCSwiftPackageProductDependency section */
424 | };
425 | rootObject = 5A076A3D247E57000002B523 /* Project object */;
426 | }
427 |
--------------------------------------------------------------------------------