Weak References in Swift Playground Don't Work as Expected

A puzzle: weak reference not working in Swift 4?

I believe this has something to do with how Playgrounds treat code. It doesn't have quite the same lifecycle and you can't expect variables to be deallocated once your code is done running.

If you make your objects be optional, everything deallocates as expected:

do {
let user1: User? = User(name: "John")
let phone1: Phone? = Phone(model: "iPhone7")
user1?.phone = phone1
phone1?.user = user1
}

User John is initialized
Phone iPhone7 is initialized
Phone iPhone7 is deallocated
User John is deallocated
Done

Strong Reference not turning into weak when deinitialize

Playgrounds can hold strong references to your data for the purpose of previews. To get a true depiction of the deinitialization of a piece of code, test it outside of a playground.

Furthermore, when a weak reference exists to an object, the deinitializer isn't actually called immediately when the last weak reference is released. Instead, the object is deinitialized the next time the weak reference is attempted to be accessed. See this blog post for more details.

Property is unable to set to nil via weak reference

Playgrounds are the work of the devil. Test in a real app project, not a playground, and you will see that this works as you expect.

Weak view reference won't get deallocated after temporarily added to view hierarchy

There's an autorelease attached to TestView when you add and remove it to the superview. If you make sure to drain the autorelease pool, then this test behaves as you expect:

autoreleasepool {
holderView.addSubview(element!)
element?.removeFromSuperview()
}

That said, you cannot rely on this behavior. There is no promise about when an object will be destroyed. You only know it will be after you release your last strong reference to it. There may be additional retains or autoreleases on the object. And there's no promise that deinit will ever be called, so definitely make sure not to put any mandatory logic there. It should only perform memory cleanup. (Both Mac and iOS, for slightly different reasons, never call deinit during program termination.)

In your failing test case,deinit is printed after the assertion fails. You can demonstrate this by placing breakpoints on the failing assertion and the print. This is because the autorelease pool is drained at the end of the test case.

The underlying lesson is that it is very common for balanced retain/autorelease calls to be be placed on an object that is passed to ObjC code (and UIKit is heavily ObjC). You should generally not expect UIKit objects to be destroyed before the end of the current event loop. Don't rely on that (it's not promised), but it's often true.

How to avoid a retain cycle on my custom dependency injection tool?

Many changes, so just compare - in general we need to think about reference counting, ie. who keeps references... and so it works only for reference-types.

Tested with 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?
}

Output for test code:

Sample Image

Complete test module in project is here



Related Topics



Leave a reply



Submit