Weak or Strong? A Practical Guide to Objective-C Ownership Under ARC
ARC handles retain/release for you, but ownership qualifiers are still your decision. A practical rule for __weak vs __strong, a safe delegate example, the weak-strong dance for blocks, and how to verify with Instruments.
07 Jun 2026, 07:51 UTC

Automatic Reference Counting removed the drudgery of manual retain and release, but it did not remove the one decision that still causes crashes and leaks in Objective-C codebases: which ownership qualifier belongs on a reference. Get __weak vs __strong wrong and you either leak entire object graphs through retain cycles or dereference a nil you expected to be alive. This post gives you a decision rule that covers the common cases, plus a worked delegate example you can adapt directly.
What ARC actually does for you
ARC is a compile-time feature, not a garbage collector. The compiler analyzes your code and inserts retain, release, and autorelease calls at the correct points. It has been the default since Xcode 5 and works on iOS 7 / macOS 10.7 and later. Because the insertion is deterministic, ARC eliminates most leaks and over-releases that plagued manual memory management.
What ARC cannot do is reason about object graphs. If object A holds a strong reference to B, and B holds a strong reference back to A, both keep each other alive forever. The compiler sees two perfectly valid strong references; it has no way to know the cycle is unintended. Breaking cycles is still your job, and the tool for it is the ownership qualifier.
The decision rule
Objective-C under ARC offers four qualifiers, but two of them cover nearly every real situation:
__strong(the default): the reference keeps the object alive. Use it for anything you own — child objects, model data, views a controller manages.__weak: the reference does not keep the object alive, and the runtime automatically sets it tonilwhen the object deallocates. Use it for back-references — delegates, data sources, parents in a tree, targets of observers.
The rule of thumb: ownership should form a tree, and every edge that points back up the tree must be __weak. If you can answer "who is responsible for this object's lifetime?" with a single owner, the owner is strong and everyone else is weak.
Two less common qualifiers exist for completeness. __unsafe_unretained is a non-owning reference that is not zeroed on deallocation — a dangling pointer waiting to crash — and is only justified for legacy compatibility or rare performance-critical paths. __autoreleasing appears mostly in NSError ** out-parameters and is usually handled implicitly.
Worked example: a delegate that doesn't leak
The delegate pattern is the canonical place this decision matters. A view controller creates a downloader; the downloader reports progress back to the controller. The controller owns the downloader (strong), so the downloader must not own the controller (weak).
// RMFDownloader.h
@interface RMFDownloader : NSObject
// Weak: the delegate typically owns us. Strong here would cycle.
@property (nonatomic, weak, nullable) id<RMFDownloaderDelegate> delegate;
- (void)startWithURL:(NSURL *)url;
@end
// RMFFeedViewController.m
@interface RMFFeedViewController () <RMFDownloaderDelegate>
@property (nonatomic, strong) RMFDownloader *downloader; // we own it
@end
@implementation RMFFeedViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.downloader = [[RMFDownloader alloc] init];
self.downloader.delegate = self; // safe: downloader holds us weakly
[self.downloader startWithURL:self.feedURL];
}
@endWith delegate declared weak, dismissing the view controller lets it deallocate even while a download is in flight; the downloader's delegate pointer is zeroed automatically and its delegate messages become harmless no-ops (messaging nil is legal in Objective-C). Flip the qualifier to strong and the pair leaks: the controller owns the downloader, the downloader owns the controller, and neither can ever be released.
Blocks: the other cycle trap
Blocks capture self strongly by default. If self also retains the block — directly or through an object it owns — you get a cycle. The standard idiom is the weak-strong dance:
__weak typeof(self) weakSelf = self;
[self.downloader setCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return; // self was deallocated; bail out
[strongSelf reloadTable];
}];The outer __weak breaks the cycle; the inner __strong guarantees the object stays alive for the duration of the block so it cannot vanish halfway through. Skipping the inner strong reference is a real bug class: weakSelf can become nil between two statements, and while messaging nil won't crash, silently skipped logic is hard to debug.
Note the related __block qualifier, which lets a block mutate a captured local variable. It is about mutability, not ownership, but it composes with the pattern above when a block needs to update state.
Trade-offs and how to verify
Weak references are not free. Each one requires the runtime to maintain a side table so the pointer can be zeroed on deallocation, which adds a small cost on assignment and read. In practice this is negligible outside of tight loops touching millions of weak pointers, but it is why __unsafe_unretained still exists as an escape hatch — one you should only reach for after measuring.
You can confirm your ownership choices rather than trusting them:
- Run the app under Instruments' Leaks instrument and exercise the flow (open and dismiss the view controller repeatedly). Cycled objects show up as persistent allocations.
- Run the Clang Static Analyzer (Product > Analyze in Xcode), which flags many retain cycles directly.
- For a deeper look, compile with
clang -fdump-arc-commandsto see exactly where the compiler inserted retain/release calls around your qualifiers. - In mixed ARC/non-ARC projects, verify each legacy file is compiled with
-fobjc-arc(or ARC files with-fno-objc-arc) in Build Phases > Compile Sources; a missing flag produces either compiler errors or double-release crashes.
The takeaway
Draw the ownership tree before you write the property declarations. Strong goes down the tree, weak goes back up, delegates and block-captured self are almost always weak, and __unsafe_unretained is a last resort. Then prove it with one Instruments pass over your most complex screen — five minutes of profiling beats a week of hunting a leaked view controller graph in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.