PainlessInjection 1.8.0

PainlessInjection 1.8.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2020年3月
SPM支持 SPM

Yaroslav Zhurakovskiy 维护。



  • yaroslav-zhurakovskiy

Platforms License

Swift Package Manager Carthage compatible CocoaPods compatible

Build Status codecov

PainlessInjection

PainlessInjection 是一个轻量级的 Swift 依赖注入框架。

依赖注入(DI)是一种软件设计模式,通过实现控制反转(IoC)来解决依赖。在该模式中,PainlessInjection 帮助您的应用程序分割成松散耦合的组件,这使得开发、测试和维护更加容易。PainlessInjection 利用 Swift 的泛型类型系统和一等函数来简单流畅地定义应用程序的依赖。

安装

PainlessInjection 可通过 CarthageCocoaPodsSwift 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