Swift
Protocol
Weak Reference
Swift Programming
Memory Management

How can I make a weak protocol reference in 'pure' Swift without objc

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

You can create weak protocol references in pure Swift, but only when the protocol is restricted to class types. The key is to make the protocol inherit from AnyObject, because ARC only manages reference types and weak only applies to objects.

Make the Protocol Class-Only

If a protocol can be adopted by a struct or enum, Swift has no object reference to weaken. That is why the first step is to add an AnyObject constraint.

swift
1protocol DownloadObserver: AnyObject {
2    func downloadDidFinish(fileName: String)
3}
4
5final class DownloadManager {
6    weak var observer: DownloadObserver?
7
8    func finishDownload() {
9        observer?.downloadDidFinish(fileName: "report.pdf")
10    }
11}
12
13final class ScreenController: DownloadObserver {
14    func downloadDidFinish(fileName: String) {
15        print("Finished:", fileName)
16    }
17}
18
19let manager = DownloadManager()
20let screen = ScreenController()
21manager.observer = screen
22manager.finishDownload()

This is the simplest and most common answer. In many designs, a plain weak var delegate: SomeProtocol? is enough, and you do not need wrappers or type erasure.

Store Multiple Weak Protocol References

The question becomes harder when you want a collection of delegates. An array keeps strong references to its elements, so storing protocol values directly will prevent deallocation. In that case, create a wrapper that owns only a weak reference.

swift
1protocol EventListener: AnyObject {
2    func handle(event: String)
3}
4
5final class WeakEventListener {
6    weak var value: (any EventListener)?
7
8    init(_ value: any EventListener) {
9        self.value = value
10    }
11}
12
13final class EventBus {
14    private var listeners: [WeakEventListener] = []
15
16    func addListener(_ listener: any EventListener) {
17        listeners.append(WeakEventListener(listener))
18        listeners.removeAll { $0.value == nil }
19    }
20
21    func post(_ event: String) {
22        listeners.removeAll { $0.value == nil }
23
24        for box in listeners {
25            box.value?.handle(event: event)
26        }
27    }
28}
29
30final class Logger: EventListener {
31    func handle(event: String) {
32        print("Log:", event)
33    }
34}
35
36let bus = EventBus()
37let logger = Logger()
38bus.addListener(logger)
39bus.post("user_signed_in")

This pattern is useful for multicast delegates, event hubs, and observer lists. The wrapper exists only to break strong ownership. The actual protocol type still defines the behavior.

When a Generic Weak Box Helps

If you need the same weak-storage pattern in many places, a generic box is a cleaner tool:

swift
1final class WeakBox<T: AnyObject> {
2    weak var value: T?
3
4    init(_ value: T) {
5        self.value = value
6    }
7}
8
9final class CacheOwner {
10    weak var delegate: AnyObject?
11}

For ordinary classes, WeakBox is straightforward. For protocols, dedicated wrappers are often easier to read because protocol existentials and generics can get awkward, especially when the compiler asks for any SomeProtocol.

The design rule is simple:

  • Use a plain weak property for one delegate.
  • Use a wrapper for arrays or dictionaries of delegates.
  • Keep the protocol class-only with AnyObject.

Common Pitfalls

The most common mistake is forgetting the class constraint. If the protocol does not inherit from AnyObject, Swift will reject weak var observer: MyProtocol? because the protocol might be implemented by a value type.

Another mistake is assuming a collection of protocol values is weak just because the property holding the collection is weak. It is not. The array itself may be weakly referenced, but the array still strongly retains its contents. That is why each element needs its own weak wrapper.

A subtler bug appears when the referenced object has no strong owner anywhere else. If the only reference is weak, the instance is deallocated immediately. That is expected ARC behavior, not a compiler bug. Make sure some other part of the program owns the delegate for as long as it should stay alive.

Finally, avoid reaching for @objc unless you need Objective-C runtime features such as optional protocol methods or NSHashTable. Pure Swift works well here as long as you model ownership correctly.

Summary

  • Weak protocol references are possible in pure Swift when the protocol inherits from AnyObject.
  • A single delegate usually needs only weak var delegate: SomeProtocol?.
  • Collections of delegates need per-item weak wrappers, because arrays store elements strongly.
  • 'weak only works with reference types managed by ARC, not with structs or enums.'
  • If an object disappears immediately, check whether any strong reference is keeping it alive.

Course illustration
Course illustration

All Rights Reserved.