PainlessInjection
PainlessInjection 是一个轻量级的 Swift 依赖注入框架。
依赖注入(DI)是一种软件设计模式,通过实现控制反转(IoC)来解决依赖。在该模式中,PainlessInjection 帮助您的应用程序分割成松散耦合的组件,这使得开发、测试和维护更加容易。PainlessInjection 利用 Swift 的泛型类型系统和一等函数来简单流畅地定义应用程序的依赖。
安装
PainlessInjection 可通过 Carthage、CocoaPods 或 Swift Package Manager 获取。
需求
- iOS 8.0+
- Swift 5+
- Carthage 0.18+(如果使用的话)
- CocoaPods 1.1.1+(如果使用的话)
Carthage
要使用Carthage安装PainlessInjection,请将以下行添加到您的Cartfile中。
github "yaroslav-zhurakovskiy/PainlessInjection"
CocoaPods
要使用CocoaPods安装PainlessInjection,请将以下行添加到您的Podfile中。
pod "PainlessInjection"
Swift Package Manager
您可以通过在Package.swift文件中添加适当的描述,使用Swift Package Manager来安装PainlessInjection。
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "YOUR_PROJECT_NAME",
dependencies: [
.package(url: "https://github.com/yaroslav-zhurakovskiy/PainlessInjection.git", from: "1.3.0"),
]
)
使用说明
简单依赖
import PainlessInjection
// Define Services
class ServiceModule: Module {
override func load() {
define(Service.self) { InMemmoryService() }
}
}
// Load Modules
Container.load()
// Instantiate Service
let service = Container.get(type: Service.self)
// Using service
print(type(of: service))
Swift 5.1 @propertyWrapper功能
如果您使用Swift 5.1,可以利用自动装配的简洁形式。只需将@Inject属性添加到类/结构的属性中,框架将为您解决一切。
// You service
enum AuthServiceError: Error {
case serverIsDown
case tooManyAttempts
}
protocol AuthService: class {
func login(username: String, password: String, completion: @escaping (Result<Bool, AuthServiceError>) -> Void)
}
// Your controller that uses AuthService
class LoginController: UIViewController {
// AuthService will be resolved automatically based on loaded modules
@Inject var service: AuthService
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var passwordLabel: UILabel!
@IBAction func login() {
service.login(
username: usernameLabel.text ?? "",
password: passwordLabel.text ?? ""
) { result in
// Implement the logic
// Redirect to home page
}
}
}
// Fake implementation that will enable you to test without the actual server
class FakeAuthService: AuthService {
func login(username: String, password: String, completion: @escaping (Result<Bool, AuthServiceError>) -> Void) {
guard username != "error" && password != "error" else {
completion(.failure(AuthServiceError.serverIsDown))
return
}
if username == "JohnDoe" && password == "132" {
completion(.success(true))
} else {
completion(.success(false))
}
}
}
// Module with fake services
class TestModule: Module {
func load() {
define(AuthService.self) { FakeAuthService() }
}
}
// Loading and testing
Container.load() // It will load TestModule
type(of: LoginController().service) == FakeAuthService.self // AuthService is automatically resolved to FakeAuthService
具有参数和子依赖的依赖项
// Define Services
class ServiceModule: Module {
override func load() {
define(UserService.self) { RestUserService() }
}
}
// Define Controllers
class ControllersModule {
override func load() {)
define(ResetPasswordController.self) { args in
ResetPasswordController(email: args.at(0), userService: self.resolve())
}
}
}
// Load Modules
Container.load()
// Instantiate ResetPasswordController
let controller = Container.get(type: ResetPasswordController.self, args: ["[email protected]"])
具有可选参数的依赖项
// Define Services
class ServiceModule: Module {
override func load() {
define(TaskService.self) { RestTaskService() }
}
}
// Define Controllers
class ControllersModule {
override func load() {)
define(EditTaskController.self) { args in
EditTaskController(task: args.optionalAt(0), service: self.resolve())
}
}
}
// Load Modules
Container.load()
// Passing nil
let controller2 = Container.get(type: EditTaskController.self, args: [nil as Any])
// Passing a task
let task = Task(name: "Code something")
let controller2 = Container.get(type: EditTaskController.self, args: [task])
使用单例作用域
你通常不希望每次都重新创建一个服务。你想要使用某种单例。为了实现这一点,你可以使用单例作用域。
import PainlessInjection
class ServiceModule: Module {
override func load() {
define(Service.self) { InMemmoryService() }.inSingletonScope()
}
}
// Use Service as a singletone
let service1 = Container.get(type: Service.self)
let service2 = Container.get(type: Service.self)
// service1 & service2 is the same object
service1 === service2
使用缓存作用域
如果你想缓存一个对象一段时间,你可以使用缓存作用域。
import PainlessInjection
class ServiceModule: Module {
override func load() {
// Service will be cached for 10 minutes.
// After that it will be recreated again.
define(Service.self) { InMemmoryService() }.inCacheScope(interval: 10 * 60)
}
}
// Resolve Service type
let service1 = Container.get(type: Service.self)
let service2 = Container.get(type: Service.self)
// service1 & service2 is the same object
service1 === service2
// Wait for 10 minutes and 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 10 * 60 + 1) {
let service3 = Container.get(type: Service.self)
// service3 is different from service1 & service2
service3 != service1 && service3 != service2
}
使用装饰器
你可以使用装饰器来为依赖创建过程添加额外的逻辑。例如,你可以在每次解决依赖时记录日志。
import PainlessInjection
class PrintableDependency: Dependency {
private let original: Dependency
init(original: Dependency) {
self.original = original
}
var type: Any.Type {
return original.type
}
func create(_ args: [Any]) -> Any {
print("\(type) was created with", args, ".")
return original.create(args)
}
}
class UserModule: Module {
override func load() {
// Every time you will try to resolve User dependency it will be printed.
define(User.self) { User() }.decorate({ PrintableDependency(original: $0) })
}
}
何时加载依赖
在AppDelegate类的application:didFinishLaunchingWithOptions方法中放置Container.load()。
import PainlessInjection
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Container.load()
print(Container.loadedModules) // prints a list of loaded Modules
return true
}
}
类型推断
let service: Service = Container.get()
let user: User = Container.get("John", "Doe")
let anonymousUser: User = Container.get(nil as Any, nil as Any)
为不同应用程序版本使用不同的依赖项
使用预处理器宏。
该模块仅加载于调试构建中。
import PainlessInjection
#if DEBUG
class DebugModule: Module {
override func load() {
define(Service.self) { InMemmoryService() }.inSingletonScope()
}
}
#endif
该模块仅加载于发布构建中。
import PainlessInjection
#if !DEBUG
class ReleaseModule: Module {
override func load() {
define(Service.self) { RestService() }.inSingletonScope()
}
}
#endif
使用加载谓词。
使用 "STORE_TARGET" 环境变量确定应用程序版本。
import PainlessInjection
import Foundation
class StorePredicate: LoadModulePredicate {
private let value: String
init(valueMatching value: String) {
self.value = value
}
func shouldLoadModule() -> Bool {
guard let value = ProcessInfo.processInfo.environment["TARGET_STORE"] else {
assertionFailure("No TARGET_STORE env var was found!")
}
return value == self.value
}
}
如果应用程序在加拿大商店运行,则加载此模块。
class CanadianStoreModule: Module {
override func load() {
define(Service.self) { CanadaRestService() }.inSingletonScope()
}
override loadingPredicate() {
return StorePredicate(valueMatching: "Canada")
}
}
如果应用程序在美国商店运行,则加载此模块。
class USAStoreModule: Module {
override func load() {
define(Service.self) { USARestService() }.inSingletonScope()
}
override loadingPredicate() {
return StorePredicate(valueMatching: "USA")
}
}
加载服务
Container.load()
let service = Container.get(type: Service.self)
print(type(of: service)) // Will print USAStoreModule or CanadaRestService