The difference between strong, weak, and assign became real to me while debugging a download screen that would not disappear. The coordinator kept working correctly, but the screen stayed in memory after navigation. The cause was not ARC itself. I had accidentally made the coordinator own its delegate.
Changing the delegate to weak fixed the ownership cycle, but the useful lesson was broader: property attributes describe relationships, not decoration. The runnable project at the end recreates a download coordinator and verifies those relationships with nine deterministic checks.
Start With the Ownership Question
When I review an object property, I ask whether the containing object should keep the assigned object alive. If the answer is yes, strong is normally appropriate. If another component owns the object and this property only needs to observe it, weak is usually the safer relationship.
assign answers a different need. It stores a value directly and is the normal choice for scalar and C value types such as BOOL, NSInteger, and many structs. Using it for an Objective-C object creates an unsafe non-owning pointer that is not automatically cleared when the object goes away.
I avoid explaining these choices through guessed reference-count numbers. Temporary references, framework behavior, and optimizer decisions make those numbers a poor design tool. The stable question is which object owns which other object.
Strong Means the Property Is an Owner
The sample coordinator owns its download queue because the queue must remain available after the setup method returns:
@interface DownloadCoordinator : NSObject
@property (nonatomic, strong) NSMutableArray<NSString *> *queue;
@end
NSMutableArray *outsideQueue =
[NSMutableArray arrayWithObject:@"photo.jpg"];
coordinator.queue = outsideQueue;
outsideQueue = nil;
Clearing the local variable does not empty or destroy the coordinator’s queue. The strong property is still an owner. That is exactly what the feature needs: queued work belongs to the coordinator until it is processed or explicitly removed.
Strong ownership is not inherently a leak. A leak appears when ownership is unintended or forms a cycle that application behavior never breaks. Arrays, dictionaries, closures, timers, tasks, and observer registrations are frequent owners that deserve the same inspection as properties.
Weak Means Observe Without Extending Lifetime
The coordinator should notify a screen while that screen exists, but it should not decide how long the screen exists. The navigation stack or another UI owner has that responsibility. The delegate is therefore weak:
@property (nonatomic, weak)
id<DownloadCoordinatorDelegate> delegate;
While a local strong variable owns the screen, the coordinator can call it normally. When that owner is cleared, the screen deinitializes and the weak delegate becomes nil. Sending an Objective-C message through the now-nil property does nothing instead of targeting released storage.
This makes weak a good fit for many delegate and back-reference relationships. It is not a universal rule, though. Some APIs intentionally own a helper or callback target. The attribute should follow the documented lifetime relationship rather than a naming convention alone.
Weak Is Not a Promise That the Object Will Be There
A weak reference is safe to observe, but it does not guarantee availability. Code must handle the object disappearing between operations. In simple Objective-C code, reading the weak property into a strong local variable creates a stable owner for the work being performed:
id<DownloadCoordinatorDelegate> delegate = self.delegate;
if (delegate != nil) {
[delegate downloadDidFinish:fileName];
}
The local variable keeps that particular object alive for the duration of the call. This is clearer than repeatedly reading a weak property and assuming every read must produce the same result.
If an asynchronous task needs the screen to survive until completion, weak alone cannot provide that behavior. The task needs an intentional owner, a captured value with a documented lifetime, or a result path that works when the screen has already disappeared.
Assign Is for Values, Not Normal Object Relationships
The sample uses assign for configuration that is stored by value:
@property (nonatomic, assign) NSUInteger retryLimit;
@property (nonatomic, assign, getter=isWiFiOnly) BOOL WiFiOnly;
coordinator.retryLimit = 3;
coordinator.WiFiOnly = YES;
No separate Objective-C object needs to be retained for these settings. The values are copied into the instance’s storage. The same basic reasoning applies to many C numeric types, enums, and structures, although a property may use copy or a custom setter when its API requires different semantics.
An object property declared assign behaves like an unsafe non-owning reference. After the target deinitializes, the stored address is not changed to nil. Reading or messaging the released object can crash, or appear to work until the memory is reused. That intermittent behavior is why the bug can survive testing.
What the Unsafe Reference Check Actually Demonstrates
The command-line project includes an __unsafe_unretained variable to model the risk, but it deliberately never sends a message after deallocation. Doing that would turn an educational sample into undefined behavior.
__unsafe_unretained DownloadScreen *unsafeReference = nil;
DownloadScreen *temporary = [DownloadScreen new];
unsafeReference = temporary;
temporary = nil;
unsafeReference = nil;
The owner must manually clear the unsafe reference because the runtime does not provide zeroing behavior for it. Real application code rarely gains anything from accepting this burden. If the relationship must not own the target, I use weak unless a narrowly documented low-level requirement says otherwise.
This is also why I do not demonstrate an intentional EXC_BAD_ACCESS and call that proof. A crash is not a reliable test assertion, and its exact appearance can change with diagnostics, optimization, and memory reuse.
Copy Solves a Different Ownership Problem
Although the title focuses on three attributes, string properties often need one more choice. A copy property owns an independent value, protecting the receiver when a caller passes a mutable string:
NSMutableString *name =
[NSMutableString stringWithString:@"Summer Downloads"];
coordinator.displayName = name;
[name appendString:@" Changed"];
// displayName is still "Summer Downloads"
A strong string property would keep the same mutable object alive, allowing outside mutation to change a value the coordinator may have assumed was stable. For immutable value-like objects such as strings, blocks, and custom objects that support copying, the API contract determines whether strong or copy is appropriate.
The Review Checklist I Use in Projects
- Use
strongwhen this object should own the referenced object. - Use
weakfor a non-owning object relationship that may end first. - Expect every weak reference to become unavailable.
- Use
assignfor scalar and C value properties. - Do not message an unsafe object reference after its owner releases it.
- Use
copywhen the property promises an independent value. - Inspect collections and closures as ownership edges too.
- Verify lifetimes with deallocation probes and focused tests.
In the download scenario, the graph is simple once stated aloud: the coordinator owns its queue, the screen owns or otherwise retains the coordinator for the feature’s lifetime, and the coordinator merely observes the screen through its delegate. The weak edge prevents a cycle without making the callback unsafe.
That ownership sentence is often more useful than a diagram full of approximate retain counts. If the sentence is difficult to write, the code probably needs clearer responsibilities.
Download and Run the Complete Project
The complete example is a Foundation command-line program for macOS. It requires Xcode Command Line Tools and uses no network access, external dependencies, credentials, UIKit, or private runtime structures.
git clone https://github.com/2252408699/objc-property-ownership-demo.git
cd objc-property-ownership-demo
make run
A successful run prints nine PASS checks followed by All property ownership checks passed. Run make clean afterward if you want to remove the generated build directory.
Complete source code: objc-property-ownership-demo on GitHub
