如何避免我的自定义依赖项注入工具出现保留周期?
How to avoid a retain cycle on my custom dependency injection tool?
我创建了一个自定义 propertyWrapper
来在代码中注入我的依赖项,因此在测试代码时,我可以使用 WritableKeyPath
link 将模拟传递给对象内存。
这就是我在生产代码中使用它的方式。这非常方便,因为我不需要在初始化程序中传递对象。
@Injected(\.child) var child
这就是我在单元测试中使用它来代替 WritableKeyPath
通过模拟的方式。
let parentMock = ParentMock()
InjectedDependency[\.parent] = parentMock
问题是,在我尝试使用它的代码的某些部分,当 Child
class 需要访问时,似乎正在创建幽灵对象到Parent
class一个循环。当我在 Playground 中查找并使用它时,我可能注意到当 linked 彼此时创建了两个对象,并且在设置变量为 nil
.
如何更新我的 @propertyWrapper
或可以对此解决方案进行哪些改进以使其按预期工作?为什么创建两个对象而不是它们引用内存中的对象?
所以下面设置这个自定义依赖注入工具在代码中的使用。
我已经用 weak var parent: Parent?
实现了 classic 方法来释放内存中的对象,没有问题来展示我的预期。
protocol ParentProtocol {}
class Parent: ParentProtocol {
//var child: Child?
@Injected(\.child) var child
init() { print(" Allocating Parent in memory") }
deinit { print ("♻️ Deallocating Parent from memory") }
}
protocol ChildProtocol {}
class Child: ChildProtocol {
//weak var parent: Parent?
@Injected(\.parent) var parent
init() { print(" Allocating Child in memory") }
deinit { print("♻️ Deallocating Child from memory") }
}
var mary: Parent? = Parent()
var tom: Child? = Child()
mary?.child = tom!
tom?.parent = mary!
// When settings the Parent and Child to nil,
// both are expected to be deallocating.
mary = .none
tom = .none
这是使用自定义依赖注入解决方案时日志中的响应。
Allocating Parent in memory
Allocating Child in memory
Allocating Child in memory // Does not appear when using the weak reference.
♻️ Deallocating Child from memory
Allocating Parent in memory // Does not appear when using the weak reference.
♻️ Deallocating Parent from memory
这是我的自定义 PropertyWrapper
的实现,用于处理 Parent
和 Child
键之后的依赖项注入作为使用示例。
// The key protocol for the @propertyWrapper initialization.
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
// The main dependency injection custom tool.
@propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
// The custom tool to use in unit tests to implement the mock
// within the associated WritableKeyPath.
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// The Parent and Child keys to access the object in memory.
extension InjectedDependency {
var parent: ParentProtocol {
get { Self[ParentKey.self] }
set { Self[ParentKey.self] = newValue }
}
var child: ChildProtocol {
get { Self[ChildKey.self] }
set { Self[ChildKey.self] = newValue }
}
}
// The instantiation of the value linked to the key.
struct ParentKey: InjectedKeyProtocol {
static var currentValue: ParentProtocol = Parent()
}
struct ChildKey: InjectedKeyProtocol {
static var currentValue: ChildProtocol = Child()
}
很多变化,所以只是比较 - 通常我们需要考虑引用计数,即。谁保留参考...因此它仅适用于 reference-types.
测试 Xcode 13.3 / iOS 15.4
protocol ParentProtocol: AnyObject {}
class Parent: ParentProtocol {
//var child: Child?
@Injected(\.child) var child
init() { print(" Allocating Parent in memory") }
deinit { print ("♻️ Deallocating Parent from memory") }
}
protocol ChildProtocol: AnyObject {}
class Child: ChildProtocol {
//weak var parent: Parent?
@Injected(\.parent) var parent
init() { print(" Allocating Child in memory") }
deinit { print("♻️ Deallocating Child from memory") }
}
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value? { get set }
}
// The main dependency injection custom tool.
@propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T?>
var wrappedValue: T? {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T?>) {
self.keyPath = keyPath
}
}
// The custom tool to use in unit tests to implement the mock
// within the associated WritableKeyPath.
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value? where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T?>) -> T? {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// The Parent and Child keys to access the object in memory.
extension InjectedDependency {
var parent: ParentProtocol? {
get { Self[ParentKey.self] }
set { Self[ParentKey.self] = newValue }
}
var child: ChildProtocol? {
get { Self[ChildKey.self] }
set { Self[ChildKey.self] = newValue }
}
}
// The instantiation of the value linked to the key.
struct ParentKey: InjectedKeyProtocol {
static weak var currentValue: ParentProtocol?
}
struct ChildKey: InjectedKeyProtocol {
static weak var currentValue: ChildProtocol?
}
测试代码的输出:
我创建了一个自定义 propertyWrapper
来在代码中注入我的依赖项,因此在测试代码时,我可以使用 WritableKeyPath
link 将模拟传递给对象内存。
这就是我在生产代码中使用它的方式。这非常方便,因为我不需要在初始化程序中传递对象。
@Injected(\.child) var child
这就是我在单元测试中使用它来代替 WritableKeyPath
通过模拟的方式。
let parentMock = ParentMock()
InjectedDependency[\.parent] = parentMock
问题是,在我尝试使用它的代码的某些部分,当 Child
class 需要访问时,似乎正在创建幽灵对象到Parent
class一个循环。当我在 Playground 中查找并使用它时,我可能注意到当 linked 彼此时创建了两个对象,并且在设置变量为 nil
.
如何更新我的 @propertyWrapper
或可以对此解决方案进行哪些改进以使其按预期工作?为什么创建两个对象而不是它们引用内存中的对象?
所以下面设置这个自定义依赖注入工具在代码中的使用。
我已经用 weak var parent: Parent?
实现了 classic 方法来释放内存中的对象,没有问题来展示我的预期。
protocol ParentProtocol {}
class Parent: ParentProtocol {
//var child: Child?
@Injected(\.child) var child
init() { print(" Allocating Parent in memory") }
deinit { print ("♻️ Deallocating Parent from memory") }
}
protocol ChildProtocol {}
class Child: ChildProtocol {
//weak var parent: Parent?
@Injected(\.parent) var parent
init() { print(" Allocating Child in memory") }
deinit { print("♻️ Deallocating Child from memory") }
}
var mary: Parent? = Parent()
var tom: Child? = Child()
mary?.child = tom!
tom?.parent = mary!
// When settings the Parent and Child to nil,
// both are expected to be deallocating.
mary = .none
tom = .none
这是使用自定义依赖注入解决方案时日志中的响应。
Allocating Parent in memory
Allocating Child in memory
Allocating Child in memory // Does not appear when using the weak reference.
♻️ Deallocating Child from memory
Allocating Parent in memory // Does not appear when using the weak reference.
♻️ Deallocating Parent from memory
这是我的自定义 PropertyWrapper
的实现,用于处理 Parent
和 Child
键之后的依赖项注入作为使用示例。
// The key protocol for the @propertyWrapper initialization.
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
// The main dependency injection custom tool.
@propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
// The custom tool to use in unit tests to implement the mock
// within the associated WritableKeyPath.
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// The Parent and Child keys to access the object in memory.
extension InjectedDependency {
var parent: ParentProtocol {
get { Self[ParentKey.self] }
set { Self[ParentKey.self] = newValue }
}
var child: ChildProtocol {
get { Self[ChildKey.self] }
set { Self[ChildKey.self] = newValue }
}
}
// The instantiation of the value linked to the key.
struct ParentKey: InjectedKeyProtocol {
static var currentValue: ParentProtocol = Parent()
}
struct ChildKey: InjectedKeyProtocol {
static var currentValue: ChildProtocol = Child()
}
很多变化,所以只是比较 - 通常我们需要考虑引用计数,即。谁保留参考...因此它仅适用于 reference-types.
测试 Xcode 13.3 / iOS 15.4
protocol ParentProtocol: AnyObject {}
class Parent: ParentProtocol {
//var child: Child?
@Injected(\.child) var child
init() { print(" Allocating Parent in memory") }
deinit { print ("♻️ Deallocating Parent from memory") }
}
protocol ChildProtocol: AnyObject {}
class Child: ChildProtocol {
//weak var parent: Parent?
@Injected(\.parent) var parent
init() { print(" Allocating Child in memory") }
deinit { print("♻️ Deallocating Child from memory") }
}
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value? { get set }
}
// The main dependency injection custom tool.
@propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T?>
var wrappedValue: T? {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T?>) {
self.keyPath = keyPath
}
}
// The custom tool to use in unit tests to implement the mock
// within the associated WritableKeyPath.
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value? where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T?>) -> T? {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// The Parent and Child keys to access the object in memory.
extension InjectedDependency {
var parent: ParentProtocol? {
get { Self[ParentKey.self] }
set { Self[ParentKey.self] = newValue }
}
var child: ChildProtocol? {
get { Self[ChildKey.self] }
set { Self[ChildKey.self] = newValue }
}
}
// The instantiation of the value linked to the key.
struct ParentKey: InjectedKeyProtocol {
static weak var currentValue: ParentProtocol?
}
struct ChildKey: InjectedKeyProtocol {
static weak var currentValue: ChildProtocol?
}
测试代码的输出: