"Nothing arrived. No activation, no message, no error. The delegate had simply stopped existing."
The Delegate Pattern Is Everywhere
Apple frameworks talk back to your code through delegates: an object you hand to a framework object, conforming to a protocol, whose methods the framework calls when something happens. NSApplicationDelegate, NSWindowDelegate, URLSessionDataDelegate, WCSessionDelegate, UNUserNotificationCenterDelegate — every track after this one leans on the pattern. In Swift the contract is a protocol, and a class-bound protocol (: AnyObject) is what lets the framework hold it weakly.
Weak, by Design
Swift memory is reference counted. If a session held its delegate strongly and the delegate held the session, neither would ever be freed. So most Cocoa delegates are declared weak — WatchConnectivity's header says it plainly: @property (nonatomic, weak, nullable) id <WCSessionDelegate> delegate. URLSession is the well-known exception: it retains its delegate until the session is invalidated. A weak reference does not keep its object alive. When the last strong reference goes away, the weak one becomes nil.
That is exactly how a family watch app went deaf. It installed a small forwarding delegate in front of the kit's own delegate, created inside an install() function and never stored. The function returned, the relay deallocated, WCSession.default.delegate read nil from then on, and nothing arrived — no activation callback, no transfers, no messages, and no error anywhere. The fix was one line: keep a strong reference for the life of the process. The test that pins it now asserts the session's delegate is the relay at launch.
One Delegate Slot, Not a List
A delegate property holds one object. Assign another and the first is silently unseated. In the same watch pairing, the app later set itself as the session delegate to receive a small settings payload — and the kit's delegate, the only place that acknowledged sent captures, stopped receiving anything. The watch never let go of payloads the phone already had. When a shared component owns a delegate, read what it exposes instead of taking the slot.
The Objective-C Runtime Still Builds Some of Your Objects
When a framework instantiates your class from a type — SwiftUI's @UIApplicationDelegateAdaptor building your app delegate is the family's example — it goes through the Objective-C -init slot. A subclass of an NSObject class that declares its own designated initializer needs an @objc init() to fill that slot. A plain Swift init() compiles, never fills it, and the app traps at launch.