100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

ARC and Memory Management

How Automatic Reference Counting manages object lifetimes in Objective-C, and how it relates to the manual retain/release model it replaced.

Memory & Foundation FrameworkIntermediate10 min readJul 10, 2026
Analogies

What ARC Actually Does

Automatic Reference Counting (ARC) is a compile-time feature, not a runtime garbage collector. When you build your project, the Clang compiler inserts the retain, release, and autorelease calls for you at every point an object pointer is assigned, passed, or goes out of scope, based on strict ownership rules it infers from your variable qualifiers and method naming conventions. Because the calls are inserted at compile time, there is no runtime pause to scan the heap the way a tracing garbage collector would pause an app; the cost is spread out as ordinary function calls baked into your compiled binary.

🏏

Cricket analogy: It's like a scorer who fills in every run and wicket the instant it happens rather than reconstructing the scorecard at the end of the innings from memory — Clang writes the retain/release ledger entries as it compiles, not later at runtime, the way Sachin Tendulkar's scorebook was updated ball by ball.

Ownership Qualifiers: strong, weak, and unsafe_unretained

ARC's rules hinge on four ownership qualifiers applied to object pointers: __strong (the default), __weak, __unsafe_unretained, and __autoreleasing. A strong reference increments the retain count and keeps the object alive as long as the reference exists; a weak reference does not increment the count and is automatically set to nil when the object is deallocated, which is what makes it safe for breaking retain cycles such as delegate references. __unsafe_unretained behaves like weak but is not zeroed on deallocation, so it can become a dangling pointer — it exists mainly for compatibility with older runtimes that don't support zeroing weak references. Property declarations expose these as attributes, so @property (nonatomic, strong) NSString *name; and @property (nonatomic, weak) id<UITableViewDelegate> delegate; are the idiomatic ways to declare them.

🏏

Cricket analogy: A strong reference is like the official captain of the team — as long as that role is filled, the team (object) stays active — while a weak reference is like a substitute fielder such as an impact player who can be swapped out or vanish from the XI without the team ceasing to exist.

objectivec
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) Person *bestFriend; // avoids retain cycle
@end

@implementation Person
- (instancetype)initWithName:(NSString *)name {
    self = [super init];
    if (self) {
        _name = [name copy];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"%@ is being deallocated", self.name);
}
@end

Person *alice = [[Person alloc] initWithName:@"Alice"];
Person *bob = [[Person alloc] initWithName:@"Bob"];
alice.bestFriend = bob; // weak: does not retain bob
bob.bestFriend = alice; // weak: does not retain alice
// When alice and bob go out of scope, both dealloc normally.

Autorelease Pools and Object Lifetime

Even under ARC, autorelease pools still matter. Methods like arrayWithObjects: or stringWithFormat: return objects that are autoreleased rather than immediately owned by the caller, meaning their retain count is decremented at the end of the current autorelease pool's scope rather than instantly. In tight loops that create many temporary objects — for example, processing thousands of rows and building a formatted NSString for each — memory can balloon until the pool drains, so wrapping the loop body in @autoreleasepool { } forces periodic draining and keeps peak memory bounded. The main run loop on iOS and macOS wraps each event-processing cycle in its own autorelease pool automatically, which is why memory spikes inside a single tight synchronous loop are the case developers usually need to handle manually.

🏏

Cricket analogy: An autorelease pool is like the end-of-over changeover where the ground staff clear discarded gloves, bails, and spare balls left on the field — during the over things pile up, but the changeover is the guaranteed drain point, just as a tight loop over thousands of deliveries needs its own @autoreleasepool to avoid clutter building up mid-innings.

Do not confuse ARC with garbage collection. ARC still uses reference counting under the hood, so retain cycles (two objects strongly holding each other) are never automatically collected — you must break them manually with weak or unsafe_unretained references, or the objects leak for the lifetime of the app.

Bridging with Core Foundation

ARC only manages Objective-C object memory; it has no knowledge of Core Foundation types like CFStringRef or CGImageRef, which still require manual CFRetain/CFRelease calls. When you need to convert between a Core Foundation type and its toll-free-bridged Objective-C equivalent (such as CFStringRef and NSString *), you must tell the compiler who owns the resulting memory using bridging casts: __bridge performs a plain cast with no ownership transfer, __bridge_transfer hands ownership of a CF object to ARC (which will release it later), and __bridge_retained hands ownership of an ARC-managed object to the CF world (you must CFRelease it yourself later). Getting these wrong causes either a leak (missed release) or a crash from over-release.

🏏

Cricket analogy: A __bridge cast is like a fielder just glancing at the ball without taking possession, __bridge_transfer is like handing the ball to the umpire who now takes custody of it, and __bridge_retained is like the bowler taking the ball back from the umpire — each transfer of custody must be explicit, just as ARC's bridging casts must explicitly state who now owns the memory.

  • ARC inserts retain/release/autorelease calls at compile time based on ownership qualifiers — it is not a runtime garbage collector.
  • __strong (default) keeps an object alive; __weak auto-nils on deallocation; __unsafe_unretained does not zero and can dangle.
  • ARC does not break retain cycles automatically — strong reference cycles between objects (e.g., delegate patterns) still leak unless one side is weak.
  • Autoreleased objects (from convenience constructors) are freed when the enclosing autorelease pool drains, not immediately.
  • Wrap tight loops that create many temporary objects in @autoreleasepool { } to bound peak memory.
  • Core Foundation objects are not managed by ARC and require explicit CFRetain/CFRelease or bridging casts (__bridge, __bridge_transfer, __bridge_retained).
  • dealloc is still a valid override point under ARC for cleanup, but you should never call [super dealloc] explicitly — ARC does that for you.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ObjectiveCStudyNotes#ARCAndMemoryManagement#ARC#Memory#Management#Actually#StudyNotes#SkillVeris#ExamPrep