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
Hey Marko, just read through the article. Is it possible that you forgot to declare an extension of the
WeakRef
type which conforms to theMyDelegate
and forwards the command to the wrapped object? Because you can’t just inject theWeakRef
intoMyClass
without it conforming toMyDelegate
.Hi Yannic! Yes, you are right. I have skipped this step intentionally, but I have updated article now to make it clearer. Thank you for feedback!