A better way to handle weak properties and memory management

In this post we will have a look to alternative way how can we handle memory management without leaking implementation details in our components and using ‘weak’ properties all over our code base.

Usually when we are dealing with delegation we usually tend to use weak properties for achieving our goal. I believe that this habit comes from UIKit where we tend to break retain cycle in ViewController’s when we use delegation in ViewController’s since they are hold in memory by the system when used. So usually we start with something like this:

protocol MyDelegate: class {
  func myClassDidSomething()
}

class MyClass {
  weak var delegate: MyDelegate?
}

In general there is no big issue with this solution, but we can do it even better. In example above we can point out two issues. First is that we are leaking how our references are handled in ‘MyClass’ and we are forced into composition that expects weak reference. And second is since we predefined that we need ‘weak var’ we are forced to use ‘:class’ conformance in ‘MyDelegate’ otherwise we are not able to use ‘weak var’.

But what if someday later we deicide that our delegate will be ‘struct’. Or even better, what if we deicide that in one use case our delegate would be ‘struct’ (maybe a nice component that one of your colleague developed) and and in other it would be ‘class’. With this approach it’s not possible to use both components since we are restricted to use classes. We can ask our colleague to change his model from ‘struct’ to ‘class’ and probably make him unhappy since his needs to make changes because our design decision or we can do something else and we prepare our codebase for welcoming future changes with ease.

Solution

First we introduce new component ‘WeakRef’ which will help us capturing ‘weak’ references.

final class WeakRef<T: AnyObject> {
  weak var object: T?
  init(_ object: T) {
    self.object = object
  }
}

Next we can update our protocol definition and prepare ‘MyClass’ for refactoring (we can also ditch optional from delegate and make it as let as well).

protocol MyDelegate {
  func myClassDidSomething()
}

class MyClass {
  let delegate: MyDelegate
}

And then we update our composition using new ‘WeakRef’ component.

Using classes

static func buildMyClass() -> MyClass {
  let anyClassDelegate: MyDelegate = ... /* any class that conforms to MyDelegate */
  let myClass = MyClass(delegate: WeakRef(anyClassDelegate))
  return myClass
}

extension WeakRef: MyDelegate where T: MyDelegate {
	func myClassDidSomething() {
		object?.myClassDidSomething()
	}
}

Using structs

static func buildMyClass() -> MyClass {
  let anyStructDelegate: MyDelegate = ... /* any struct that conforms to MyDelegate */
  let myClass = MyClass(delegate: anyStructDelegate)
  return myClass
}

We can see how our codebase became more opened for changes and modifications since things only change in composition level and our ‘class’ and ‘protocol’ definition stays intact since we removed implementation details from components to composition level.

Thank you for reading!


Detect user’s country without accessing their location

/

Sometimes we are faced with challenge where we would like to improve user experience of our app based on where in the world or better said in which country ...
Read More

DDD – Aggregates part I.

/

Welcome to the fourth article about Domain Driven Design. In the previous article, we learned about one of the primary concepts in Domain Driven DesignEntities. This time, ...
Read More

Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Yannic
Yannic
1 year ago

Hey Marko, just read through the article. Is it possible that you forgot to declare an extension of the WeakRef type which conforms to the MyDelegate and forwards the command to the wrapped object? Because you can’t just inject the WeakRef into MyClass without it conforming to MyDelegate.