How to Make a Weak Protocol Reference in Pure Swift (Without @objc)? A Complete Guide with Examples
\n\nImagine: you are writing an app in Swift, using the Delegate pattern, and suddenly — bam! — your objects are not being deallocated, memory is growing, and the app starts to lag. Sound familiar? Most likely, you have encountered a retain cycle (a cycle of strong references). A classic problem where the delegate holds a strong reference to the owner, and the owner holds a strong reference to the delegate. The solution that first comes to mind is to make the protocol reference weak (weak). But here Swift throws a surprise: you cannot simply declare weak var delegate: MyProtocol if the protocol is not marked with @objc. The compiler will give an error. Why? And most importantly — how to get around this?
In this article, we will break down how to make a weak protocol reference in pure Swift without @objc. You will learn why this is a problem in the first place, what solutions exist, and how to write clean, safe code without memory leaks. Let's go!
\n\nWhy Doesn't Weak Protocol Reference Work "Out of the Box"?
\n\nLet's recall how weak works in Swift. A weak reference (weak) can only exist for reference types (classes). Why? Because only class objects have memory management through ARC (Automatic Reference Counting). Structures (struct) and enumerations (enum) are value types, they are copied, and the concept of a "weak reference" does not make sense for them.
When you write:
\nweak var delegate: MyProtocol?\nThe compiler does not know whether MyProtocol will be implemented by a class, a structure, or an enumeration. What if it is a structure? Then weak cannot be applied. Hence the error: «'weak' may only be applied to class and class-bound protocol types».
In the past (in Objective-C), all protocols could by default only be implemented by classes, so weak worked. In pure Swift, protocols are universal — and this is great, but it adds complexity.
Solution #1: Class-Bound Protocol
\n\nThe simplest and most common way is to explicitly specify that the protocol can be implemented only by classes. For this, the keyword AnyObject (or the deprecated class) is used in the protocol declaration.
Example:
\nprotocol MyDelegate: AnyObject {\n func didSomething()\n}\n\nclass ViewController {\n weak var delegate: MyDelegate?\n\n func doSomething() {\n delegate?.didSomething()\n }\n}\n\nclass SomeClass: MyDelegate {\n func didSomething() {\n print("Delegate triggered!")\n }\n}\n\n// Usage\nlet vc = ViewController()\nlet some = SomeClass()\nvc.delegate = some\nvc.doSomething() // Prints: "Delegate triggered!"\n\nEverything works. MyDelegate can now only be implemented by a class (class), and weak is legal. This is the Apple-recommended approach for delegates.
Why is this good?
\n- \n
- Simplicity and readability. \n
- Full ARC support. \n
- No need for
@objc. \n
Cons?
\n- \n
- You lose the ability to implement the protocol with a structure. But for delegates, this is usually not needed. \n
Solution #2: Using a Generic Wrapper (For Exotic Cases)
\n\nWhat if you must allow the protocol to be implemented by both classes and structures, but still want a weak reference? This is rare, but possible. For example, you are writing a library where the user can pass both a class and a structure. Then a wrapper comes to the rescue.
\n\nThe idea: create a structure that stores a weak reference to an object implementing the protocol. For classes — we