Swinject
Swinject is a lightweight dependency injection framework for Swift.
Dependency injection (DI) is a software design pattern that implements Inversion of Control (IoC) for resolving dependencies. In the pattern, Swinject helps your app split into loosely-coupled components, which can be developed, tested and maintained more easily. Swinject is powered by the Swift generic type system and first class functions to define dependencies of your app simply and fluently.
Features
- Pure Swift Type Support
- Injection with Arguments
- Initializer/Property/Method Injections
- Initialization Callback
- Circular Dependency Injection
- Object Scopes as None (Transient), Graph, Container (Singleton) and Hierarchy
- Support of both Reference and Value Types
- Self-registration (Self-binding)
- Container Hierarchy
- Thread Safety
- Modular Components
Extensions
- SwinjectPropertyLoader: Loading property values from resources.
- SwinjectStoryboard: Automatic dependency injection via Storyboard.
- Swinject-CodeGen: Type-safe code generation of
Container
from a CSV/YAML file defining dependencies. - SwinjectAutoregistration: Automatic registration of services by leveraging the Swift Generics.
Requirements
- iOS 8.0+ / Mac OS X 10.10+ / watchOS 2.0+ / tvOS 9.0+
- Swift 2.2 or 2.3
- Xcode 7.0+
- Swift 3
- Xcode 8.0+
- Swift 3.2, 4.x
- Xcode 9.0+
- Carthage 0.18+ (if you use)
- CocoaPods 1.1.1+ (if you use)
Installation
Swinject is available through Carthage, CocoaPods, or Swift Package Manager.
Carthage
To install Swinject with Carthage, add the following line to your Cartfile
.
Swift 2.2 or 2.3
github "Swinject/Swinject" ~> 1.1.4
Swift 3.x or 4.x
github "Swinject/Swinject"
# Uncomment if you use SwinjectStoryboard
# github "Swinject/SwinjectStoryboard"
Then run carthage update --no-use-binaries
command or just carthage update
. For details of the installation and usage of Carthage, visit its project page.
CocoaPods
To install Swinject with CocoaPods, add the following lines to your Podfile
.
Swift 2.2 or 2.3
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0' # or platform :osx, '10.10' if your target is OS X.
use_frameworks!
pod 'Swinject', '~> 1.1.4'
Swift 3.x
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0' # or platform :osx, '10.10' if your target is OS X.
use_frameworks!
pod 'Swinject'
# Uncomment if you use SwinjectStoryboard
# pod 'SwinjectStoryboard'
Then run pod install
command. For details of the installation and usage of CocoaPods, visit its official website.
Swift Package Manager
in Package.swift
add the following:
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/Swinject/Swinject.git", from: "2.7.1")
],
targets: [
.target(
name: "MyProject",
dependencies: [..., "Swinject"]
)
...
]
Documentation
- Technical documents including patterns of dependency injection and examples.
- API reference
Basic Usage
First, register a service and component pair to a Container
, where the component is created by the registered closure as a factory. In this example, Cat
and PetOwner
are component classes implementing Animal
and Person
service protocols, respectively.
let container = Container()
container.register(Animal.self) { _ in Cat(name: "Mimi") }
container.register(Person.self) { r in
PetOwner(pet: r.resolve(Animal.self)!)
}
Then get an instance of a service from the container. The person is resolved to a pet owner, and playing with the cat named Mimi!
let person = container.resolve(Person.self)!
person.play() // prints "I'm playing with Mimi."
Where definitions of the protocols and classes are
protocol Animal {
var name: String? { get }
}
class Cat: Animal {
let name: String?
init(name: String?) {
self.name = name
}
}
and
protocol Person {
func play()
}
class PetOwner: Person {
let pet: Animal
init(pet: Animal) {
self.pet = pet
}
func play() {
let name = pet.name ?? "someone"
print("I'm playing with \(name).")
}
}
Notice that the pet
of PetOwner
is automatically set as the instance of Cat
when Person
is resolved to the instance of PetOwner
. If a container already set up is given, you do not have to care what are the actual types of the services and how they are created with their dependency.
Where to Register Services
Services must be registered to a container before they are used. The typical registration approach will differ depending upon whether you are using SwinjectStoryboard
or not.
The following view controller class is used in addition to the protocols and classes above in the examples below.
class PersonViewController: UIViewController {
var person: Person?
}
With SwinjectStoryboard
Import SwinjectStoryboard at the top of your swift source file if you use Swinject v2 in Swift 3.
// Only Swinject v2 in Swift 3.
import SwinjectStoryboard
Services should be registered in an extension of SwinjectStoryboard
if you use SwinjectStoryboard
. Refer to the project page of SwinjectStoryboard for further details.
extension SwinjectStoryboard {
@objc class func setup() {
defaultContainer.register(Animal.self) { _ in Cat(name: "Mimi") }
defaultContainer.register(Person.self) { r in
PetOwner(pet: r.resolve(Animal.self)!)
}
defaultContainer.register(PersonViewController.self) { r in
let controller = PersonViewController()
controller.person = r.resolve(Person.self)
return controller
}
}
}
Without SwinjectStoryboard
If you do not use SwinjectStoryboard
to instantiate view controllers, services should be registered to a container in your application's AppDelegate
. Registering before exiting application:didFinishLaunchingWithOptions:
will ensure that the services are setup appropriately before they are used.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let container: Container = {
let container = Container()
container.register(Animal.self) { _ in Cat(name: "Mimi") }
container.register(Person.self) { r in
PetOwner(pet: r.resolve(Animal.self)!)
}
container.register(PersonViewController.self) { r in
let controller = PersonViewController()
controller.person = r.resolve(Person.self)
return controller
}
return container
}()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
// Instantiate a window.
let window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
self.window = window
// Instantiate the root view controller with dependencies injected by the container.
window.rootViewController = container.resolve(PersonViewController.self)
return true
}
}
Notice that the example uses a convenience initializer taking a closure to register services to the new instance of Container
.
Play in Playground!
The project contains Sample-iOS.playground
to demonstrate the features of Swinject. Download or clone the project, run the playground, modify it, and play with it to learn Swinject.
To run the playground in the project, first build the project, then select Editor > Execute Playground
menu in Xcode.
Example Apps
- SwinjectSimpleExample demonstrates dependency injection and Swinject in a simple weather app that lists current weather information at some locations.
- SwinjectMVVMExample demonstrates dependency injection with Swift and reactive programming with ReactiveCocoa in MVVM architecture.
Blog Posts
The following blog posts introduce Swinject and the concept of dependency injection.
- Dependency Injection Framework for Swift - Introduction to Swinject
- Dependency Injection Framework for Swift - Simple Weather App Example with Swinject Part 1/2
- Dependency Injection Framework for Swift - Simple Weather App Example with Swinject Part 2/2
Contribution Guide
A guide to submit issues, to ask general questions, or to open pull requests is here.
Question?
- Slack feel free to discuss anything Swinject related.
- Stack Overflow we are trying to monitor questions tagged
swinject
Credits
The DI container features of Swinject are inspired by:
and highly inspired by:
- Funq - Daniel Cazzulino and the project team.
License
MIT license. See the LICENSE file for details.