diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..805ffc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# macOS +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Note: if you ignore the Pods directory, make sure to uncomment +# `pod install` in .travis.yml +# +# Pods/ diff --git a/Documentation/AppTasks.md b/Documentation/AppTasks.md new file mode 100644 index 0000000..881823c --- /dev/null +++ b/Documentation/AppTasks.md @@ -0,0 +1,124 @@ + +## Overview +Additions library provides an ordered way to perform application boot tasks. The main components of the boot process are: + +* [AppTasks](#apptasks) +* [ServiceProvider](#serviceprovider) +* [AppTask](#apptask) +* [NoOpTask](#nooptask) + +Here's example sequence diagram from an app launched using `AppTasks`: + +![AppTasks sequence example diagram](Assets/AppTaskLaunchSequenceDiagram.jpeg) + +To understand better each step, keep on reading. + +## AppTasks +### Description +`AppTasks` provides an object that manages setup tasks when booting the application. When setup tasks are running, it buffers `AppDelegate` delegate method calls, when setup tasks are finished, it forwards buffered `AppDelegate` calls, and it keeps sending further `AppDelegate` calls to observing tasks. + +### Example +You must create this object on your `SceneDelegate` or `AppDelegate` in order to forward application events. + +```swift +class AppDelegate: UIResponder, UIApplicationDelegate { + + private var appServices = AppServices() + /// In this case we are getting an instance of `AppTasks` by building it, but after it is built, we can also get it by calling: `AppTasks.shared` + private lazy var tasks = AppTasks.build(serviceProviders: [AdditionsServices(), appServices]) { + print("all tasks completed") + } + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + /// Calls to `didFinishLaunching` and other delegate methods must be redirected to the array of `tasks` we received from building an `AppTasks` object, so that they can be buffered and forwarded to the currently registered tasks. We will do the same for all `AppDeleagte` delegate calls. + tasks.forEach { + _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions) + } + return true + } +} +``` + +## ServiceProvider +### Description +To take advantage of the `ServiceLocator` that `Additions` provides, you must create a subclass of `ServiceProvider`. It is in charge of building the necessary components for `AppTasks` to work. + +It is also where we’ll define in which order tasks must be ran. + +### Example +```swift +import Additions + +class AppServices: ServiceProvider { + private lazy var onboardingScreenTask = OnboardingScreenTask() + private lazy var homeScreenTask = HomeScreenTask() + private lazy var pushNotoificationTask = PushNotoificationTask() + + lazy var appTasks: [AppTask] = { + /// We need to present onboarding screens before showing the home screen, so we add a dependency from `homeScreenTask` to `onboardingScreenTask` this way, `homeScreenTask` won't be ran until `onboardingScreenTask` calls the `setFinished()` method. + homeScreenTask.addDependency(onboardingScreenTask) + return [ + onboardingScreenTask, + homeScreenTask, + pushNotoificationTask + ] + }() + + /// `modules` method implementation is required by the `ServiceLoactor` protocol. + func modules() -> [Register] { + /// If we want to use `@Inject` to have the stored instance of a task injected to an other class, we can do so by registering it with the `ServiceLocator`. + return [ + Register { homeScreenTask } + ] + } +} +``` + +## AppTask +### Description +An `AppTask` is some kind of setup task that needs to be performed before any `AppDelegate` delegate call is performed. + +To create an `AppTask` you must provide an object that conforms to `AppTask` , overrides its `main()` method, and calls `setFinished()` when it finishes the process. + +### Example + +```swift +/// Conform to `AppTask` +class OnboardingScreenTask: AppTask { + private let onboardingNavigator: OnboardingNavigator + init(onboardingNavigator: OnboardingNavigator) { ... } + + /// Override `main()` + override func main() { + /// Presents onboarding screens + onboardingNavigator.navigate(completion: { + /// Call `setFinisehd()` when onboarding is completed. + self.setFinished() + }) + } +} +``` + +### NoOpTask + +### Description + +A `NoOpTask` is a task that finishes immediately. The use of this kind of task is to receive and react to `AppDelegate` delegate calls. It will keep receiving these events throught the app’s lifecycle. + +### Example + +```swift +/// Conform to `AppTask` +class PushNotoificationTask: NoOpTask { + private let pushService: PushService + init(pushService: PushService) { ... } + func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + pushService.application( + application, + didRegisterForRemoteNotificationsWithDeviceToken: deviceToken + ) + } +} +``` \ No newline at end of file diff --git a/Documentation/Assets/AppTaskLaunchSequenceDiagram.jpeg b/Documentation/Assets/AppTaskLaunchSequenceDiagram.jpeg new file mode 100644 index 0000000..72ab39e Binary files /dev/null and b/Documentation/Assets/AppTaskLaunchSequenceDiagram.jpeg differ diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 0000000..97fb69f --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,13 @@ +use_frameworks! + +platform :ios, '13.0' + +target 'SwiftAdditions_Example' do + pod 'SwiftAdditions', :path => '../' + + target 'SwiftAdditions_ExampleTests' do + inherit! :search_paths + + + end +end diff --git a/Example/SwiftAdditions.xcodeproj/project.pbxproj b/Example/SwiftAdditions.xcodeproj/project.pbxproj new file mode 100644 index 0000000..58c03b9 --- /dev/null +++ b/Example/SwiftAdditions.xcodeproj/project.pbxproj @@ -0,0 +1,626 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; + BA784DD8F4C55E2DA0F06C76 /* Pods_SwiftAdditions_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E6D2395DAA77F23437572C0 /* Pods_SwiftAdditions_Example.framework */; }; + FA24D0B2427159D706DE1499 /* Pods_SwiftAdditions_ExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE43F4F989FDC62C897034B0 /* Pods_SwiftAdditions_ExampleTests.framework */; }; + FBE5AB2F29A4C4D4002AC234 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2329A4C4D4002AC234 /* OnboardingScreen.swift */; }; + FBE5AB3129A4C4D4002AC234 /* WindowSetupTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2629A4C4D4002AC234 /* WindowSetupTask.swift */; }; + FBE5AB3229A4C4D4002AC234 /* SomeLongRunningTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2729A4C4D4002AC234 /* SomeLongRunningTask.swift */; }; + FBE5AB3329A4C4D4002AC234 /* OnboardingTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2829A4C4D4002AC234 /* OnboardingTask.swift */; }; + FBE5AB3429A4C4D4002AC234 /* PermissionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2929A4C4D4002AC234 /* PermissionTask.swift */; }; + FBE5AB3529A4C4D4002AC234 /* MainUISetupTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2A29A4C4D4002AC234 /* MainUISetupTask.swift */; }; + FBE5AB3629A4C4D4002AC234 /* SyncTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2B29A4C4D4002AC234 /* SyncTask.swift */; }; + FBE5AB3729A4C4D4002AC234 /* DependentTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2C29A4C4D4002AC234 /* DependentTask.swift */; }; + FBE5AB3829A4C4D4002AC234 /* ExampleAppServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2D29A4C4D4002AC234 /* ExampleAppServices.swift */; }; + FBE5AB3929A4C4D4002AC234 /* PrinterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB2E29A4C4D4002AC234 /* PrinterView.swift */; }; + FBE5AB3B29A4C70C002AC234 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBE5AB3A29A4C70C002AC234 /* LaunchScreen.storyboard */; }; + FBE5AB3D29A4C784002AC234 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB3C29A4C784002AC234 /* AppDelegate.swift */; }; + FBE5AB4529A4C9AD002AC234 /* FooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AB4429A4C9AD002AC234 /* FooTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + FBE5AB4629A4C9AD002AC234 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = SwiftAdditions_Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2E7DC0C456D5644A746436CC /* Pods-SwiftAdditions_ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAdditions_ExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-SwiftAdditions_ExampleTests/Pods-SwiftAdditions_ExampleTests.debug.xcconfig"; sourceTree = ""; }; + 3344D85DA9D879FC9899E7E8 /* Pods_SwiftAdditions_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAdditions_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AF4908F558270954A471FDA /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + 4BF242E421A0E43E8A5C9BD0 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 4C19070FFAD1DCDAEE96CA7D /* SwiftAdditions.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SwiftAdditions.podspec; path = ../SwiftAdditions.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 4F3814AD447CAF3159EBB462 /* Pods-SwiftAdditions_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAdditions_Tests.debug.xcconfig"; path = "Target Support Files/Pods-SwiftAdditions_Tests/Pods-SwiftAdditions_Tests.debug.xcconfig"; sourceTree = ""; }; + 607FACD01AFB9204008FA782 /* SwiftAdditions_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAdditions_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 6E6D2395DAA77F23437572C0 /* Pods_SwiftAdditions_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAdditions_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6FFBF68D48E0E4DAC4F8C3C4 /* Pods-SwiftAdditions_ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAdditions_ExampleTests.release.xcconfig"; path = "Target Support Files/Pods-SwiftAdditions_ExampleTests/Pods-SwiftAdditions_ExampleTests.release.xcconfig"; sourceTree = ""; }; + 7D2053A54883590DBA3F6AD4 /* Pods-SwiftAdditions_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAdditions_Example.release.xcconfig"; path = "Target Support Files/Pods-SwiftAdditions_Example/Pods-SwiftAdditions_Example.release.xcconfig"; sourceTree = ""; }; + CE43F4F989FDC62C897034B0 /* Pods_SwiftAdditions_ExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAdditions_ExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D36970CB619591D030E7DE35 /* Pods-SwiftAdditions_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAdditions_Tests.release.xcconfig"; path = "Target Support Files/Pods-SwiftAdditions_Tests/Pods-SwiftAdditions_Tests.release.xcconfig"; sourceTree = ""; }; + D6DB106CD46C8BFA69801CBE /* Pods-SwiftAdditions_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAdditions_Example.debug.xcconfig"; path = "Target Support Files/Pods-SwiftAdditions_Example/Pods-SwiftAdditions_Example.debug.xcconfig"; sourceTree = ""; }; + FBE5AB2329A4C4D4002AC234 /* OnboardingScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = ""; }; + FBE5AB2629A4C4D4002AC234 /* WindowSetupTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowSetupTask.swift; sourceTree = ""; }; + FBE5AB2729A4C4D4002AC234 /* SomeLongRunningTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SomeLongRunningTask.swift; sourceTree = ""; }; + FBE5AB2829A4C4D4002AC234 /* OnboardingTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingTask.swift; sourceTree = ""; }; + FBE5AB2929A4C4D4002AC234 /* PermissionTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionTask.swift; sourceTree = ""; }; + FBE5AB2A29A4C4D4002AC234 /* MainUISetupTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainUISetupTask.swift; sourceTree = ""; }; + FBE5AB2B29A4C4D4002AC234 /* SyncTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncTask.swift; sourceTree = ""; }; + FBE5AB2C29A4C4D4002AC234 /* DependentTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DependentTask.swift; sourceTree = ""; }; + FBE5AB2D29A4C4D4002AC234 /* ExampleAppServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleAppServices.swift; sourceTree = ""; }; + FBE5AB2E29A4C4D4002AC234 /* PrinterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrinterView.swift; sourceTree = ""; }; + FBE5AB3A29A4C70C002AC234 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + FBE5AB3C29A4C784002AC234 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + FBE5AB4229A4C9AD002AC234 /* SwiftAdditions_ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftAdditions_ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FBE5AB4429A4C9AD002AC234 /* FooTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACCD1AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BA784DD8F4C55E2DA0F06C76 /* Pods_SwiftAdditions_Example.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBE5AB3F29A4C9AD002AC234 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FA24D0B2427159D706DE1499 /* Pods_SwiftAdditions_ExampleTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4E36A654E39BA1DA78B8896F /* Pods */ = { + isa = PBXGroup; + children = ( + D6DB106CD46C8BFA69801CBE /* Pods-SwiftAdditions_Example.debug.xcconfig */, + 7D2053A54883590DBA3F6AD4 /* Pods-SwiftAdditions_Example.release.xcconfig */, + 4F3814AD447CAF3159EBB462 /* Pods-SwiftAdditions_Tests.debug.xcconfig */, + D36970CB619591D030E7DE35 /* Pods-SwiftAdditions_Tests.release.xcconfig */, + 2E7DC0C456D5644A746436CC /* Pods-SwiftAdditions_ExampleTests.debug.xcconfig */, + 6FFBF68D48E0E4DAC4F8C3C4 /* Pods-SwiftAdditions_ExampleTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + 607FACD21AFB9204008FA782 /* Example for SwiftAdditions */, + FBE5AB4329A4C9AD002AC234 /* SwiftAdditions_ExampleTests */, + 607FACD11AFB9204008FA782 /* Products */, + 4E36A654E39BA1DA78B8896F /* Pods */, + 7C3692E88D4564628B1C660A /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACD01AFB9204008FA782 /* SwiftAdditions_Example.app */, + FBE5AB4229A4C9AD002AC234 /* SwiftAdditions_ExampleTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACD21AFB9204008FA782 /* Example for SwiftAdditions */ = { + isa = PBXGroup; + children = ( + FBE5AB3C29A4C784002AC234 /* AppDelegate.swift */, + 607FACD71AFB9204008FA782 /* ViewController.swift */, + FBE5AB2D29A4C4D4002AC234 /* ExampleAppServices.swift */, + FBE5AB2329A4C4D4002AC234 /* OnboardingScreen.swift */, + FBE5AB2E29A4C4D4002AC234 /* PrinterView.swift */, + FBE5AB2529A4C4D4002AC234 /* Tasks */, + 607FACDC1AFB9204008FA782 /* Images.xcassets */, + FBE5AB3A29A4C70C002AC234 /* LaunchScreen.storyboard */, + 607FACD31AFB9204008FA782 /* Supporting Files */, + ); + name = "Example for SwiftAdditions"; + path = SwiftAdditions; + sourceTree = ""; + }; + 607FACD31AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACD41AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + 4C19070FFAD1DCDAEE96CA7D /* SwiftAdditions.podspec */, + 3AF4908F558270954A471FDA /* README.md */, + 4BF242E421A0E43E8A5C9BD0 /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = ""; + }; + 7C3692E88D4564628B1C660A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6E6D2395DAA77F23437572C0 /* Pods_SwiftAdditions_Example.framework */, + 3344D85DA9D879FC9899E7E8 /* Pods_SwiftAdditions_Tests.framework */, + CE43F4F989FDC62C897034B0 /* Pods_SwiftAdditions_ExampleTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + FBE5AB2529A4C4D4002AC234 /* Tasks */ = { + isa = PBXGroup; + children = ( + FBE5AB2629A4C4D4002AC234 /* WindowSetupTask.swift */, + FBE5AB2729A4C4D4002AC234 /* SomeLongRunningTask.swift */, + FBE5AB2829A4C4D4002AC234 /* OnboardingTask.swift */, + FBE5AB2929A4C4D4002AC234 /* PermissionTask.swift */, + FBE5AB2A29A4C4D4002AC234 /* MainUISetupTask.swift */, + FBE5AB2B29A4C4D4002AC234 /* SyncTask.swift */, + FBE5AB2C29A4C4D4002AC234 /* DependentTask.swift */, + ); + path = Tasks; + sourceTree = ""; + }; + FBE5AB4329A4C9AD002AC234 /* SwiftAdditions_ExampleTests */ = { + isa = PBXGroup; + children = ( + FBE5AB4429A4C9AD002AC234 /* FooTests.swift */, + ); + path = SwiftAdditions_ExampleTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACCF1AFB9204008FA782 /* SwiftAdditions_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAdditions_Example" */; + buildPhases = ( + 8DAB54E37191EFFE73FA9335 /* [CP] Check Pods Manifest.lock */, + 607FACCC1AFB9204008FA782 /* Sources */, + 607FACCD1AFB9204008FA782 /* Frameworks */, + 607FACCE1AFB9204008FA782 /* Resources */, + C9CCD6972C91CF2534D1DE56 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftAdditions_Example; + productName = SwiftAdditions; + productReference = 607FACD01AFB9204008FA782 /* SwiftAdditions_Example.app */; + productType = "com.apple.product-type.application"; + }; + FBE5AB4129A4C9AD002AC234 /* SwiftAdditions_ExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FBE5AB4829A4C9AD002AC234 /* Build configuration list for PBXNativeTarget "SwiftAdditions_ExampleTests" */; + buildPhases = ( + 746B17971B2E277830EEB913 /* [CP] Check Pods Manifest.lock */, + FBE5AB3E29A4C9AD002AC234 /* Sources */, + FBE5AB3F29A4C9AD002AC234 /* Frameworks */, + FBE5AB4029A4C9AD002AC234 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FBE5AB4729A4C9AD002AC234 /* PBXTargetDependency */, + ); + name = SwiftAdditions_ExampleTests; + productName = SwiftAdditions_ExampleTests; + productReference = FBE5AB4229A4C9AD002AC234 /* SwiftAdditions_ExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1400; + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = CocoaPods; + TargetAttributes = { + 607FACCF1AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = ""; + }; + FBE5AB4129A4C9AD002AC234 = { + CreatedOnToolsVersion = 14.0; + DevelopmentTeam = RCTL45N8E9; + ProvisioningStyle = Automatic; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAdditions" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + Base, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACCF1AFB9204008FA782 /* SwiftAdditions_Example */, + FBE5AB4129A4C9AD002AC234 /* SwiftAdditions_ExampleTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACCE1AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, + FBE5AB3B29A4C70C002AC234 /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBE5AB4029A4C9AD002AC234 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 746B17971B2E277830EEB913 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SwiftAdditions_ExampleTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8DAB54E37191EFFE73FA9335 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SwiftAdditions_Example-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C9CCD6972C91CF2534D1DE56 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SwiftAdditions_Example/Pods-SwiftAdditions_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/SwiftAdditions/Additions.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Additions.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftAdditions_Example/Pods-SwiftAdditions_Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607FACCC1AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FBE5AB3D29A4C784002AC234 /* AppDelegate.swift in Sources */, + FBE5AB3729A4C4D4002AC234 /* DependentTask.swift in Sources */, + 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, + FBE5AB3129A4C4D4002AC234 /* WindowSetupTask.swift in Sources */, + FBE5AB3229A4C4D4002AC234 /* SomeLongRunningTask.swift in Sources */, + FBE5AB3429A4C4D4002AC234 /* PermissionTask.swift in Sources */, + FBE5AB3529A4C4D4002AC234 /* MainUISetupTask.swift in Sources */, + FBE5AB2F29A4C4D4002AC234 /* OnboardingScreen.swift in Sources */, + FBE5AB3929A4C4D4002AC234 /* PrinterView.swift in Sources */, + FBE5AB3829A4C4D4002AC234 /* ExampleAppServices.swift in Sources */, + FBE5AB3329A4C4D4002AC234 /* OnboardingTask.swift in Sources */, + FBE5AB3629A4C4D4002AC234 /* SyncTask.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBE5AB3E29A4C9AD002AC234 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FBE5AB4529A4C9AD002AC234 /* FooTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + FBE5AB4729A4C9AD002AC234 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* SwiftAdditions_Example */; + targetProxy = FBE5AB4629A4C9AD002AC234 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF01AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D6DB106CD46C8BFA69801CBE /* Pods-SwiftAdditions_Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = SwiftAdditions/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF11AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7D2053A54883590DBA3F6AD4 /* Pods-SwiftAdditions_Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = SwiftAdditions/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + FBE5AB4929A4C9AD002AC234 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2E7DC0C456D5644A746436CC /* Pods-SwiftAdditions_ExampleTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = RCTL45N8E9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.adevinta.SwiftAdditions-ExampleTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAdditions_Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SwiftAdditions_Example"; + }; + name = Debug; + }; + FBE5AB4A29A4C9AD002AC234 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6FFBF68D48E0E4DAC4F8C3C4 /* Pods-SwiftAdditions_ExampleTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = RCTL45N8E9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.adevinta.SwiftAdditions-ExampleTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAdditions_Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SwiftAdditions_Example"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAdditions" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAdditions_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF01AFB9204008FA782 /* Debug */, + 607FACF11AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FBE5AB4829A4C9AD002AC234 /* Build configuration list for PBXNativeTarget "SwiftAdditions_ExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FBE5AB4929A4C9AD002AC234 /* Debug */, + FBE5AB4A29A4C9AD002AC234 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/Example/SwiftAdditions.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/SwiftAdditions.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..89bb87c --- /dev/null +++ b/Example/SwiftAdditions.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/SwiftAdditions.xcodeproj/xcshareddata/xcschemes/SwiftAdditions-Example.xcscheme b/Example/SwiftAdditions.xcodeproj/xcshareddata/xcschemes/SwiftAdditions-Example.xcscheme new file mode 100644 index 0000000..f139714 --- /dev/null +++ b/Example/SwiftAdditions.xcodeproj/xcshareddata/xcschemes/SwiftAdditions-Example.xcscheme @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/SwiftAdditions.xcworkspace/contents.xcworkspacedata b/Example/SwiftAdditions.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..880e16f --- /dev/null +++ b/Example/SwiftAdditions.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/SwiftAdditions.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/SwiftAdditions.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/SwiftAdditions.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/SwiftAdditions/AppDelegate.swift b/Example/SwiftAdditions/AppDelegate.swift new file mode 100644 index 0000000..0d47b70 --- /dev/null +++ b/Example/SwiftAdditions/AppDelegate.swift @@ -0,0 +1,32 @@ +import Additions +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + private lazy var serviceProviders: [ServiceProvider] = [ + ExampleAppServices(), + AdditionsServices(), + ] + + private lazy var tasks = AppTasks.build(serviceProviders: serviceProviders) { + print("all tasks completed") + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + + tasks.forEach { + _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions) + } + + return true + } + + func applicationDidEnterBackground(_ application: UIApplication) { + tasks.forEach { + $0.applicationDidEnterBackground?(application) + } + } +} + diff --git a/Example/SwiftAdditions/Base.lproj/LaunchScreen.storyboard b/Example/SwiftAdditions/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..337783c --- /dev/null +++ b/Example/SwiftAdditions/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/SwiftAdditions/ExampleAppServices.swift b/Example/SwiftAdditions/ExampleAppServices.swift new file mode 100644 index 0000000..e845cc7 --- /dev/null +++ b/Example/SwiftAdditions/ExampleAppServices.swift @@ -0,0 +1,44 @@ +import Foundation +import Additions + +class ExampleAppServices: ServiceProvider { + + lazy var someLongRunningTask = SomeLongRunningTask() + lazy var shortTask = SyncTask() + lazy var dependentTask = DependentTask() + lazy var permissionTask = PermissionTask() + lazy var windowSetupTask = WindowSetupTask() + lazy var onboardingTask = OnboardingTask() + lazy var mainUISetupTask = MainUISetupTask() + + lazy var appTasks: [AppTask] = { + + dependentTask.addDependency(someLongRunningTask) + + windowSetupTask.addDependency(dependentTask) + + onboardingTask.addDependency(windowSetupTask) + + permissionTask.addDependency(onboardingTask) + + mainUISetupTask.addDependency(permissionTask) + mainUISetupTask.addDependency(windowSetupTask) + + return [ + someLongRunningTask, + shortTask, + dependentTask, + permissionTask, + windowSetupTask, + onboardingTask, + mainUISetupTask, + ] + }() + + func modules() -> [Register] { + [ + Register(ReaderProtocol.self, { Reader() }), + Register { self.onboardingTask } + ] + } +} diff --git a/Example/SwiftAdditions/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/SwiftAdditions/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..7006c9e --- /dev/null +++ b/Example/SwiftAdditions/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Example/SwiftAdditions/Info.plist b/Example/SwiftAdditions/Info.plist new file mode 100644 index 0000000..d213d09 --- /dev/null +++ b/Example/SwiftAdditions/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/Example/SwiftAdditions/LaunchScreen.storyboard b/Example/SwiftAdditions/LaunchScreen.storyboard new file mode 100644 index 0000000..337783c --- /dev/null +++ b/Example/SwiftAdditions/LaunchScreen.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/SwiftAdditions/OnboardingScreen.swift b/Example/SwiftAdditions/OnboardingScreen.swift new file mode 100644 index 0000000..69cf969 --- /dev/null +++ b/Example/SwiftAdditions/OnboardingScreen.swift @@ -0,0 +1,19 @@ +import SwiftUI +import Additions + +struct OnboardingScreen: View { + + @Inject var onboardingTask: OnboardingTask + + var body: some View { + VStack { + Text("Hello, World!") + + Button(action: { + onboardingTask.finish() + }, label: { + Text("Got it!") + }) + } + } +} diff --git a/Example/SwiftAdditions/PrinterView.swift b/Example/SwiftAdditions/PrinterView.swift new file mode 100644 index 0000000..031e557 --- /dev/null +++ b/Example/SwiftAdditions/PrinterView.swift @@ -0,0 +1,50 @@ +import SwiftUI +import Additions + +struct PrinterView: View { + + @ObservedObject var presenter = PrinterPresenter() + + var body: some View { + Text(presenter.text) + } +} + +class PrinterPresenter: ObservableObject { + @Inject var reader: ReaderProtocol + @Published var text: String = "" + + init() { + reader.start { (c) in + self.text.append(c) + } + } +} + +protocol ReaderProtocol { + func start(update: @escaping (Character) -> Void) +} + +class Reader: ReaderProtocol { + var timer: Timer? + + var characters = "Hello world!!!" + + func start(update: @escaping (Character) -> Void ) { + timer = Timer.scheduledTimer( + withTimeInterval: 0.2, + repeats: true, block: { (timer) in + guard let first = self.characters.first else { + self.cancel() + return + } + self.characters.removeFirst() + update(first) + }) + } + + func cancel() { + timer?.invalidate() + timer = nil + } +} diff --git a/Example/SwiftAdditions/Tasks/DependentTask.swift b/Example/SwiftAdditions/Tasks/DependentTask.swift new file mode 100644 index 0000000..b0d0f12 --- /dev/null +++ b/Example/SwiftAdditions/Tasks/DependentTask.swift @@ -0,0 +1,14 @@ +import Foundation +import Additions + +class DependentTask: AppTask { + @Inject var dispatch: Dispatching + + override func main() { + super.main() + dispatch.dispatchMain(after: 1) { + print("\(self) finished") + self.state = .finished + } + } +} diff --git a/Example/SwiftAdditions/Tasks/MainUISetupTask.swift b/Example/SwiftAdditions/Tasks/MainUISetupTask.swift new file mode 100644 index 0000000..21dcb43 --- /dev/null +++ b/Example/SwiftAdditions/Tasks/MainUISetupTask.swift @@ -0,0 +1,22 @@ +import Foundation +import Additions +import SwiftUI + +class MainUISetupTask: AppTask { + + @Inject var dispatch: Dispatching + override func main() { + super.main() + + dispatch.dispatchMain { + guard let task = self.dependencies.first(where: { $0 is WindowSetupTask }) as? WindowSetupTask else { + return + } + + let contentView = PrinterView() + task.window?.rootViewController = UIHostingController(rootView: contentView) + self.state = .finished + print("\(self) \(#function) finished") + } + } +} diff --git a/Example/SwiftAdditions/Tasks/OnboardingTask.swift b/Example/SwiftAdditions/Tasks/OnboardingTask.swift new file mode 100644 index 0000000..f0bdd0d --- /dev/null +++ b/Example/SwiftAdditions/Tasks/OnboardingTask.swift @@ -0,0 +1,25 @@ +import Foundation +import Additions +import SwiftUI + +class OnboardingTask: AppTask { + + @Inject var dispatch: Dispatching + + override func main() { + super.main() + + dispatch.dispatchMain { + guard let task = self.dependencies.first(where: { $0 is WindowSetupTask }) as? WindowSetupTask else { + return + } + + task.window?.rootViewController = UIHostingController(rootView: OnboardingScreen()) + } + } + + func finish() { + state = .finished + print("\(self) \(#function) finished") + } +} diff --git a/Example/SwiftAdditions/Tasks/PermissionTask.swift b/Example/SwiftAdditions/Tasks/PermissionTask.swift new file mode 100644 index 0000000..50cefc0 --- /dev/null +++ b/Example/SwiftAdditions/Tasks/PermissionTask.swift @@ -0,0 +1,23 @@ +import Foundation +import Additions +import UserNotifications +import UIKit + +class PermissionTask: AppTask { + + override func main() { + super.main() + + let center = UNUserNotificationCenter.current() + center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + + self.state = .finished + print("\(self) finished") + } + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + print("\(self) \(#function) \n with \(deviceToken)") + } + +} diff --git a/Example/SwiftAdditions/Tasks/SomeLongRunningTask.swift b/Example/SwiftAdditions/Tasks/SomeLongRunningTask.swift new file mode 100644 index 0000000..e0132fb --- /dev/null +++ b/Example/SwiftAdditions/Tasks/SomeLongRunningTask.swift @@ -0,0 +1,14 @@ +import Foundation +import Additions + +class SomeLongRunningTask: AppTask { + @Inject var dispatch: Dispatching + + override func main() { + super.main() + dispatch.dispatchMain(after: 1) { + print("\(self) finished") + self.state = .finished + } + } +} diff --git a/Example/SwiftAdditions/Tasks/SyncTask.swift b/Example/SwiftAdditions/Tasks/SyncTask.swift new file mode 100644 index 0000000..de8e464 --- /dev/null +++ b/Example/SwiftAdditions/Tasks/SyncTask.swift @@ -0,0 +1,10 @@ +import Foundation +import Additions + +class SyncTask: NoOpAppTask { + + override func main() { + super.main() + print("\(self) finished") + } +} diff --git a/Example/SwiftAdditions/Tasks/WindowSetupTask.swift b/Example/SwiftAdditions/Tasks/WindowSetupTask.swift new file mode 100644 index 0000000..5f1edd0 --- /dev/null +++ b/Example/SwiftAdditions/Tasks/WindowSetupTask.swift @@ -0,0 +1,29 @@ +import Foundation +import Additions +import SwiftUI + +class WindowSetupTask: AppTask { + + var window: UIWindow? + @Inject var dispatch: Dispatching + + override func main() { + super.main() + + dispatch.dispatchMain { + //launch a loading screen here + if let scene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }), + let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + let vc = UIStoryboard(name: "LaunchScreen", bundle: .main).instantiateInitialViewController() + + window.rootViewController = vc + self.window = window + window.makeKeyAndVisible() + self.state = .finished + print("\(self) finished") + } + } + } + +} diff --git a/Example/SwiftAdditions/ViewController.swift b/Example/SwiftAdditions/ViewController.swift new file mode 100644 index 0000000..0f80420 --- /dev/null +++ b/Example/SwiftAdditions/ViewController.swift @@ -0,0 +1,24 @@ +// +// ViewController.swift +// SwiftAdditions +// +// Created by Marton Kerekes on 02/20/2023. +// Copyright (c) 2023 Marton Kerekes. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + +} + diff --git a/Example/SwiftAdditions_ExampleTests/FooTests.swift b/Example/SwiftAdditions_ExampleTests/FooTests.swift new file mode 100644 index 0000000..1145fc9 --- /dev/null +++ b/Example/SwiftAdditions_ExampleTests/FooTests.swift @@ -0,0 +1,50 @@ +import Combine +import XCTest +import Additions +@testable import SwiftAdditions_Example + +class FooTests: XCTestCase { + + var cancellables = [AnyCancellable]() + var mockReader: MockReader! + + override func setUp() { + super.setUp() + mockReader = MockReader() + + CoreServiceLocator.shared.add { + Register(ReaderProtocol.self) { self.mockReader } + } + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + + CoreServiceLocator.shared.removeAll() + + mockReader = nil + } + + func testFoo() { + mockReader.mockValue = "b" + let presenter = PrinterPresenter() + var capturedValue: String! + let exp = expectation(description: "foo") + presenter.$text.sink { + capturedValue = $0 + exp.fulfill() + }.store(in: &cancellables) + + waitForExpectations(timeout: 1) + XCTAssertEqual(capturedValue, "b") + } + +} + +class MockReader: ReaderProtocol { + var mockValue: Character! + func start(update: @escaping (Character) -> Void) { + update(mockValue) + } +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..bb877a5 --- /dev/null +++ b/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "Additions", + platforms: [ + .iOS(.v13) + ], + products: [ + .library( + name: "Additions", + targets: ["Additions"] + ) + ], + targets: [ + .target( + name: "Additions", + dependencies: [], + path: "SwiftAdditions/Classes" + ) + ] +) diff --git a/README.md b/README.md index 5b015bf..9615a73 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ -# SwiftAdditions -A swift library for syntax sugar, dependency injection and a controlled startup process +# Additions + +[![Build status](https://badger.engprod-pro.mpi-internal.com/badge/travis/scmspain/ios-common--lib-additions)](https://badger.engprod-pro.mpi-internal.com/redirect/travis/scmspain/ios-common--lib-additions) +[![Version](https://img.shields.io/cocoapods/v/Additions.svg?style=flat)](https://cocoapods.org/pods/Additions) +[![License](https://img.shields.io/cocoapods/l/Additions.svg?style=flat)](https://cocoapods.org/pods/Additions) +[![Platform](https://img.shields.io/cocoapods/p/Additions.svg?style=flat)](https://cocoapods.org/pods/Additions) + +## Example + +To run the example project, clone the repo, and run `pod install` from the Example directory first. + +There's multiple niceties in this repo anyone in Adevinta might be able to use + +### Additions +- `CoreServiceLocator` simplification using `@Inject` +- `Dispatching` for easy handling of `DispatchQueue` and `GCD` +- `AsyncOperation` simple wrapper over `NSOperation` +- [AppTasks](Documentation/AppTasks.md) in conjuction with `AppTask: AsyncOperation` and `ServiceProvider` will provide a coordinated way to start any application. + +### Extensions and Syntax Sugar: +- Array +- Binding +- ProcessInfo +- SafeInitialisers for Foundation stuff +- String +- URL +- ProcessInfo +- Dictionary + + +## Requirements + +- iOS > 13 + +## Installation + +Additions is available through [CocoaPods](https://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod 'Additions' +``` + +## Author + +Marton Kerekes, marton.kerekes@adevinta.com + +## License + +Additions is available under the MIT license. See the LICENSE file for more info. diff --git a/SPM Example/SwiftAdditions.xcodeproj/project.pbxproj b/SPM Example/SwiftAdditions.xcodeproj/project.pbxproj new file mode 100644 index 0000000..997f8c3 --- /dev/null +++ b/SPM Example/SwiftAdditions.xcodeproj/project.pbxproj @@ -0,0 +1,547 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; + 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; + FBE5AACA29A3C228002AC234 /* ExampleAppServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AAC929A3C228002AC234 /* ExampleAppServices.swift */; }; + FBE5AAD429A3C269002AC234 /* WindowSetupTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AACD29A3C269002AC234 /* WindowSetupTask.swift */; }; + FBE5AAD529A3C26A002AC234 /* SomeLongRunningTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AACE29A3C269002AC234 /* SomeLongRunningTask.swift */; }; + FBE5AAD629A3C26A002AC234 /* OnboardingTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AACF29A3C269002AC234 /* OnboardingTask.swift */; }; + FBE5AAD729A3C26A002AC234 /* PermissionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AAD029A3C269002AC234 /* PermissionTask.swift */; }; + FBE5AAD829A3C26A002AC234 /* MainUISetupTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AAD129A3C269002AC234 /* MainUISetupTask.swift */; }; + FBE5AAD929A3C26A002AC234 /* SyncTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AAD229A3C269002AC234 /* SyncTask.swift */; }; + FBE5AADA29A3C26A002AC234 /* DependentTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AAD329A3C269002AC234 /* DependentTask.swift */; }; + FBE5AADD29A3C287002AC234 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AADB29A3C287002AC234 /* OnboardingScreen.swift */; }; + FBE5AADE29A3C287002AC234 /* PrinterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AADC29A3C287002AC234 /* PrinterView.swift */; }; + FBE5AAE029A3C4DC002AC234 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AADF29A3C4DC002AC234 /* SceneDelegate.swift */; }; + FBE5AAE229A3C5A2002AC234 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBE5AAE129A3C5A2002AC234 /* LaunchScreen.storyboard */; }; + FBE5AAE629A4BBA1002AC234 /* Additions in Frameworks */ = {isa = PBXBuildFile; productRef = FBE5AAE529A4BBA1002AC234 /* Additions */; }; + FBE5AAEE29A4BE97002AC234 /* SwiftAdditions_ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE5AAED29A4BE97002AC234 /* SwiftAdditions_ExampleTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + FBE5AAEF29A4BE97002AC234 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = SwiftAdditions_Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 4FBE22E96276CC982359605F /* Pods_SwiftAdditions_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAdditions_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD01AFB9204008FA782 /* SwiftAdditions_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAdditions_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + B74C47468BA71D4FC79CA52B /* Pods_SwiftAdditions_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAdditions_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FBE5AAC929A3C228002AC234 /* ExampleAppServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppServices.swift; sourceTree = ""; }; + FBE5AACD29A3C269002AC234 /* WindowSetupTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowSetupTask.swift; sourceTree = ""; }; + FBE5AACE29A3C269002AC234 /* SomeLongRunningTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SomeLongRunningTask.swift; sourceTree = ""; }; + FBE5AACF29A3C269002AC234 /* OnboardingTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingTask.swift; sourceTree = ""; }; + FBE5AAD029A3C269002AC234 /* PermissionTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionTask.swift; sourceTree = ""; }; + FBE5AAD129A3C269002AC234 /* MainUISetupTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainUISetupTask.swift; sourceTree = ""; }; + FBE5AAD229A3C269002AC234 /* SyncTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncTask.swift; sourceTree = ""; }; + FBE5AAD329A3C269002AC234 /* DependentTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DependentTask.swift; sourceTree = ""; }; + FBE5AADB29A3C287002AC234 /* OnboardingScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = ""; }; + FBE5AADC29A3C287002AC234 /* PrinterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrinterView.swift; sourceTree = ""; }; + FBE5AADF29A3C4DC002AC234 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + FBE5AAE129A3C5A2002AC234 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = "../../../ios-common--lib-additions/Example/Additions/LaunchScreen.storyboard"; sourceTree = ""; }; + FBE5AAEB29A4BE97002AC234 /* SwiftAdditions_ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftAdditions_ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FBE5AAED29A4BE97002AC234 /* SwiftAdditions_ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftAdditions_ExampleTests.swift; sourceTree = ""; }; + FBE5AB4C29A4CAF5002AC234 /* SwiftAdditions */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftAdditions; path = ..; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACCD1AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FBE5AAE629A4BBA1002AC234 /* Additions in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBE5AAE829A4BE97002AC234 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + FBE5AB4B29A4CAF5002AC234 /* Packages */, + 607FACD21AFB9204008FA782 /* Example for SwiftAdditions */, + FBE5AAEC29A4BE97002AC234 /* SwiftAdditions_ExampleTests */, + 607FACD11AFB9204008FA782 /* Products */, + BE0DBDA585C11D4D85A69730 /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACD01AFB9204008FA782 /* SwiftAdditions_Example.app */, + FBE5AAEB29A4BE97002AC234 /* SwiftAdditions_ExampleTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACD21AFB9204008FA782 /* Example for SwiftAdditions */ = { + isa = PBXGroup; + children = ( + 607FACD51AFB9204008FA782 /* AppDelegate.swift */, + FBE5AADF29A3C4DC002AC234 /* SceneDelegate.swift */, + 607FACD71AFB9204008FA782 /* ViewController.swift */, + FBE5AAC929A3C228002AC234 /* ExampleAppServices.swift */, + FBE5AADB29A3C287002AC234 /* OnboardingScreen.swift */, + FBE5AADC29A3C287002AC234 /* PrinterView.swift */, + FBE5AAE129A3C5A2002AC234 /* LaunchScreen.storyboard */, + FBE5AACC29A3C269002AC234 /* Tasks */, + 607FACDC1AFB9204008FA782 /* Images.xcassets */, + 607FACD31AFB9204008FA782 /* Supporting Files */, + ); + name = "Example for SwiftAdditions"; + path = SwiftAdditions; + sourceTree = ""; + }; + 607FACD31AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACD41AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + BE0DBDA585C11D4D85A69730 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4FBE22E96276CC982359605F /* Pods_SwiftAdditions_Example.framework */, + B74C47468BA71D4FC79CA52B /* Pods_SwiftAdditions_Tests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + FBE5AACC29A3C269002AC234 /* Tasks */ = { + isa = PBXGroup; + children = ( + FBE5AACD29A3C269002AC234 /* WindowSetupTask.swift */, + FBE5AACE29A3C269002AC234 /* SomeLongRunningTask.swift */, + FBE5AACF29A3C269002AC234 /* OnboardingTask.swift */, + FBE5AAD029A3C269002AC234 /* PermissionTask.swift */, + FBE5AAD129A3C269002AC234 /* MainUISetupTask.swift */, + FBE5AAD229A3C269002AC234 /* SyncTask.swift */, + FBE5AAD329A3C269002AC234 /* DependentTask.swift */, + ); + path = Tasks; + sourceTree = ""; + }; + FBE5AAEC29A4BE97002AC234 /* SwiftAdditions_ExampleTests */ = { + isa = PBXGroup; + children = ( + FBE5AAED29A4BE97002AC234 /* SwiftAdditions_ExampleTests.swift */, + ); + path = SwiftAdditions_ExampleTests; + sourceTree = ""; + }; + FBE5AB4B29A4CAF5002AC234 /* Packages */ = { + isa = PBXGroup; + children = ( + FBE5AB4C29A4CAF5002AC234 /* SwiftAdditions */, + ); + name = Packages; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACCF1AFB9204008FA782 /* SwiftAdditions_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAdditions_Example" */; + buildPhases = ( + 607FACCC1AFB9204008FA782 /* Sources */, + 607FACCD1AFB9204008FA782 /* Frameworks */, + 607FACCE1AFB9204008FA782 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftAdditions_Example; + packageProductDependencies = ( + FBE5AAE529A4BBA1002AC234 /* Additions */, + ); + productName = SwiftAdditions; + productReference = 607FACD01AFB9204008FA782 /* SwiftAdditions_Example.app */; + productType = "com.apple.product-type.application"; + }; + FBE5AAEA29A4BE97002AC234 /* SwiftAdditions_ExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FBE5AAF129A4BE97002AC234 /* Build configuration list for PBXNativeTarget "SwiftAdditions_ExampleTests" */; + buildPhases = ( + FBE5AAE729A4BE97002AC234 /* Sources */, + FBE5AAE829A4BE97002AC234 /* Frameworks */, + FBE5AAE929A4BE97002AC234 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FBE5AAF029A4BE97002AC234 /* PBXTargetDependency */, + ); + name = SwiftAdditions_ExampleTests; + productName = SwiftAdditions_ExampleTests; + productReference = FBE5AAEB29A4BE97002AC234 /* SwiftAdditions_ExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1400; + LastUpgradeCheck = 1400; + ORGANIZATIONNAME = CocoaPods; + TargetAttributes = { + 607FACCF1AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = ""; + }; + FBE5AAEA29A4BE97002AC234 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAdditions" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + Base, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACCF1AFB9204008FA782 /* SwiftAdditions_Example */, + FBE5AAEA29A4BE97002AC234 /* SwiftAdditions_ExampleTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACCE1AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, + FBE5AAE229A3C5A2002AC234 /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBE5AAE929A4BE97002AC234 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607FACCC1AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FBE5AADD29A3C287002AC234 /* OnboardingScreen.swift in Sources */, + FBE5AAD429A3C269002AC234 /* WindowSetupTask.swift in Sources */, + FBE5AAD529A3C26A002AC234 /* SomeLongRunningTask.swift in Sources */, + 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, + FBE5AAD929A3C26A002AC234 /* SyncTask.swift in Sources */, + FBE5AADA29A3C26A002AC234 /* DependentTask.swift in Sources */, + FBE5AAD629A3C26A002AC234 /* OnboardingTask.swift in Sources */, + FBE5AACA29A3C228002AC234 /* ExampleAppServices.swift in Sources */, + FBE5AAE029A3C4DC002AC234 /* SceneDelegate.swift in Sources */, + FBE5AAD729A3C26A002AC234 /* PermissionTask.swift in Sources */, + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + FBE5AAD829A3C26A002AC234 /* MainUISetupTask.swift in Sources */, + FBE5AADE29A3C287002AC234 /* PrinterView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBE5AAE729A4BE97002AC234 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FBE5AAEE29A4BE97002AC234 /* SwiftAdditions_ExampleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + FBE5AAF029A4BE97002AC234 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* SwiftAdditions_Example */; + targetProxy = FBE5AAEF29A4BE97002AC234 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF01AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = SwiftAdditions/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF11AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = SwiftAdditions/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + FBE5AAF229A4BE97002AC234 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = RCTL45N8E9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.adevinta.SwiftAdditions-ExampleTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAdditions_Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SwiftAdditions_Example"; + }; + name = Debug; + }; + FBE5AAF329A4BE97002AC234 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = RCTL45N8E9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.adevinta.SwiftAdditions-ExampleTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAdditions_Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SwiftAdditions_Example"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAdditions" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAdditions_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF01AFB9204008FA782 /* Debug */, + 607FACF11AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FBE5AAF129A4BE97002AC234 /* Build configuration list for PBXNativeTarget "SwiftAdditions_ExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FBE5AAF229A4BE97002AC234 /* Debug */, + FBE5AAF329A4BE97002AC234 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + FBE5AAE529A4BBA1002AC234 /* Additions */ = { + isa = XCSwiftPackageProductDependency; + productName = Additions; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/SPM Example/SwiftAdditions.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SPM Example/SwiftAdditions.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..89bb87c --- /dev/null +++ b/SPM Example/SwiftAdditions.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SPM Example/SwiftAdditions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SPM Example/SwiftAdditions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SPM Example/SwiftAdditions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SPM Example/SwiftAdditions.xcodeproj/xcshareddata/xcschemes/SwiftAdditions-Example.xcscheme b/SPM Example/SwiftAdditions.xcodeproj/xcshareddata/xcschemes/SwiftAdditions-Example.xcscheme new file mode 100644 index 0000000..22c117a --- /dev/null +++ b/SPM Example/SwiftAdditions.xcodeproj/xcshareddata/xcschemes/SwiftAdditions-Example.xcscheme @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SPM Example/SwiftAdditions/AppDelegate.swift b/SPM Example/SwiftAdditions/AppDelegate.swift new file mode 100644 index 0000000..397b00b --- /dev/null +++ b/SPM Example/SwiftAdditions/AppDelegate.swift @@ -0,0 +1,27 @@ +import Additions +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + private var tasks: AppTasks? { + AppTasks.shared + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + + tasks?.forEach { + _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions) + } + + return true + } + + func applicationDidEnterBackground(_ application: UIApplication) { + tasks?.forEach { + $0.applicationDidEnterBackground?(application) + } + } +} + diff --git a/SPM Example/SwiftAdditions/ExampleAppServices.swift b/SPM Example/SwiftAdditions/ExampleAppServices.swift new file mode 100644 index 0000000..e845cc7 --- /dev/null +++ b/SPM Example/SwiftAdditions/ExampleAppServices.swift @@ -0,0 +1,44 @@ +import Foundation +import Additions + +class ExampleAppServices: ServiceProvider { + + lazy var someLongRunningTask = SomeLongRunningTask() + lazy var shortTask = SyncTask() + lazy var dependentTask = DependentTask() + lazy var permissionTask = PermissionTask() + lazy var windowSetupTask = WindowSetupTask() + lazy var onboardingTask = OnboardingTask() + lazy var mainUISetupTask = MainUISetupTask() + + lazy var appTasks: [AppTask] = { + + dependentTask.addDependency(someLongRunningTask) + + windowSetupTask.addDependency(dependentTask) + + onboardingTask.addDependency(windowSetupTask) + + permissionTask.addDependency(onboardingTask) + + mainUISetupTask.addDependency(permissionTask) + mainUISetupTask.addDependency(windowSetupTask) + + return [ + someLongRunningTask, + shortTask, + dependentTask, + permissionTask, + windowSetupTask, + onboardingTask, + mainUISetupTask, + ] + }() + + func modules() -> [Register] { + [ + Register(ReaderProtocol.self, { Reader() }), + Register { self.onboardingTask } + ] + } +} diff --git a/SPM Example/SwiftAdditions/Images.xcassets/AppIcon.appiconset/Contents.json b/SPM Example/SwiftAdditions/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..7006c9e --- /dev/null +++ b/SPM Example/SwiftAdditions/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/SPM Example/SwiftAdditions/Info.plist b/SPM Example/SwiftAdditions/Info.plist new file mode 100644 index 0000000..1e0787c --- /dev/null +++ b/SPM Example/SwiftAdditions/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/SPM Example/SwiftAdditions/OnboardingScreen.swift b/SPM Example/SwiftAdditions/OnboardingScreen.swift new file mode 100644 index 0000000..69cf969 --- /dev/null +++ b/SPM Example/SwiftAdditions/OnboardingScreen.swift @@ -0,0 +1,19 @@ +import SwiftUI +import Additions + +struct OnboardingScreen: View { + + @Inject var onboardingTask: OnboardingTask + + var body: some View { + VStack { + Text("Hello, World!") + + Button(action: { + onboardingTask.finish() + }, label: { + Text("Got it!") + }) + } + } +} diff --git a/SPM Example/SwiftAdditions/PrinterView.swift b/SPM Example/SwiftAdditions/PrinterView.swift new file mode 100644 index 0000000..031e557 --- /dev/null +++ b/SPM Example/SwiftAdditions/PrinterView.swift @@ -0,0 +1,50 @@ +import SwiftUI +import Additions + +struct PrinterView: View { + + @ObservedObject var presenter = PrinterPresenter() + + var body: some View { + Text(presenter.text) + } +} + +class PrinterPresenter: ObservableObject { + @Inject var reader: ReaderProtocol + @Published var text: String = "" + + init() { + reader.start { (c) in + self.text.append(c) + } + } +} + +protocol ReaderProtocol { + func start(update: @escaping (Character) -> Void) +} + +class Reader: ReaderProtocol { + var timer: Timer? + + var characters = "Hello world!!!" + + func start(update: @escaping (Character) -> Void ) { + timer = Timer.scheduledTimer( + withTimeInterval: 0.2, + repeats: true, block: { (timer) in + guard let first = self.characters.first else { + self.cancel() + return + } + self.characters.removeFirst() + update(first) + }) + } + + func cancel() { + timer?.invalidate() + timer = nil + } +} diff --git a/SPM Example/SwiftAdditions/SceneDelegate.swift b/SPM Example/SwiftAdditions/SceneDelegate.swift new file mode 100644 index 0000000..61c0c29 --- /dev/null +++ b/SPM Example/SwiftAdditions/SceneDelegate.swift @@ -0,0 +1,28 @@ +import UIKit +import SwiftUI +import Additions + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + private lazy var serviceProviders: [ServiceProvider] = [ + ExampleAppServices(), + AdditionsServices(), + ] + + private lazy var tasks = AppTasks.build(serviceProviders: serviceProviders) { + print("all tasks completed") + } + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + tasks.forEach { + $0.scene?(scene, willConnectTo: session, options: connectionOptions) + } + } + + func sceneDidDisconnect(_ scene: UIScene) { + tasks.forEach { + $0.sceneDidEnterBackground?(scene) + } + } +} + diff --git a/SPM Example/SwiftAdditions/Tasks/DependentTask.swift b/SPM Example/SwiftAdditions/Tasks/DependentTask.swift new file mode 100644 index 0000000..b0d0f12 --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/DependentTask.swift @@ -0,0 +1,14 @@ +import Foundation +import Additions + +class DependentTask: AppTask { + @Inject var dispatch: Dispatching + + override func main() { + super.main() + dispatch.dispatchMain(after: 1) { + print("\(self) finished") + self.state = .finished + } + } +} diff --git a/SPM Example/SwiftAdditions/Tasks/MainUISetupTask.swift b/SPM Example/SwiftAdditions/Tasks/MainUISetupTask.swift new file mode 100644 index 0000000..21dcb43 --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/MainUISetupTask.swift @@ -0,0 +1,22 @@ +import Foundation +import Additions +import SwiftUI + +class MainUISetupTask: AppTask { + + @Inject var dispatch: Dispatching + override func main() { + super.main() + + dispatch.dispatchMain { + guard let task = self.dependencies.first(where: { $0 is WindowSetupTask }) as? WindowSetupTask else { + return + } + + let contentView = PrinterView() + task.window?.rootViewController = UIHostingController(rootView: contentView) + self.state = .finished + print("\(self) \(#function) finished") + } + } +} diff --git a/SPM Example/SwiftAdditions/Tasks/OnboardingTask.swift b/SPM Example/SwiftAdditions/Tasks/OnboardingTask.swift new file mode 100644 index 0000000..f0bdd0d --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/OnboardingTask.swift @@ -0,0 +1,25 @@ +import Foundation +import Additions +import SwiftUI + +class OnboardingTask: AppTask { + + @Inject var dispatch: Dispatching + + override func main() { + super.main() + + dispatch.dispatchMain { + guard let task = self.dependencies.first(where: { $0 is WindowSetupTask }) as? WindowSetupTask else { + return + } + + task.window?.rootViewController = UIHostingController(rootView: OnboardingScreen()) + } + } + + func finish() { + state = .finished + print("\(self) \(#function) finished") + } +} diff --git a/SPM Example/SwiftAdditions/Tasks/PermissionTask.swift b/SPM Example/SwiftAdditions/Tasks/PermissionTask.swift new file mode 100644 index 0000000..50cefc0 --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/PermissionTask.swift @@ -0,0 +1,23 @@ +import Foundation +import Additions +import UserNotifications +import UIKit + +class PermissionTask: AppTask { + + override func main() { + super.main() + + let center = UNUserNotificationCenter.current() + center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + + self.state = .finished + print("\(self) finished") + } + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + print("\(self) \(#function) \n with \(deviceToken)") + } + +} diff --git a/SPM Example/SwiftAdditions/Tasks/SomeLongRunningTask.swift b/SPM Example/SwiftAdditions/Tasks/SomeLongRunningTask.swift new file mode 100644 index 0000000..e0132fb --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/SomeLongRunningTask.swift @@ -0,0 +1,14 @@ +import Foundation +import Additions + +class SomeLongRunningTask: AppTask { + @Inject var dispatch: Dispatching + + override func main() { + super.main() + dispatch.dispatchMain(after: 1) { + print("\(self) finished") + self.state = .finished + } + } +} diff --git a/SPM Example/SwiftAdditions/Tasks/SyncTask.swift b/SPM Example/SwiftAdditions/Tasks/SyncTask.swift new file mode 100644 index 0000000..de8e464 --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/SyncTask.swift @@ -0,0 +1,10 @@ +import Foundation +import Additions + +class SyncTask: NoOpAppTask { + + override func main() { + super.main() + print("\(self) finished") + } +} diff --git a/SPM Example/SwiftAdditions/Tasks/WindowSetupTask.swift b/SPM Example/SwiftAdditions/Tasks/WindowSetupTask.swift new file mode 100644 index 0000000..5f1edd0 --- /dev/null +++ b/SPM Example/SwiftAdditions/Tasks/WindowSetupTask.swift @@ -0,0 +1,29 @@ +import Foundation +import Additions +import SwiftUI + +class WindowSetupTask: AppTask { + + var window: UIWindow? + @Inject var dispatch: Dispatching + + override func main() { + super.main() + + dispatch.dispatchMain { + //launch a loading screen here + if let scene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }), + let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + let vc = UIStoryboard(name: "LaunchScreen", bundle: .main).instantiateInitialViewController() + + window.rootViewController = vc + self.window = window + window.makeKeyAndVisible() + self.state = .finished + print("\(self) finished") + } + } + } + +} diff --git a/SPM Example/SwiftAdditions/ViewController.swift b/SPM Example/SwiftAdditions/ViewController.swift new file mode 100644 index 0000000..0f80420 --- /dev/null +++ b/SPM Example/SwiftAdditions/ViewController.swift @@ -0,0 +1,24 @@ +// +// ViewController.swift +// SwiftAdditions +// +// Created by Marton Kerekes on 02/20/2023. +// Copyright (c) 2023 Marton Kerekes. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + +} + diff --git a/SPM Example/SwiftAdditions_ExampleTests/SwiftAdditions_ExampleTests.swift b/SPM Example/SwiftAdditions_ExampleTests/SwiftAdditions_ExampleTests.swift new file mode 100644 index 0000000..36b937a --- /dev/null +++ b/SPM Example/SwiftAdditions_ExampleTests/SwiftAdditions_ExampleTests.swift @@ -0,0 +1,50 @@ +import Combine +import XCTest +import Additions +@testable import SwiftAdditions_Example + +class Tests: XCTestCase { + + var cancellables = [AnyCancellable]() + var mockReader: MockReader! + + override func setUp() { + super.setUp() + mockReader = MockReader() + + CoreServiceLocator.shared.add { + Register(ReaderProtocol.self) { self.mockReader } + } + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + + CoreServiceLocator.shared.removeAll() + + mockReader = nil + } + + func testFoo() { + mockReader.mockValue = "b" + let presenter = PrinterPresenter() + var capturedValue: String! + let exp = expectation(description: "foo") + presenter.$text.sink { + capturedValue = $0 + exp.fulfill() + }.store(in: &cancellables) + + waitForExpectations(timeout: 1) + XCTAssertEqual(capturedValue, "b") + } + +} + +class MockReader: ReaderProtocol { + var mockValue: Character! + func start(update: @escaping (Character) -> Void) { + update(mockValue) + } +} diff --git a/SwiftAdditions.podspec b/SwiftAdditions.podspec new file mode 100644 index 0000000..4335204 --- /dev/null +++ b/SwiftAdditions.podspec @@ -0,0 +1,25 @@ +# +# Be sure to run `pod lib lint SwiftAdditions.podspec` to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'SwiftAdditions' + s.module_name = "Additions" + s.version = '1.0.0' + s.summary = 'A short description of Additions.' + + s.description = 'A swift library for syntax sugar, dependency injection and a controlled startup process' + + s.homepage = 'https://github.com/AdevintaSpain/SwiftAdditions' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Marton Kerekes' => 'kerekes.j.marton@gmail.com' } + s.source = { :git => 'git@github.com:AdevintaSpain/SwiftAdditions.git', :tag => s.version.to_s } + + s.ios.deployment_target = '13.0' + + s.source_files = 'SwiftAdditions/Classes/**/*' +end diff --git a/SwiftAdditions/Assets/.gitkeep b/SwiftAdditions/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/SwiftAdditions/Classes/.gitkeep b/SwiftAdditions/Classes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/SwiftAdditions/Classes/Additions/AdditionsServices.swift b/SwiftAdditions/Classes/Additions/AdditionsServices.swift new file mode 100644 index 0000000..408c529 --- /dev/null +++ b/SwiftAdditions/Classes/Additions/AdditionsServices.swift @@ -0,0 +1,13 @@ +import Foundation + +public class AdditionsServices: ServiceProvider { + public init() {} + + public var appTasks = [AppTask]() + + public func modules() -> [Register] { + [ + Register(Dispatching.self) { Dispatcher() } + ] + } +} diff --git a/SwiftAdditions/Classes/Additions/AsyncOperation.swift b/SwiftAdditions/Classes/Additions/AsyncOperation.swift new file mode 100644 index 0000000..e33ddd1 --- /dev/null +++ b/SwiftAdditions/Classes/Additions/AsyncOperation.swift @@ -0,0 +1,42 @@ +import Foundation + +open class AsyncOperation: Operation { + override open var isAsynchronous: Bool { return true } + override open var isExecuting: Bool { return state == .executing } + override open var isFinished: Bool { return state == .finished } + + public var state = State.ready { + willSet { + willChangeValue(forKey: state.keyPath) + willChangeValue(forKey: newValue.keyPath) + } + didSet { + didChangeValue(forKey: state.keyPath) + didChangeValue(forKey: oldValue.keyPath) + } + } + + public enum State: String { + case ready = "Ready" + case executing = "Executing" + case finished = "Finished" + fileprivate var keyPath: String { return "is" + self.rawValue } + } + + override open func start() { + if self.isCancelled { + state = .finished + } else { + state = .ready + main() + } + } + + override open func main() { + if self.isCancelled { + state = .finished + } else { + state = .executing + } + } +} diff --git a/SwiftAdditions/Classes/Additions/CoreServiceLocator.swift b/SwiftAdditions/Classes/Additions/CoreServiceLocator.swift new file mode 100644 index 0000000..d897060 --- /dev/null +++ b/SwiftAdditions/Classes/Additions/CoreServiceLocator.swift @@ -0,0 +1,157 @@ +import Foundation +//based on https://github.com/ZamzamInc/Shank + +public protocol ServiceLocator { + var root: CoreServiceLocator { get } + func services() -> [Register] +} + +public extension ServiceLocator { + var root: CoreServiceLocator { + CoreServiceLocator.shared + } + + func services() -> [Register] { + [] + } +} + +/// +/// A dependency collection that provides resolutions for object instances. +/// +/// Can be used directly: +/// use +/// ``` +/// func add(@Factory _ modules: () -> [Register]) +/// ``` +/// +/// **IMPORTANT** +/// Best used through `AppTasks`. +/// +/// See documentation to on how to add `ServiceProvider` instances (which provide their own set of tasks and registries `[Register]`). +/// + +public class CoreServiceLocator { + /// Stored object instance factories. + private var services = [String: Register]() + + fileprivate init() {} + deinit { services.removeAll() } +} + +extension CoreServiceLocator { + /// Composition root container of dependencies. + public static var shared = CoreServiceLocator() + + /// Registers a specific type and its instantiating factory. + public func add(@Factory _ module: () -> Register) { + let module = module() + services[module.name] = module + } + + /// Register factories + /// - Parameter modules: The factories added + public func add(@Factory _ modules: () -> [Register]) { + modules().forEach { + services[$0.name] = $0 + } + } + + /// Register factories through the `ServiceProvider` array. Each has a set of `Register` arrays. Note that they can be overriden if two `ServiceProvider` instances carry the same type of `Register`. The last one stays. + /// - Parameter buildTasks: The ServiceProvider added + public func addBuildTasks(_ buildTasks: () -> [ServiceProvider]) { + buildTasks().forEach { (task) in + task.modules().forEach { + services[$0.name] = $0 + } + } + } + + public func removeAll() { + services.removeAll() + } + + /// Resolves through inference and returns an instance of the given type from the current default container. + /// **Important** + /// - Although `public` for legacy purposes, try to avoid using this, instead use `Inject` + /// - If the dependency is not found, an exception will occur. + /// + public func module(for type: T.Type = T.self) -> T { + let name = String(describing: T.self) + + guard let component = services[name]?.resolve() else { + fatalError("Dependency '\(T.self)' not resolved!") + } + + return component as! T + } +} + +// MARK: Public API +public extension CoreServiceLocator { + @resultBuilder struct Factory { + public static func buildBlock(_ modules: Register...) -> [Register] { modules } + public static func buildBlock(_ module: Register) -> Register { module } + } +} + +/// +/// A type that contributes to the object graph. +/// +/// If you use protocols: +/// ``` +/// CoreServiceLocator.shared.add { +/// Register(SomeProtocol.self) { SomeImplementation() } // can be fetched using @Inject var foo: SomeProtocol +/// } +/// ``` +/// Otherwise type is inferred from `T.self`: +/// CoreServiceLocator.shared.add { +/// Register { SomeImplementation() } // can be fetched using @Inject var foo: SomeImplementation +/// } +/// +///--- +/// **IMPORTANT!**ServiceProvider +/// - Best to use through `ServiceProvider`. (avoids using `CoreServiceLocator.shared`) See `ServiceProvider` documentation for more info. +/// - see `PrinterTests` how to replace protocol implementations with mocks. +/// + +public struct Register { + fileprivate let name: String + fileprivate let resolve: () -> Any + + public init(_ type: T.Type = T.self, _ resolve: @escaping () -> T) { + self.name = String(describing: T.self) + self.resolve = resolve + } +} + +/// +/// Resolves an instance from the dependency injection container. +/// +/// Can fetch based on protocol names also: +/// +/// ``` +/// @Inject var foo: SomeProtocol +/// ``` +/// +@propertyWrapper +public class Inject: ObservableObject { + private let name: String? + private var storage: Value? + + public var wrappedValue: Value { + storage ?? { + let value: Value = CoreServiceLocator.shared.module(for: Value.self) + storage = value // Reuse instance for later + return value + }() + } + + public init() { + self.name = nil + } + + public init(_ type: Value.Type = Value.self) { + self.name = String(describing: Value.self) + } +} diff --git a/SwiftAdditions/Classes/Additions/Dispatching.swift b/SwiftAdditions/Classes/Additions/Dispatching.swift new file mode 100644 index 0000000..69ba895 --- /dev/null +++ b/SwiftAdditions/Classes/Additions/Dispatching.swift @@ -0,0 +1,64 @@ +import Foundation + +public typealias Action = () -> Void + +public protocol Dispatching { + func dispatchMain(block: @escaping Action) + func dispatchMain(after seconds: Double, block: @escaping Action) + func block(_ block: @escaping Action) + func dispatch(after seconds: Double, queue: DispatchQueue, block: @escaping Action) + + func onMain(result: T, completion: @escaping (T) -> Void) +} + +public struct Dispatcher: Dispatching { + public init() {} + public func dispatchMain(block: @escaping Action) { + DispatchQueue.main.async(execute: block) + } + + public func dispatchMain(after seconds: Double = 0, block: @escaping Action) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(seconds * 100)), execute: block) + } + + public func block(_ block: @escaping Action) { + dispatch(queue: DispatchQueue.global(), block: block) + } + + public func dispatch(queue: DispatchQueue, block: @escaping Action) { + queue.async(execute: block) + } + + public func dispatch(after seconds: Double = 0, queue: DispatchQueue, block: @escaping Action) { + queue.asyncAfter(deadline: .now() + .milliseconds(Int(seconds * 100)), execute: block) + } + + public func onMain(result: T, completion: @escaping (T) -> Void) { + dispatchMain { + completion(result) + } + } +} + +public struct MockDispatcher: Dispatching { + public init() {} + public func dispatchMain(block: @escaping Action) { + block() + } + + public func dispatchMain(after seconds: Double, block: @escaping Action) { + block() + } + + public func block(_ block: @escaping Action) { + block() + } + + public func dispatch(after seconds: Double, queue: DispatchQueue, block: @escaping Action) { + block() + } + + public func onMain(result: T, completion: @escaping (T) -> Void) { + completion(result) + } +} diff --git a/SwiftAdditions/Classes/Extensions/Array.swift b/SwiftAdditions/Classes/Extensions/Array.swift new file mode 100755 index 0000000..b06c314 --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/Array.swift @@ -0,0 +1,7 @@ +import Foundation + +public extension Array { + subscript(safe index: Int) -> Element? { + return indices.contains(index) ? self[index] : .none + } +} diff --git a/SwiftAdditions/Classes/Extensions/Binding.swift b/SwiftAdditions/Classes/Extensions/Binding.swift new file mode 100644 index 0000000..3c21558 --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/Binding.swift @@ -0,0 +1,17 @@ +import SwiftUI +import Combine + +@available(iOS 13.0, *) +extension Binding { + public func onChange(_ closure: @escaping (Value) -> Void) -> Self { + return Binding( + get: { + wrappedValue + }, + set: { + self.wrappedValue = $0 + closure($0) + } + ) + } +} diff --git a/SwiftAdditions/Classes/Extensions/Buildable.swift b/SwiftAdditions/Classes/Extensions/Buildable.swift new file mode 100644 index 0000000..3e6070a --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/Buildable.swift @@ -0,0 +1,11 @@ +import Foundation + +public protocol Buildable {} + +public extension Buildable { + func set(_ keyPath: WritableKeyPath, to newValue: T) -> Self { + var copy = self + copy[keyPath: keyPath] = newValue + return copy + } +} diff --git a/SwiftAdditions/Classes/Extensions/Dictionary.swift b/SwiftAdditions/Classes/Extensions/Dictionary.swift new file mode 100644 index 0000000..4709b7e --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/Dictionary.swift @@ -0,0 +1,12 @@ +import Foundation + +extension Dictionary where Key == String, Value == String { + static func + (left: [K: V], right: [K: V]) -> [K: V] { + var result = left + for (k, v) in right { + result[k] = v + } + + return result + } +} diff --git a/SwiftAdditions/Classes/Extensions/ProcessInfo.swift b/SwiftAdditions/Classes/Extensions/ProcessInfo.swift new file mode 100644 index 0000000..7256a1f --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/ProcessInfo.swift @@ -0,0 +1,11 @@ +import Foundation + +public extension ProcessInfo { + var isUITesting: Bool { + if let testing = environment["isTesting"], testing == "true" { + return true + } else { + return false + } + } +} diff --git a/SwiftAdditions/Classes/Extensions/SafeInitialisers.swift b/SwiftAdditions/Classes/Extensions/SafeInitialisers.swift new file mode 100644 index 0000000..c485f97 --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/SafeInitialisers.swift @@ -0,0 +1,59 @@ +import Foundation + +public extension Int { + init?(safe value: String?) { + guard let value = value, let safeInt = Int(value) else { return nil } + self = safeInt + } +} + +public extension Double { + init?(safe value: String?) { + guard let value = value, let safeInt = Double(value) else { return nil } + self = safeInt + } +} + +public extension String { + init?(safe value: String?) { + guard let value = value else { return nil } + self = value + } + + init?(safe value: Int64?) { + guard let value = value else { return nil } + self = String(value) + } +} + +public extension Bool { + init?(safe value: String?) { + guard let value = value else { return nil } + if value == "true" { + self = true + } else if value == "false" { + self = false + } else { + return nil + } + } + + init(safeFalse value: String?) { + guard let value = value else { self = false; return } + guard value == "true" else { self = false; return } + self = true + } + + init(safeTrue value: String?) { + guard let value = value else { self = true; return } + guard value == "false" else { self = true; return } + self = false + } +} + +public extension URL { + init?(safe string: String?) { + guard let string = string else { return nil } + self.init(string: string) + } +} diff --git a/SwiftAdditions/Classes/Extensions/String.swift b/SwiftAdditions/Classes/Extensions/String.swift new file mode 100644 index 0000000..9f53ce0 --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/String.swift @@ -0,0 +1,91 @@ +import Foundation + +public extension String { + + var localised: String { + return NSLocalizedString(self, comment: "") + } + + func localized(params: [String]) -> String{ + return String(format: NSLocalizedString(self, comment: ""), params) + } + + func localised(param: String) -> String { + return String(format: NSLocalizedString(self, comment: ""), param) + } + + func localised(param1: String, param2: String) -> String { + return String(format: NSLocalizedString(self, comment: ""), param1, param2) + } + + func localised(param1: String, param2: String, param3: String) -> String { + return String(format: NSLocalizedString(self, comment: ""), param1, param2, param3) + } + + func localised(param1: String, param2: String, param3: String, param4: String) -> String { + return String(format: NSLocalizedString(self, comment: ""), param1, param2, param3, param4) + } + + subscript (bounds: CountableClosedRange) -> String { + let start = index(startIndex, offsetBy: bounds.lowerBound) + let end = index(startIndex, offsetBy: bounds.upperBound) + return String(self[start...end]) + } + + subscript (bounds: CountableRange) -> String { + let start = index(startIndex, offsetBy: bounds.lowerBound) + let end = index(startIndex, offsetBy: bounds.upperBound) + return String(self[start.. NSRange? { + if let range = self.range(of: string) { + return NSRange(range, in: self) + } + return nil + } + + func getURLRange() -> [NSTextCheckingResult] { + + let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) + let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) + + return matches + } + + + func extractURL(from location: Int) -> URL? { + let matches = self.getURLRange() + for match in matches { + guard NSLocationInRange(location, match.range), + let text = self as NSString?, + let urlToOpen = URL(string: (text.substring(with: match.range))) else { continue } + + var link = urlToOpen.absoluteString + if link.lowercased().hasPrefix("http://") == false && link.lowercased().hasPrefix("https://") == false { + link = "http://\(link)" + } + guard let url = URL(string: link) else { continue } + return url + + } + return nil + } + + var length: Int { + return count + } + + subscript (i: Int) -> String { + return self[i ..< i + 1] + } + + func substring(fromIndex: Int) -> String { + return self[min(fromIndex, length) ..< length] + } + + func substring(toIndex: Int) -> String { + return self[0 ..< max(0, toIndex)] + } + +} diff --git a/SwiftAdditions/Classes/Extensions/URL.swift b/SwiftAdditions/Classes/Extensions/URL.swift new file mode 100644 index 0000000..a85a216 --- /dev/null +++ b/SwiftAdditions/Classes/Extensions/URL.swift @@ -0,0 +1,19 @@ +import Foundation + +public extension URL { + func stringRemoving(params: [String]) -> String? { + var components = URLComponents(url: self, resolvingAgainstBaseURL: false) + params.forEach { (param) in + components?.queryItems?.removeAll(where: { (item) -> Bool in + return item.name == param + }) + } + guard var reducedURL = components?.url?.absoluteString else { + return nil + } + if reducedURL.last == "?" { + reducedURL.removeLast() + } + return reducedURL + } +} diff --git a/SwiftAdditions/Classes/Startup/AppTask.swift b/SwiftAdditions/Classes/Startup/AppTask.swift new file mode 100644 index 0000000..f2e300f --- /dev/null +++ b/SwiftAdditions/Classes/Startup/AppTask.swift @@ -0,0 +1,32 @@ +import Foundation + +/// This task represents a long running action that can be queued +/// +/// **IMPORTANT!** +/// +/// Override `main` to start the operation, then set `state = .finished` +/// +///`AppTask` implementations can override `ApplicationLifecycleTask` functions such as +/// ``` +/// func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool +/// ``` +/// or +/// ``` +/// func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions +/// ``` +open class AppTask: AsyncOperation, ApplicationLifecycleTask { + public override init() { + super.init() + } + + open override func main() { + super.main() + + print("started \(self)") + } + + public func setFinished() { + state = .finished + print("finished \(self)") + } +} diff --git a/SwiftAdditions/Classes/Startup/AppTasks.swift b/SwiftAdditions/Classes/Startup/AppTasks.swift new file mode 100644 index 0000000..86b2101 --- /dev/null +++ b/SwiftAdditions/Classes/Startup/AppTasks.swift @@ -0,0 +1,180 @@ +import Foundation +import UIKit +import UserNotifications + +public protocol ApplicationLifecycleTask: UIWindowSceneDelegate, UIApplicationDelegate, UNUserNotificationCenterDelegate {} + + +/// +/// An instance of this protocol will provide a combination of sync / async operations that can be queued before the app's first functions start: +/// +/// See in the example app, and its output: +/// +/// ``` +/// class ExampleAppServices: ServiceProvider { +/// +/// lazy var someLongRunningTask = SomeLongRunningTask() +/// lazy var shortTask = SyncTask() +/// lazy var dependentTask = DependentTask() +/// lazy var permissionTask = PermissionTask() +/// lazy var uiTask = UITask() +/// +/// lazy var appTasks: [AppTask] = { +/// dependentTask.addDependency(someLongRunningTask) +/// uiTask.addDependency(permissionTask) +/// +/// return [ +/// someLongRunningTask, +/// shortTask, +/// dependentTask, +/// permissionTask, +/// uiTask, +/// ] +/// }() +/// +/// func modules() -> [Register] { +/// [ +/// Register(ReaderProtocol.self, { Reader() }) +/// ] +/// } +/// } +/// ``` +/// +/// Output: +/// ``` +/// finished +/// finished +/// finished +/// finished +/// finished +/// all tasks completed +/// scene(_:willConnectTo:options:) finished +/// ``` +/// +public protocol ServiceProvider { + func modules() -> [Register] + var appTasks: [AppTask] { get } +} + +/// +/// Can be tasked with taking care of a controlled startup process. +/// +/// Example usage: +/// ``` +/// private lazy var serviceProviders: [ServiceProvider] = [ +/// ExampleAppServices(), +/// AdditionsServices(), +/// ] +/// ``` +/// +/// used in first class getting called, usually SceneDelegate (AppDelegate if first is not implemented) +/// ``` +/// private lazy var tasks = AppTasks.build(serviceProviders: serviceProviders) { +/// print("all tasks completed") +/// } +/// ``` +/// +/// subsequent uses in other classes, usually AppDelegate: +/// ``` +/// private var tasks: AppTasks? { +/// AppTasks.shared +/// } +/// ``` +/// +/// interracting with the AppTasks should be done using app lifecycle events (AppDelegate / SceneDelegate): +/// ``` +/// func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { +/// tasks.forEach { +/// $0.scene?(scene, willConnectTo: session, options: connectionOptions) +/// } +/// } +/// ``` +/// +/// or +/// ``` +/// func applicationDidEnterBackground(_ application: UIApplication) { +/// tasks?.forEach { +/// $0.applicationDidEnterBackground?(application) +/// } +/// } +/// +/// ``` +open class AppTasks { + + public static var shared: AppTasks? + public static func build(serviceProviders: [ServiceProvider], finished: @escaping Action) -> AppTasks { + let tasks = AppTasks(serviceProviders, finished: finished) + shared = tasks + return tasks + } + + @Inject private var dispatch: Dispatching + private let queue = OperationQueue() + private var serviceProviders = [ServiceProvider]() + + private lazy var allTasks: [AppTask] = { + serviceProviders.compactMap { $0.appTasks }.reduce([AppTask]()) { (result, next) in + return result + next + } + }() + + private init(_ serviceProviders: [ServiceProvider], finished: @escaping Action) { + self.serviceProviders = serviceProviders + CoreServiceLocator.shared.addBuildTasks { + serviceProviders + } + + queue.addOperations(allTasks, waitUntilFinished: false) + queue.addBarrierBlock { + self.dispatch.dispatchMain { [weak self] in + finished() + self?.isReady = true + } + } + } + + private var isReady: Bool = false { + didSet { + guard isReady else { return } + + buffer.forEach { predicate in + switch predicate { + case .forPredicate(let body): + try? forEach(body) + case .allSatisfyPredicate(let body): + _ = try? allSatisfy(body) + } + } + buffer.removeAll() + } + } + + private enum BufferedPredicate { + case forPredicate( (ApplicationLifecycleTask) throws -> Void) + case allSatisfyPredicate((ApplicationLifecycleTask) throws -> Bool) + } + + private var buffer = [BufferedPredicate]() + + public func forEach(_ body: @escaping (ApplicationLifecycleTask) throws -> Void) rethrows { + guard isReady else { + buffer.append(.forPredicate(body)) + return + } + + try allTasks.forEach(body) + } + + @discardableResult + public func allSatisfy(_ predicate: @escaping (ApplicationLifecycleTask) throws -> Bool) rethrows -> Bool { + guard isReady else { + buffer.append(.allSatisfyPredicate(predicate)) + return true + } + return try allTasks.allSatisfy(predicate) + } + + public func reduce(_ initialResult: Result, _ nextPartialResult: (Result, ApplicationLifecycleTask) throws -> Result) rethrows -> Result { + try allTasks.reduce(initialResult, nextPartialResult) + } +} diff --git a/SwiftAdditions/Classes/Startup/NoOpAppTask.swift b/SwiftAdditions/Classes/Startup/NoOpAppTask.swift new file mode 100644 index 0000000..a3e1c41 --- /dev/null +++ b/SwiftAdditions/Classes/Startup/NoOpAppTask.swift @@ -0,0 +1,12 @@ +import Foundation + +/// +/// This task represents an async action that can be queued also, like `AppTask`. +/// +/// If there's nothing to do, don't override `main`. Otherwise inherit directly from `AppTask`. +open class NoOpAppTask: AppTask { + open override func main() { + super.main() + setFinished() + } +} diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj new file mode 120000 index 0000000..3c5a8e7 --- /dev/null +++ b/_Pods.xcodeproj @@ -0,0 +1 @@ +Example/Pods/Pods.xcodeproj \ No newline at end of file