diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d316aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Mac OS X +*.DS_Store + +# Xcode +.build +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +*.xcuserstate +project.xcworkspace/ +xcuserdata/ +Pods/*.xcodeproj/xcuserdata/ +xcuserdata/ +/.build +/Packages + +# Generated files +*.o +*.pyc + +# Docs +docs/docsets/XCoordinator.tgz +docs/undocumented.json + +# Backup files +*~.nib +\#*# +.#* + +.swiftpm diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0b43d04 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: swift +os: osx +osx_image: xcode10.1 +script: + - swift build + - swift test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6fc0704 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 QuickBird Studios + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..3eec1be --- /dev/null +++ b/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version:4.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + + +import PackageDescription + +let package = Package( + name: "XServiceLocator", + products: [ + .library( + name: "XServiceLocator", + targets: ["XServiceLocator"]), + ], + targets: [ + .target( + name: "XServiceLocator", + dependencies: []), + .testTarget( + name: "XServiceLocatorTests", + dependencies: ["XServiceLocator"]), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..f585063 --- /dev/null +++ b/README.md @@ -0,0 +1,255 @@ +# XServiceLocator + +*"Craving for proper Dependency Injection?"* + +

+ +

+ +# [![Build Status](https://travis-ci.com/quickbirdstudios/XServiceLocator.svg?branch=master)](https://travis-ci.com/quickbirdstudios/XServiceLocator) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/XServiceLocator.svg)](https://cocoapods.org/pods/XServiceLocator) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/XServiceLocator) [![Platform](https://img.shields.io/badge/platform-iOS-lightgrey.svg)](https://github.com/quickbirdstudios/XServiceLocator) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/quickbirdstudios/XServiceLocator/blob/master/LICENSE) + +XServiceLocator is a light-weight Swift library/framework for dynamically providing objects with the dependencies they need. The library is based on the Service Locator pattern. The idea is that objects get their dependencies from a certain store. XServiceLocator enables you to use [seamless dependency injection](https://quickbirdstudios.com/blog/swift-dependency-injection-service-locators/) throughout your iOS app without the need for any background magic. + +## πŸ”© Components + +### πŸ“¦ Container + +Stores the configuration on how to create instances of the registered types + +### πŸ› οΈ Resolver + +Resolves the actual implementation for a type, by creating an instance of a class, using the configuration of the Container + +### 🏭 ServiceFactory + +A generic factory solution for creating instances of the generic type + + +## πŸƒβ€β™€οΈ Getting started + +### πŸšΆβ€β™€οΈ Basic steps + +1. Register all of the types using any of the two `register` methods with the container. + + +```swift +let container = Container() + .register(Int.self, instance: 10) + .register(Double.self) { _ in 20 } +``` + +2. Wherever you need an instance of the type, you can access it using any of the two `resolve` methods of the `Resolver`. Let's first get the Resolver from the `Container` and then use the resolver to resolve the dependencies. + +```swift + let resolver = container.resolver + + let intValue = try! resolver.resolve(Int.self) + let doubleValue: Double = try! resolver.resolve() +``` + +### 🎲 Custom Objects + +You can also register custom types or instances for protocols, such as: + +```swift +// Setting up + +protocol Shape { + var name: String { get } +} + +class Circle: Shape { + let name = "Circle" + let radius: Double + + init(radius: Double) { + self.radius = radius + } + +} + +class Rectangle: Shape { + let name = "Rectangle" + let width: Double + let height: Double + + init(width: Double, height: Double) { + self.width = width + self.height = height + } +} + +struct Drawing { + let shape: Shape +} + +// Registering all the dependencies + +let container = Container() + .register(Shape.self, instance: Circle(radius: 10)) + .register(Circle.self) { _ in Circle(radius: 20) } + .register(Rectangle.self, instance: Rectangle(width: 30, height: 15)) + .register(Drawing.self) { _ in + let shape = Rectangle(width: 10, height: 5) + return Drawing(shape: shape) + } + + +// Resolving the dependencies + +let resolver = container.resolver + +let rectangle = try! resolver.resolve(Rectangle.self) +let shape = try! resolver.resolve(Shape.self) +let circle: Circle = try! resolver.resolve() +let drawing: Drawing = try! resolver.resolve() + +// Accessing values + +print(rectangle.name) // Rectangle +print(shape.name) // Circle +print(circle.name) // Circle +print(drawing.shape.name) // Rectangle + +print("\(rectangle.width), \(rectangle.height)") // 30.0, 15.0 +print((shape as! Circle).radius) // 10.0 +print(circle.radius) // 20.0 +``` + + +### 🎩 Using already registered instances for subsequent registrations + +```swift +// Registering all the dependencies +let container = Container() + .register(Double.self, instance: 30) + .register(Rectangle.self, instance: Rectangle(width: 10, height: 20)) + .register(Circle.self) { resolver in Circle(radius: try! resolver.resolve()) } + + +// Resolving the dependencies + +let resolver = container.resolver + +let circle: Circle = try! resolver.resolve() + + +// Accessing values + +circle.radius // 30.0 +``` + +## πŸ‘Ά Using (Child) Dependency Resolver + +A container can have another resolver as a dependency which can be used for resolution if the main resolver (container) fails to resolve the dependency. + +```swift +// Registering all the dependencies + +let dependency = Container() + .register(Double.self, instance: 100) + .register(Shape.self, instance: Rectangle(width: 10, height: 20)) + +let container = Container(dependency: dependency) + .register(Rectangle.self, instance: Rectangle(width: 15, height: 7.5)) + + +// Resolving the dependencies + +let resolver = container.resolver + +let shapeRectangle = try! resolver.resolve(Shape.self) as! Rectangle +let rectangle: Rectangle = try! resolver.resolve() +let doubleValue: Double = try! resolver.resolve() + + +// Accessing values + +print("\(shapeRectangle.width), \(shapeRectangle.height)") // 10.0, 20.0 +print("\(rectangle.width), \(rectangle.height)") // 15.0, 7.5 +print(doubleValue) // 100 +``` + +## πŸ’ͺ Array of Resolvers + +An array of resolvers also act as a resolver. As soon as any element of the array is able to resolve successfully, the object is returned. + +```swift +// Registering all the dependencies + +let container = Container() + .register(Int.self, instance: 10) + .register(Double.self, instance: 20) + +let container1 = Container() + .register(Float.self, instance: 30) + .register(Double.self, instance: 50) + +let arrayOfResolvers: Resolver = [ + container, + container1, +] + + +// Resolving the dependencies +let intValue = try! arrayOfResolvers.resolve(Int.self) // 10 +let floatValue = try! arrayOfResolvers.resolve(Float.self) // 30.0 +let doubleValue = try! arrayOfResolvers.resolve(Double.self) // 20.0 +``` + +## β€πŸ€·β€β™‚οΈ Why the ServiceLocator pattern? +1. Loose coupling between classes/objects +2. Provides better testability +3. Provides extensibility. New instances can be easily registered and implementations can be switched without changing a lot. + +## β€πŸ€” Why XServiceLocator? +1. **Plug and Play**. Integrate the library and you are ready to go. +2. **Supports Array of resolvers**. A combination of resolvers can be used for resolution of a type. +3. Supports registration and resolution of any type. +4. Developed and maintained by a group of developers who really care about the community and the quality of their solutions. +5. Everything is built using Swift only. No Objective-C legacy code. + +## πŸ›  Installation + +#### CocoaPods + +To integrate `XServiceLocator` into your Xcode project using CocoaPods, add this to your `Podfile`: + +```ruby +pod 'XServiceLocator', '~> 1.0' +``` + +#### Carthage + +To integrate `XServiceLocator` into your Xcode project using Carthage, add this to your `Cartfile`: + +``` +github "quickbirdstudios/XServiceLocator" ~> 1.0 +``` + +Then run `carthage update`. + +If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained [over at Carthage](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). + +#### Swift Package Manager + +See [this WWDC presentation](https://developer.apple.com/videos/play/wwdc2019/408/) about more information how to adopt Swift packages in your app. + +Specify `https://github.com/quickbirdstudios/XServiceLocator.git` as the `XServiceLocator` package link. + +#### Manually + +If you prefer not to use any of the dependency managers, you can integrate `XServiceLocator` into your iOS project manually, by downloading the source code and placing the files in your project directory. + +## πŸ‘€ Author +This framework is created with ❀️ by [QuickBird Studios](https://quickbirdstudios.com). + +## ❀️ Contributing + +Open an issue if you need help, if you found a bug, or if you want to discuss a feature request. + +Open a PR if you want to make changes to XServiceLocator. + +## πŸ“ƒ License + +XServiceLocator is released under an MIT license. See [License.md](https://github.com/quickbirdstudios/XServiceLocator/blob/master/LICENSE) for more information. diff --git a/Sources/XServiceLocator/Container.swift b/Sources/XServiceLocator/Container.swift new file mode 100644 index 0000000..2b60c91 --- /dev/null +++ b/Sources/XServiceLocator/Container.swift @@ -0,0 +1,101 @@ +// +// Container.swift +// XServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +/// Stores the configuration on how to create instances of the registered types +public class Container { + let dependency: Resolver? + let factories: [AnyServiceFactory] + + /// Initializes a new container with an optional child resolver + /// - Parameter dependency: a child resolver that is used when the dependency cannot be resolved by this container. + /// However, the container first tries to resolve the dependencies by itself + public init(dependency: Resolver? = nil) { + self.dependency = dependency + self.factories = [] + } + + /// Initializes a new container with an optional child resolver and an array of factories + /// - Parameter dependency: a child resolver that is used when the dependency cannot be resolved by this container. + /// However, the container first tries to resolve the dependencies by itself + /// - Parameter factories: an array of already registered types. + init(dependency: Resolver? = nil, factories: [AnyServiceFactory]) { + self.dependency = dependency + self.factories = factories + } + + // MARK: - Register + + /// Register the `instance` as a object of Type `ServiceType`. The same instance will be resolved on every resolve call. + /// - Parameter interface: The target type `ServiceType` to register the object as + /// - Parameter instance: The instance to register + public func register(_ interface: ServiceType.Type, instance: ServiceType) -> Container { + return register(interface) { _ in instance } + } + + /// Register the `factory` as a template to create object of Type `ServiceType`. The `factory` will be called on + /// every resolve call which means that a new instance will be created on every resolve call. + /// - Parameter interface: The target type `ServiceType` to register the object as + /// - Parameter factory: The factory/closure that is used to create the instance of type `ServiceType` + public func register(_ interface: ServiceType.Type, + _ factory: @escaping (Resolver) -> ServiceType) -> Container { + + let newFactory = BasicServiceFactory(interface, factory: { resolver in + factory(resolver) + }) + return .init(dependency: dependency, factories: factories + [AnyServiceFactory(newFactory)]) + } +} + +// MARK: - Resolver + +extension Container: Resolver { + /// returns a resolver that can be used to resolve the container objects + public var resolver: Resolver { return self as Resolver } + + /// Resolves to an instance of type `ServiceType` and throws if no instance/factory has already been registered. + /// In case the container is not able to resolve the instance type, it will try to resolve it using its dependency. + /// An `Error.factoryNotFound` will be thrown in case the resolution is not possible. + public func resolve(_ type: ServiceType.Type) throws -> ServiceType { + guard let factory = factories.first(where: { $0.supports(type) }) else { + guard let resolvedByDependency = try dependency?.resolve(type) else { + throw Container.unableToResolve(type) + } + + return resolvedByDependency + } + + return try factory.resolve(self) + } + + /// Resolves to an instance of type `ServiceType` and throws if no instance/factory has already been registered. + /// In case the container is not able to resolve the instance type, it will try to resolve it using its dependency. + /// An `Error.factoryNotFound` will be thrown in case the resolution is not possible. + public func resolve() throws -> ServiceType { + return try resolve(ServiceType.self) + } +} + +// MARK: - Error + +extension Container { + public static func unableToResolve(_ type: ServiceType.Type) -> Error { + return .factoryNotFound(service: type) + } + + public enum Error: Swift.Error, Equatable { + public static func == (lhs: Container.Error, rhs: Container.Error) -> Bool { + switch (lhs, rhs) { + case let (.factoryNotFound(lhsType), .factoryNotFound(rhsType)): + return lhsType == rhsType + } + } + + case factoryNotFound(service: Any.Type) + } +} diff --git a/Sources/XServiceLocator/Extensions/Array+Resolver.swift b/Sources/XServiceLocator/Extensions/Array+Resolver.swift new file mode 100644 index 0000000..808231c --- /dev/null +++ b/Sources/XServiceLocator/Extensions/Array+Resolver.swift @@ -0,0 +1,25 @@ +// +// Array+Resolver.swift +// XServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +extension Array: Resolver where Element == Resolver { + + /// tries to resolve to an instance of `ServiceType` and returns the instance as soon as the first element(resolver) + /// resolves it successfully. + public func resolve(_ type: ServiceType.Type) throws -> ServiceType { + guard !isEmpty else { throw EmptyResolverError() } + + return try firstToResolve({ try $0.resolve(type) }) + } + + public func resolve() throws -> ServiceType { + return try resolve(ServiceType.self) + } + + struct EmptyResolverError: Error { } +} diff --git a/Sources/XServiceLocator/Extensions/Collection+FirstToResolve.swift b/Sources/XServiceLocator/Extensions/Collection+FirstToResolve.swift new file mode 100644 index 0000000..f212c48 --- /dev/null +++ b/Sources/XServiceLocator/Extensions/Collection+FirstToResolve.swift @@ -0,0 +1,22 @@ +// +// Collection+FirstToResolve.swift +// XServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +// MARK: Internal API + +extension Collection where Index == Int { + + /// tries to resolve the `ServiceType` and returns the instance as soon as the first element resolves it successfully. + func firstToResolve(_ factory: (Element) throws -> ServiceType) throws -> ServiceType { + for element in self { + if let resolved = try? factory(element) { return resolved } + } + + throw Container.unableToResolve(ServiceType.self) + } +} diff --git a/Sources/XServiceLocator/Resolver.swift b/Sources/XServiceLocator/Resolver.swift new file mode 100644 index 0000000..8b6b0c4 --- /dev/null +++ b/Sources/XServiceLocator/Resolver.swift @@ -0,0 +1,17 @@ +// +// Resolver.swift +// X +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +public protocol Resolver { + /// Resolves to an instance of type `ServiceType` and throws if no instance/factory has already been registered. + func resolve(_ type: ServiceType.Type) throws -> ServiceType + + /// makes use of type inference to resolve to an instance of type `ServiceType` and throws if no instance/factory + /// has already been registered. Makes use o + func resolve() throws -> ServiceType +} diff --git a/Sources/XServiceLocator/ServiceFactory/AnyServiceFactory.swift b/Sources/XServiceLocator/ServiceFactory/AnyServiceFactory.swift new file mode 100644 index 0000000..9cf1219 --- /dev/null +++ b/Sources/XServiceLocator/ServiceFactory/AnyServiceFactory.swift @@ -0,0 +1,31 @@ +// +// AnyServiceFactory.swift +// XServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +/// Type-erased implementation of `ServiceFactory` +final class AnyServiceFactory { + private let _resolve: (Resolver) throws -> Any + private let _supports: (Any.Type) -> Bool + + init(_ serviceFactory: T) { + + self._resolve = { resolver -> Any in + try serviceFactory.resolve(resolver) + } + + self._supports = { $0 == T.ServiceType.self } + } + + func resolve(_ resolver: Resolver) throws -> ServiceType { + return try _resolve(resolver) as! ServiceType + } + + func supports(_ serviceType: ServiceType.Type) -> Bool { + return _supports(serviceType) + } +} diff --git a/Sources/XServiceLocator/ServiceFactory/BasicServiceFactory.swift b/Sources/XServiceLocator/ServiceFactory/BasicServiceFactory.swift new file mode 100644 index 0000000..905e38b --- /dev/null +++ b/Sources/XServiceLocator/ServiceFactory/BasicServiceFactory.swift @@ -0,0 +1,25 @@ +// +// BasicServiceFactory.swift +// XServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +/// Implementation of `ServiceFactory` that is used to generate instances of the generic type `ServiceType` +struct BasicServiceFactory: ServiceFactory { + private let factory: (Resolver) throws -> ServiceType + + /// initializes the factory with type `ServiceType` and a factory for creation of the type + /// - Parameter type: The type of the object that this factory supports/creates + /// - Parameter factory: The factory method that takes a resolver and should return an instance of the type + init(_ type: ServiceType.Type, factory: @escaping (Resolver) throws -> ServiceType) { + self.factory = factory + } + + /// tries resolving/generating the instance of generic type using the passed `Resolver` + func resolve(_ resolver: Resolver) throws -> ServiceType { + return try factory(resolver) + } +} diff --git a/Sources/XServiceLocator/ServiceFactory/ServiceFactory.swift b/Sources/XServiceLocator/ServiceFactory/ServiceFactory.swift new file mode 100644 index 0000000..f27d017 --- /dev/null +++ b/Sources/XServiceLocator/ServiceFactory/ServiceFactory.swift @@ -0,0 +1,23 @@ +// +// ServiceFactory.swift +// XServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +/// Generic factory solution that is used to resolve/create instances of type `ServiceType` +protocol ServiceFactory { + associatedtype ServiceType + + /// tries resolving/generating the instance of generic type using the passed `Resolver` + func resolve(_ resolver: Resolver) throws -> ServiceType +} + +extension ServiceFactory { + /// Checks if the service factory supports creation of the specific `ServiceType` + func supports(_ type: ServiceType.Type) -> Bool { + return type == ServiceType.self + } +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..db53c7f --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,7 @@ +import XCTest + +import XServiceLocatorTests + +var tests = [XCTestCaseEntry]() +tests += XServiceLocator.allTests() +XCTMain(tests) diff --git a/Tests/XServiceLocatorTests/XServiceLocatorTests.swift b/Tests/XServiceLocatorTests/XServiceLocatorTests.swift new file mode 100644 index 0000000..d9f646e --- /dev/null +++ b/Tests/XServiceLocatorTests/XServiceLocatorTests.swift @@ -0,0 +1,190 @@ +// +// XServiceLocatorTests.swift +// XServiceLocatorTests +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +@testable import XServiceLocator +import XCTest + +class XServiceLocatorTests: XCTestCase { + + func test_registerInstance_resolvesCorrectly() { + let resolver = Container() + .register(TestProtocol.self, instance: TestClass(id: 1, score: .random(in: 0...10))) + let instance: TestProtocol = try! resolver.resolve() + + XCTAssert(instance is TestClass) + } + + func test_registerFactory_resolvesCorrectly() { + let resolver = Container() + .register(TestProtocol.self) { _ in TestClass(id: 1, score: .random(in: 0...10)) } + let instance: TestProtocol = try! resolver.resolve() + + XCTAssert(instance is TestClass) + } + + func test_registerInstance_resolvesToSameInstanceEveryTime() { + let resolver = Container() + .register(TestClass.self, instance: TestClass(id: 1, score: 2)) + + let instance1: TestClass = try! resolver.resolve() + let instance2: TestClass = try! resolver.resolve() + + XCTAssertTrue(instance1 === instance2) + } + + func test_registerFactory_resolvesToNewInstanceEveryTime() { + let resolver = Container() + .register(TestClass.self) { _ in TestClass(id: 5, score: 10) } + + let instance1: TestClass = try! resolver.resolve() + let instance2: TestClass = try! resolver.resolve() + + XCTAssertFalse(instance1 === instance2) + } + + func test_registerProtocolAndClass_resolvesCorrectly() { + let resolver = Container() + .register(TestProtocol.self, instance: TestClass(id: 1, score: 10)) + .register(TestClass.self, instance: TestClass(id: 2, score: 20)) + + let resolvedProtocol: TestProtocol = try! resolver.resolve() + let resolvedInstance: TestClass = try! resolver.resolve() + + XCTAssert(resolvedProtocol is TestClass) + + XCTAssertEqual(resolvedProtocol.id, 1) + XCTAssertEqual((resolvedProtocol as! TestClass).score, 10) + + XCTAssertEqual(resolvedInstance.id, 2) + XCTAssertEqual(resolvedInstance.score, 20) + } + + func test_registerSingleInstance_resolvesCorrectly() { + let expectedInstance = TestClass(id: 1, score: 10) + let resolver = Container().register(TestProtocol.self, instance: expectedInstance) + let instance: TestProtocol = try! resolver.resolve() + + guard let castedInstance = instance as? TestClass else { + XCTFail("Expected Instance to be of type `\(String(describing: TestClass.self))`") + return + } + + XCTAssert(castedInstance === expectedInstance, + "Expected resolved instance and original instance to be the exact same object") + } + + func test_nonregisteredInstance_factoryNotFound() throws { + let resolver = Container().register(TestProtocol.self, instance: TestStruct(id: 1)) + + XCTAssertThrowsError(try resolver.resolve(DummyProtocol.self)) { error in + XCTAssertEqual(error as? Container.Error, Container.Error.factoryNotFound(service: DummyProtocol.self)) + } + } + + func test_registerTwoImplementations_lastOneOverridesPreviousOne() { + let first = TestStruct(id: 1) + let second = TestStruct(id: 2) + + let resolver = Container().register(TestProtocol.self, instance: first) + .register(TestProtocol.self, instance: second) + + let resolved: TestProtocol = try! resolver.resolve() + + XCTAssertEqual(resolved.id, first.id) + XCTAssertNotEqual(resolved.id, second.id) + } + + func test_alreadyRegisteredDependencies_canBeResolvedForSubsequenetRegistrations() { + let container = Container() + .register(Int.self, instance: 100) + .register(TestProtocol.self) { resolver in TestStruct(id: try! resolver.resolve(Int.self)) } + + let resolvedInt = try! container.resolve(Int.self) + let resolvedObject = try! container.resolve(TestProtocol.self) + + XCTAssertEqual(resolvedInt, resolvedObject.id) + } + + func test_registerInDependency_canBeResolvedByParent() { + let expectedId = 100 + let first = Container().register(Int.self, instance: expectedId) + let second = Container(dependency: first) + .register(TestProtocol.self) { resolver in TestStruct(id: try! resolver.resolve(Int.self)) } + + let resolvedIntByFirst = try! first.resolve(Int.self) + let resolvedIntBySecond = try! second.resolve(Int.self) + + XCTAssertEqual(resolvedIntByFirst, resolvedIntBySecond) + + let resolved = try! second.resolve(TestProtocol.self) + XCTAssertEqual(resolved.id, expectedId) + } + + func test_registerInDependency_isOverridenByParentWhenAccessViaParent() { + let first = Container().register(Int.self, instance: 100) + let second = Container(dependency: first) + .register(TestProtocol.self) { resolver in TestStruct(id: 150) } + .register(Int.self, instance: 200) + + let resolvedIntByFirst = try! first.resolve(Int.self) + let resolvedIntBySecond = try! second.resolve(Int.self) + + XCTAssertNotEqual(resolvedIntByFirst, resolvedIntBySecond) + XCTAssertEqual(resolvedIntByFirst, 100) + XCTAssertEqual(resolvedIntBySecond, 200) + } + + func test_ArrayOfResolvers_resolvesAll() { + let resolver: Resolver = [ + Container().register(Int.self, instance: 100), + Container().register(Float.self, instance: 200), + Container().register(Double.self, instance: 300), + ] + + XCTAssertEqual(try! resolver.resolve(Int.self), 100) + XCTAssertEqual(try! resolver.resolve(Float.self), 200) + XCTAssertEqual(try! resolver.resolve(Double.self), 300) + } + + func test_arrayOfResolvers_resolvesTheFirstMatchInTheArray() { + let first = Container().register(Int.self, instance: 100) + let second = Container().register(Int.self, instance: 200) + let third = Container().register(Int.self, instance: 300) + + let resolver1: Resolver = [first, second] + let resolver2: Resolver = [first, third] + let resolver3: Resolver = [first, second, third] + + XCTAssertEqual(try! resolver1.resolve(Int.self), 100) + XCTAssertEqual(try! resolver2.resolve(Int.self), 100) + XCTAssertEqual(try! resolver3.resolve(Int.self), 100) + + let resolver4: Resolver = [second, third] + XCTAssertEqual(try! resolver4.resolve(Int.self), 200) + + let resolver5: Resolver = [first, second, third].reversed() + XCTAssertEqual(try! resolver5.resolve(Int.self), 300) + } + + func test_emptyArrayOfResolvers_emptyResolverError() { + let resolver: Resolver = [] + + XCTAssertThrowsError(try resolver.resolve(Int.self)) { error in + XCTAssert(error is Array.EmptyResolverError) + } + } + + func test_arrayOfResolvers_factoryNotFound() { + let resolver: Resolver = [ + Container().register(Double.self, instance: 100) + ] + + XCTAssertThrowsError(try resolver.resolve(Int.self)) { error in + XCTAssertEqual(error as? Container.Error, Container.Error.factoryNotFound(service: Int.self)) + } + } +} diff --git a/Tests/XServiceLocatorTests/utils/DummyProtocol.swift b/Tests/XServiceLocatorTests/utils/DummyProtocol.swift new file mode 100644 index 0000000..b611857 --- /dev/null +++ b/Tests/XServiceLocatorTests/utils/DummyProtocol.swift @@ -0,0 +1,11 @@ +// +// DummyProtocol.swift +// XServiceLocatorTests +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +protocol DummyProtocol { +} diff --git a/Tests/XServiceLocatorTests/utils/TestClass.swift b/Tests/XServiceLocatorTests/utils/TestClass.swift new file mode 100644 index 0000000..1f63212 --- /dev/null +++ b/Tests/XServiceLocatorTests/utils/TestClass.swift @@ -0,0 +1,18 @@ +// +// TestClass.swift +// XServiceLocatorTests +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +class TestClass: TestProtocol { + let id: Int + let score: Double + + init(id: Int, score: Double) { + self.id = id + self.score = score + } +} diff --git a/Tests/XServiceLocatorTests/utils/TestProtocol.swift b/Tests/XServiceLocatorTests/utils/TestProtocol.swift new file mode 100644 index 0000000..90ef3f8 --- /dev/null +++ b/Tests/XServiceLocatorTests/utils/TestProtocol.swift @@ -0,0 +1,12 @@ +// +// TestProtocol.swift +// XServiceLocatorTests +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +public protocol TestProtocol { + var id: Int { get } +} diff --git a/Tests/XServiceLocatorTests/utils/TestStruct.swift b/Tests/XServiceLocatorTests/utils/TestStruct.swift new file mode 100644 index 0000000..0e1a9c9 --- /dev/null +++ b/Tests/XServiceLocatorTests/utils/TestStruct.swift @@ -0,0 +1,12 @@ +// +// TestStruct.swift +// XServiceLocatorTests +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +import Foundation + +struct TestStruct: TestProtocol, Equatable { + let id: Int +} diff --git a/XServiceLocator.jpeg b/XServiceLocator.jpeg new file mode 100644 index 0000000..1d977ba Binary files /dev/null and b/XServiceLocator.jpeg differ diff --git a/XServiceLocator.podspec b/XServiceLocator.podspec new file mode 100644 index 0000000..bb64c31 --- /dev/null +++ b/XServiceLocator.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |spec| + spec.name = 'XServiceLocator' + spec.homepage = 'https://github.com/quickbirdstudios/XServiceLocator' + spec.version = '1.0.0' + spec.summary = 'XServiceLocator is a light-weight library for dynamically providing objects with the dependencies they need.' + spec.license = { :type => 'MIT' } + spec.author = { 'Stefan Kofler' => 'stefan.kofler@quickbirdstudios.com', 'Mathias Quintero' => 'mathias.quintero@quickbirdstudios.com', 'Ghulam Nasir' => 'ghulam.nasir@quickbirdstudios.com' } + spec.source = { :git => 'https://github.com/quickbirdstudios/xservicelocator.git', :tag => spec.version } + spec.source_files = 'Sources/XServiceLocator/**/*.swift' + spec.framework = 'Foundation' + spec.swift_version = '5.1' + spec.ios.deployment_target = '8.0' + spec.osx.deployment_target = '10.9' +end diff --git a/XServiceLocator.xcodeproj/Info.plist b/XServiceLocator.xcodeproj/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/XServiceLocator.xcodeproj/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/XServiceLocator.xcodeproj/project.pbxproj b/XServiceLocator.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a4b4863 --- /dev/null +++ b/XServiceLocator.xcodeproj/project.pbxproj @@ -0,0 +1,566 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 509A590724F56B5000AD4B81 /* XServiceLocator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 509A58F624F56B3D00AD4B81 /* XServiceLocator.framework */; }; + 509A591624F56C4F00AD4B81 /* XServiceLocator.h in Headers */ = {isa = PBXBuildFile; fileRef = 509A591424F56C4F00AD4B81 /* XServiceLocator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 509A591824F56CB200AD4B81 /* TestClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800924E2C2150019F25F /* TestClass.swift */; }; + 509A591924F56CB200AD4B81 /* TestStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800A24E2C2150019F25F /* TestStruct.swift */; }; + 509A591A24F56CB200AD4B81 /* TestProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800B24E2C2150019F25F /* TestProtocol.swift */; }; + 509A591B24F56CB200AD4B81 /* DummyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800C24E2C2150019F25F /* DummyProtocol.swift */; }; + 509A591C24F56CB200AD4B81 /* XServiceLocatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800D24E2C2150019F25F /* XServiceLocatorTests.swift */; }; + 50F284CA24F5B36D00F3B5EE /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED7FFB24E2C2150019F25F /* Resolver.swift */; }; + 50F284CB24F5B36D00F3B5EE /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED7FFC24E2C2150019F25F /* Container.swift */; }; + 50F284CC24F5B36D00F3B5EE /* Collection+FirstToResolve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED7FFE24E2C2150019F25F /* Collection+FirstToResolve.swift */; }; + 50F284CD24F5B36D00F3B5EE /* Array+Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED7FFF24E2C2150019F25F /* Array+Resolver.swift */; }; + 50F284CE24F5B36D00F3B5EE /* AnyServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800124E2C2150019F25F /* AnyServiceFactory.swift */; }; + 50F284CF24F5B36D00F3B5EE /* ServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800224E2C2150019F25F /* ServiceFactory.swift */; }; + 50F284D024F5B36D00F3B5EE /* BasicServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED800324E2C2150019F25F /* BasicServiceFactory.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 509A590824F56B5000AD4B81 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0EADF79A21B1689F00542140 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 509A58F524F56B3D00AD4B81; + remoteInfo = XServiceLocator; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 509A58F624F56B3D00AD4B81 /* XServiceLocator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XServiceLocator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 509A591424F56C4F00AD4B81 /* XServiceLocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XServiceLocator.h; sourceTree = ""; }; + 509A591524F56C4F00AD4B81 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 509A591D24F56D5700AD4B81 /* XServiceLocatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XServiceLocatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 50ED7FFB24E2C2150019F25F /* Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = ""; }; + 50ED7FFC24E2C2150019F25F /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + 50ED7FFE24E2C2150019F25F /* Collection+FirstToResolve.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+FirstToResolve.swift"; sourceTree = ""; }; + 50ED7FFF24E2C2150019F25F /* Array+Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Resolver.swift"; sourceTree = ""; }; + 50ED800124E2C2150019F25F /* AnyServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyServiceFactory.swift; sourceTree = ""; }; + 50ED800224E2C2150019F25F /* ServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceFactory.swift; sourceTree = ""; }; + 50ED800324E2C2150019F25F /* BasicServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicServiceFactory.swift; sourceTree = ""; }; + 50ED800424E2C2150019F25F /* ServiceLocator.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ServiceLocator.podspec; sourceTree = ""; }; + 50ED800624E2C2150019F25F /* LinuxMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinuxMain.swift; sourceTree = ""; }; + 50ED800924E2C2150019F25F /* TestClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestClass.swift; sourceTree = ""; }; + 50ED800A24E2C2150019F25F /* TestStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestStruct.swift; sourceTree = ""; }; + 50ED800B24E2C2150019F25F /* TestProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestProtocol.swift; sourceTree = ""; }; + 50ED800C24E2C2150019F25F /* DummyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyProtocol.swift; sourceTree = ""; }; + 50ED800D24E2C2150019F25F /* XServiceLocatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XServiceLocatorTests.swift; sourceTree = ""; }; + 50ED800E24E2C2150019F25F /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + 50ED800F24E2C2150019F25F /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 50ED801024E2C2150019F25F /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 509A58F324F56B3D00AD4B81 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 509A58FF24F56B5000AD4B81 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 509A590724F56B5000AD4B81 /* XServiceLocator.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0EADF79921B1689F00542140 = { + isa = PBXGroup; + children = ( + 50ED7FF924E2C2150019F25F /* Sources */, + 50ED800524E2C2150019F25F /* Tests */, + 50ED801024E2C2150019F25F /* LICENSE */, + 50ED800F24E2C2150019F25F /* README.md */, + 50ED800E24E2C2150019F25F /* Package.swift */, + 50ED800424E2C2150019F25F /* ServiceLocator.podspec */, + 509A591324F56C4F00AD4B81 /* XServiceLocator */, + 50ED804B24E2C3620019F25F /* Products */, + 509A58F624F56B3D00AD4B81 /* XServiceLocator.framework */, + 509A591D24F56D5700AD4B81 /* XServiceLocatorTests.xctest */, + ); + sourceTree = ""; + }; + 509A591324F56C4F00AD4B81 /* XServiceLocator */ = { + isa = PBXGroup; + children = ( + 509A591424F56C4F00AD4B81 /* XServiceLocator.h */, + 509A591524F56C4F00AD4B81 /* Info.plist */, + ); + path = XServiceLocator; + sourceTree = ""; + }; + 50ED7FF924E2C2150019F25F /* Sources */ = { + isa = PBXGroup; + children = ( + 50ED7FFA24E2C2150019F25F /* XServiceLocator */, + ); + path = Sources; + sourceTree = ""; + }; + 50ED7FFA24E2C2150019F25F /* XServiceLocator */ = { + isa = PBXGroup; + children = ( + 50ED7FFB24E2C2150019F25F /* Resolver.swift */, + 50ED7FFC24E2C2150019F25F /* Container.swift */, + 50ED7FFD24E2C2150019F25F /* Extensions */, + 50ED800024E2C2150019F25F /* ServiceFactory */, + ); + path = XServiceLocator; + sourceTree = ""; + }; + 50ED7FFD24E2C2150019F25F /* Extensions */ = { + isa = PBXGroup; + children = ( + 50ED7FFE24E2C2150019F25F /* Collection+FirstToResolve.swift */, + 50ED7FFF24E2C2150019F25F /* Array+Resolver.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 50ED800024E2C2150019F25F /* ServiceFactory */ = { + isa = PBXGroup; + children = ( + 50ED800124E2C2150019F25F /* AnyServiceFactory.swift */, + 50ED800224E2C2150019F25F /* ServiceFactory.swift */, + 50ED800324E2C2150019F25F /* BasicServiceFactory.swift */, + ); + path = ServiceFactory; + sourceTree = ""; + }; + 50ED800524E2C2150019F25F /* Tests */ = { + isa = PBXGroup; + children = ( + 50ED800624E2C2150019F25F /* LinuxMain.swift */, + 50ED800724E2C2150019F25F /* XServiceLocatorTests */, + ); + path = Tests; + sourceTree = ""; + }; + 50ED800724E2C2150019F25F /* XServiceLocatorTests */ = { + isa = PBXGroup; + children = ( + 50ED800824E2C2150019F25F /* utils */, + 50ED800D24E2C2150019F25F /* XServiceLocatorTests.swift */, + ); + path = XServiceLocatorTests; + sourceTree = ""; + }; + 50ED800824E2C2150019F25F /* utils */ = { + isa = PBXGroup; + children = ( + 50ED800924E2C2150019F25F /* TestClass.swift */, + 50ED800A24E2C2150019F25F /* TestStruct.swift */, + 50ED800B24E2C2150019F25F /* TestProtocol.swift */, + 50ED800C24E2C2150019F25F /* DummyProtocol.swift */, + ); + path = utils; + sourceTree = ""; + }; + 50ED804B24E2C3620019F25F /* Products */ = { + isa = PBXGroup; + children = ( + ); + path = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 509A58F124F56B3D00AD4B81 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 509A591624F56C4F00AD4B81 /* XServiceLocator.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 509A58F524F56B3D00AD4B81 /* XServiceLocator */ = { + isa = PBXNativeTarget; + buildConfigurationList = 509A58FB24F56B3D00AD4B81 /* Build configuration list for PBXNativeTarget "XServiceLocator" */; + buildPhases = ( + 509A58F124F56B3D00AD4B81 /* Headers */, + 509A58F224F56B3D00AD4B81 /* Sources */, + 509A58F324F56B3D00AD4B81 /* Frameworks */, + 509A58F424F56B3D00AD4B81 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = XServiceLocator; + productName = XServiceLocator; + productReference = 509A58F624F56B3D00AD4B81 /* XServiceLocator.framework */; + productType = "com.apple.product-type.framework"; + }; + 509A590124F56B5000AD4B81 /* XServiceLocatorTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 509A590A24F56B5000AD4B81 /* Build configuration list for PBXNativeTarget "XServiceLocatorTests" */; + buildPhases = ( + 509A58FE24F56B5000AD4B81 /* Sources */, + 509A58FF24F56B5000AD4B81 /* Frameworks */, + 509A590024F56B5000AD4B81 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 509A590924F56B5000AD4B81 /* PBXTargetDependency */, + ); + name = XServiceLocatorTests; + productName = XServiceLocatorTests; + productReference = 509A591D24F56D5700AD4B81 /* XServiceLocatorTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0EADF79A21B1689F00542140 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1160; + LastUpgradeCheck = 1000; + ORGANIZATIONNAME = "QuickBird Studios GmbH"; + TargetAttributes = { + 509A58F524F56B3D00AD4B81 = { + CreatedOnToolsVersion = 11.6; + }; + 509A590124F56B5000AD4B81 = { + CreatedOnToolsVersion = 11.6; + }; + }; + }; + buildConfigurationList = 0EADF79D21B1689F00542140 /* Build configuration list for PBXProject "XServiceLocator" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 0EADF79921B1689F00542140; + productRefGroup = 0EADF79921B1689F00542140; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 509A58F524F56B3D00AD4B81 /* XServiceLocator */, + 509A590124F56B5000AD4B81 /* XServiceLocatorTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 509A58F424F56B3D00AD4B81 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 509A590024F56B5000AD4B81 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 509A58F224F56B3D00AD4B81 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 50F284CA24F5B36D00F3B5EE /* Resolver.swift in Sources */, + 50F284CB24F5B36D00F3B5EE /* Container.swift in Sources */, + 50F284CC24F5B36D00F3B5EE /* Collection+FirstToResolve.swift in Sources */, + 50F284CD24F5B36D00F3B5EE /* Array+Resolver.swift in Sources */, + 50F284CE24F5B36D00F3B5EE /* AnyServiceFactory.swift in Sources */, + 50F284CF24F5B36D00F3B5EE /* ServiceFactory.swift in Sources */, + 50F284D024F5B36D00F3B5EE /* BasicServiceFactory.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 509A58FE24F56B5000AD4B81 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 509A591824F56CB200AD4B81 /* TestClass.swift in Sources */, + 509A591924F56CB200AD4B81 /* TestStruct.swift in Sources */, + 509A591A24F56CB200AD4B81 /* TestProtocol.swift in Sources */, + 509A591B24F56CB200AD4B81 /* DummyProtocol.swift in Sources */, + 509A591C24F56CB200AD4B81 /* XServiceLocatorTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 509A590924F56B5000AD4B81 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 509A58F524F56B3D00AD4B81 /* XServiceLocator */; + targetProxy = 509A590824F56B5000AD4B81 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 0EADF7B521B168A000542140 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 0EADF7B621B168A000542140 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 509A58FC24F56B3D00AD4B81 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 77E79NGPCV; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = XServiceLocator/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.quickbirdstudios.XServiceLocator; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 509A58FD24F56B3D00AD4B81 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 77E79NGPCV; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = XServiceLocator/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.quickbirdstudios.XServiceLocator; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 509A590B24F56B5000AD4B81 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 77E79NGPCV; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.quickbirdstudios.XServiceLocatorTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 509A590C24F56B5000AD4B81 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 77E79NGPCV; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.quickbirdstudios.XServiceLocatorTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0EADF79D21B1689F00542140 /* Build configuration list for PBXProject "XServiceLocator" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EADF7B521B168A000542140 /* Debug */, + 0EADF7B621B168A000542140 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 509A58FB24F56B3D00AD4B81 /* Build configuration list for PBXNativeTarget "XServiceLocator" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 509A58FC24F56B3D00AD4B81 /* Debug */, + 509A58FD24F56B3D00AD4B81 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 509A590A24F56B5000AD4B81 /* Build configuration list for PBXNativeTarget "XServiceLocatorTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 509A590B24F56B5000AD4B81 /* Debug */, + 509A590C24F56B5000AD4B81 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0EADF79A21B1689F00542140 /* Project object */; +} diff --git a/XServiceLocator.xcodeproj/xcshareddata/xcschemes/XServiceLocator.xcscheme b/XServiceLocator.xcodeproj/xcshareddata/xcschemes/XServiceLocator.xcscheme new file mode 100644 index 0000000..22944f7 --- /dev/null +++ b/XServiceLocator.xcodeproj/xcshareddata/xcschemes/XServiceLocator.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XServiceLocator.xcodeproj/xcshareddata/xcschemes/XServiceLocatorTests.xcscheme b/XServiceLocator.xcodeproj/xcshareddata/xcschemes/XServiceLocatorTests.xcscheme new file mode 100644 index 0000000..3995e58 --- /dev/null +++ b/XServiceLocator.xcodeproj/xcshareddata/xcschemes/XServiceLocatorTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/XServiceLocator/Info.plist b/XServiceLocator/Info.plist new file mode 100644 index 0000000..9bcb244 --- /dev/null +++ b/XServiceLocator/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/XServiceLocator/XServiceLocator.h b/XServiceLocator/XServiceLocator.h new file mode 100644 index 0000000..2fa0f58 --- /dev/null +++ b/XServiceLocator/XServiceLocator.h @@ -0,0 +1,18 @@ +// +// ServiceLocator.h +// ServiceLocator +// +// Copyright Β© 2020 QuickBird Studios GmbH. All rights reserved. +// + +#import + +//! Project version number for XServiceLocator. +FOUNDATION_EXPORT double XServiceLocatorVersionNumber; + +//! Project version string for XServiceLocator. +FOUNDATION_EXPORT const unsigned char XServiceLocatorVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +