Ownership Qualifiers and Out Parameters

Understand all four ARC ownership qualifiers -- __strong, __weak, __unsafe_unretained, and __autoreleasing -- as they apply to local variables, parameters, and return values, and learn exactly how the NSError ** out-parameter pattern works under ARC, including the compiler errors that show up when a qualifier is mismatched and how to fix them.

The Manual Reference Counting and ARC lessons established the basic mechanics of retain, release, and automatic memory management, and the Advanced Objective-C Type System lesson previewed the idea that ARC attaches an ownership qualifier to every object pointer variable. This lesson finishes that story: it works through __strong, __weak, __unsafe_unretained, and __autoreleasing individually, shows how those qualifiers apply not just to local variables but to parameters and return values too, and uses the extremely common NSError ** out-parameter pattern -- familiar from the Error Handling lesson -- as the concrete case study for how ARC actually manages a pointer-to-pointer under the hood.

Why ARC Needs Ownership Qualifiers at All

ARC inserts retain and release calls for you automatically, but it needs to know, for every single object pointer variable in your program, exactly how that variable should behave: should it keep the object alive as long as the variable exists? Should it merely observe the object without affecting its lifetime? Should it participate in an autorelease pool instead of owning the object outright? Ownership qualifiers are how the compiler encodes that decision as part of a variable's actual type, not as a comment or a convention -- which is why they can be written explicitly in a declaration and why mismatching them across a function call is a genuine compile error, not just a style warning.

There are exactly four ownership qualifiers: __strong, __weak, __unsafe_unretained, and __autoreleasing. Every object pointer variable under ARC has one of these, whether or not it's written explicitly -- __strong is the default when none is written, which is why most Objective-C code never needs to type the qualifier at all. The other three exist for the specific situations covered in this lesson: avoiding retain cycles, interoperating with older APIs, and handling out parameters.

• __strong: keeps the referenced object alive for as long as the variable holds it -- the default. • __weak: refers to an object without keeping it alive; becomes nil automatically if the object is deallocated. • __unsafe_unretained: refers to an object without keeping it alive, and does NOT zero itself -- can dangle. • __autoreleasing: the qualifier ARC uses for values that will be autoreleased, most visibly in out parameters.

__strong: The Default Ownership Qualifier

A __strong reference keeps its object alive: as long as at least one __strong variable (or property, or instance variable) points to an object, ARC guarantees that object is not deallocated. When a __strong variable is reassigned or goes out of scope, ARC automatically releases whatever it previously pointed to, exactly mirroring the manual retain/release discipline the Manual Reference Counting lesson covered by hand.

Because __strong is the default, ordinary code like NSString *name = @"Ada"; is implicitly __strong NSString *name = @"Ada"; -- the qualifier is simply never written out in the overwhelming majority of Objective-C code, which is exactly why it's worth naming explicitly here: it's easy to use __strong semantics every day without ever consciously thinking of them as a qualifier at all.

• Default for every object pointer variable, property, and instance variable unless otherwise annotated. • Guarantees the referenced object stays alive as long as the __strong reference exists. • ARC automatically releases the previous value whenever a __strong variable is reassigned or goes out of scope.

__weak: A Non-Owning Reference That Zeros Itself

A __weak reference points to an object without keeping it alive -- if every __strong reference to that object goes away, the object is deallocated regardless of any __weak references still pointing at it, and ARC automatically sets each of those __weak variables to nil at that moment. This 'zeroing' behavior is what makes __weak safe to use: code that reads a __weak variable after its object has been deallocated simply gets nil back, never a dangling pointer to freed memory.

The canonical use case is breaking retain cycles, most visibly in delegate references: if a view controller holds a __strong reference to a delegate, and that delegate holds a __strong reference back to the view controller, neither can ever be deallocated. Declaring the delegate property __weak (or, in practice, weak at the property level, which applies the same qualifier) breaks that cycle while still letting the view controller message its delegate normally whenever the delegate is actually alive.

• Refers to an object without keeping it alive -- does not affect the object's reference count. • Automatically becomes nil the instant the object it referred to is deallocated -- never dangles. • The standard technique for breaking retain cycles, most commonly on delegate references.

__unsafe_unretained: A Non-Owning Reference Without the Safety Net

__unsafe_unretained also refers to an object without keeping it alive, making it look like __weak at first glance -- but it lacks the zeroing behavior entirely. If the object it points to is deallocated, an __unsafe_unretained variable keeps holding the old, now-invalid address; reading or messaging it afterward is undefined behavior, exactly the kind of dangling-pointer bug that manual reference counting made everyday practice for and that __weak was specifically introduced to eliminate.

Given that __weak is strictly safer, __unsafe_unretained mainly shows up in two situations: supporting deployment targets or runtime configurations that predate __weak support, and certain low-level or Core Foundation-adjacent interop contexts where a fully weak reference isn't available. Outside of those specific cases, __weak is almost always the better choice, and its name is a deliberate, honest warning: this reference is unsafe, and it is your job to be certain it never outlives what it points to.

• Refers to an object without keeping it alive, same as __weak -- but does NOT zero itself when the object is deallocated. • Can dangle: reading it after deallocation is undefined behavior, just like a raw pointer bug in manual reference counting. • Mainly reserved for legacy interop or contexts that don't support __weak; prefer __weak otherwise.

__autoreleasing: The Qualifier Behind Out Parameters

__autoreleasing marks a variable whose value will be placed into the nearest active autorelease pool rather than retained directly by that variable -- it's the ownership qualifier ARC assigns, largely invisibly, to the pointee of pointer-to-pointer parameters like NSError **. You will rarely need to write __autoreleasing explicitly yourself; the compiler inserts the necessary handling automatically for the extremely common &variable idiom used when calling a method that takes an out parameter.

It's worth being precise about what __autoreleasing is really doing: when a method takes an NSError **error parameter, ARC treats that as NSError * __autoreleasing *error by default. Any value the method writes through *error is autoreleased rather than simply assigned, so that ownership transfers cleanly back to the calling scope without the callee needing to know anything about how long the caller intends to keep the resulting error object alive.

• Marks a value that will be placed in the nearest autorelease pool rather than retained outright by that variable. • The implicit qualifier ARC gives to the pointee of an out-parameter pointer-to-pointer, like NSError **error. • Usually invisible: the ordinary &variable idiom at a call site is enough; ARC bridges the ownership automatically.

Ownership-Qualified Parameters and Return Values

Ownership qualifiers aren't limited to local variables -- they're a real part of a parameter's or return value's type, and can be written explicitly on either. A method signature like - (void)setDelegate:(id __weak)delegate; documents, at the type level, that the method stores its argument as a weak reference rather than a strong one; a function returning id __unsafe_unretained promises, in its type, that the caller receives a non-owning reference and must not assume the object stays alive.

In practice, most ordinary parameters and return values simply use the default __strong qualifier and never need to spell it out, exactly the way local variables usually don't. Explicit ownership qualifiers on parameters and return values become important specifically at API boundaries -- delegate setters, C-callback-style function signatures, and, most importantly for the rest of this lesson, out parameters -- where the qualifier communicates a real, compiler-enforced contract about who owns what.

• Ownership qualifiers are part of a parameter's or return value's full type, not just local-variable syntax. • id __weak delegate documents, at the type level, that a method stores a non-owning reference. • Most parameters and return values are implicitly __strong and never need the qualifier written out explicitly.

The NSError ** Out-Parameter Pattern in Practice

The Error Handling lesson introduced the convention of methods that return BOOL (or nil) to signal failure while writing a descriptive NSError object back through a trailing NSError **error parameter. This lesson's job is to show exactly how ownership flows through that parameter: NSError * is an ordinary strong object pointer; NSError ** is a pointer to one of those; and under ARC, that pointee is implicitly __autoreleasing, which is what lets the callee hand a freshly created error object back to the caller safely, without either side needing to manage retain counts manually across the call boundary.

The diagram accompanying this lesson shows this exact chain: NSError * is the value type, NSError ** is the pointer-to-pointer used to write it back to the caller, and __autoreleasing is the ownership qualifier ARC applies to that pointee so the transfer is memory-safe in both directions.

• NSError * is the ordinary, __strong-by-default type of the error object itself. • NSError ** is a pointer to that pointer -- the mechanism for writing a new NSError * back to the caller. • ARC treats the pointee of an NSError ** out parameter as __autoreleasing by default, safely transferring ownership.

ARC's Automatic Handling of Out Parameters -- and Where It Surprises You

For the common case -- declare a local NSError *error = nil; with no explicit qualifier, then pass &error to a method expecting NSError error -- ARC handles everything invisibly. Because the local variable is implicitly __strong, and the parameter expects __autoreleasing, the compiler quietly introduces a temporary __autoreleasing variable behind the scenes, passes its address to the callee, and copies the result back into your __strong local after the call returns. You never see this happen; it's exactly why most Objective-C code that uses the NSError pattern never mentions ownership qualifiers at all.

That silent bridging only works for the ordinary, unannotated &variable idiom, though. The moment a local variable's qualifier is made explicit and doesn't match what the callee expects -- an __unsafe_unretained or __weak local passed as &error, or a pre-existing NSError * __strong * pointer-to-pointer variable passed directly -- ARC has no safe way to bridge the mismatch automatically, and the compiler refuses to compile the call at all rather than silently doing the wrong thing. The mistakes below show exactly what these errors look like and how to fix each one.

• The ordinary NSError *error = nil; ... &error idiom works because ARC inserts a hidden __autoreleasing temporary automatically. • That automatic bridging only applies to the plain &variable idiom on an implicitly __strong local. • An explicitly __weak or __unsafe_unretained local, or a manually __strong-qualified pointer-to-pointer, cannot be silently bridged -- the compiler errors instead of guessing.

Code Examples

Both data and error are pointer-to-pointer out parameters: the method writes through *data and *error rather than reassigning the parameter variables themselves, and always checks that the pointer isn't NULL first, since callers are allowed to pass NULL if they don't care about that particular out value. The caller declares plain, implicitly __strong locals and passes their addresses with &; ARC handles the __autoreleasing bridging for error automatically.

*error = [NSError errorWithDomain:@"DataLoaderErrorDomain" code:404 userInfo:...];

Writing __strong explicitly changes nothing about behavior -- it's simply what NSString *greeting already meant implicitly. Reassigning a __strong variable releases its old value and retains the new one automatically; when a __strong local goes out of scope, ARC releases whatever it still holds.

__strong NSString *greeting = @"Hello";

ViewController holds a __strong reference to downloader, and downloader holds a __weak reference back to its delegate (the view controller) -- if delegate were __strong instead, neither object could ever be deallocated, since each would be keeping the other alive. Because it's __weak, if the view controller is ever deallocated first, downloader.delegate automatically becomes nil rather than dangling.

@property (nonatomic, weak) id<DownloadDelegate> delegate;

unsafe_unretained behaves like weak in that it doesn't keep target alive, but unlike weak it is never zeroed when the object it refers to is deallocated -- the property simply keeps holding a stale address. Calling a method through it after deallocation is undefined behavior, exactly the class of bug __weak exists to prevent; this is why __weak is preferred whenever it's available.

@property (nonatomic, unsafe_unretained) id target;

Common mistake: Treating __unsafe_unretained as a drop-in, equally-safe alternative to __weak -- it is not; it only lacks the retain, not the danger of a dangling pointer.

Writing NSError * __autoreleasing *error spells out explicitly what NSError error already means implicitly under ARC. It's rarely necessary to write this yourself -- the plain NSError error spelling is idiomatic -- but seeing it written out makes clear exactly which ownership qualifier the pointee carries, matching the diagram's __autoreleasing out parameter stage.

error:(NSError * __autoreleasing *)error

@interface DataLoader : NSObject
- (BOOL)loadData:(NSData **)data error:(NSError **)error;
@end

@implementation DataLoader
- (BOOL)loadData:(NSData **)data error:(NSError **)error {
    NSData *fetched = [self fetchRawBytesOrNil];
    if (fetched == nil) {
        if (error) {
            *error = [NSError errorWithDomain:@"DataLoaderErrorDomain"
                                          code:404
                                      userInfo:@{ NSLocalizedDescriptionKey: @"No data found" }];
        }
        return NO;
    }
    if (data) {
        *data = fetched;
    }
    return YES;
}
@end

// Caller:
DataLoader *loader = [[DataLoader alloc] init];
NSData *resultData = nil;
NSError *error = nil;
BOOL success = [loader loadData:&resultData error:&error];
if (!success) {
    NSLog(@"Load failed: %@", error.localizedDescription);
} else {
    NSLog(@"Loaded %lu bytes", (unsigned long)resultData.length);
}
__strong NSString *greeting = @"Hello";
greeting = @"Hola";   // ARC releases "Hello" and retains "Hola" automatically

- (void)demonstrateStrongLifetime {
    __strong NSObject *temp = [[NSObject alloc] init];
    // temp keeps this object alive for the rest of this method's scope
}   // ARC releases temp's object here, as the variable goes out of scope
@protocol DownloadDelegate <NSObject>
- (void)downloadDidFinish;
@end

@interface Downloader : NSObject
@property (nonatomic, weak) id<DownloadDelegate> delegate;
@end

@implementation Downloader
- (void)finish {
    [self.delegate downloadDidFinish];
}
@end

@interface ViewController : NSObject <DownloadDelegate>
@property (nonatomic, strong) Downloader *downloader;
@end

@implementation ViewController
- (void)start {
    self.downloader = [[Downloader alloc] init];
    self.downloader.delegate = self;   // weak: no retain cycle with self.downloader
}
- (void)downloadDidFinish {
    NSLog(@"Finished");
}
@end
@interface LegacyObserver : NSObject
@property (nonatomic, unsafe_unretained) id target;
@end

@implementation LegacyObserver
- (void)notifyTarget {
    // If 'target' has already been deallocated, this is undefined
    // behavior -- unlike a weak reference, it will NOT be nil here,
    // it will be a dangling pointer to freed memory.
    [self.target handleNotification];
}
@end
- (BOOL)parseString:(NSString *)input
              error:(NSError * __autoreleasing *)error {
    if (input.length == 0) {
        if (error) {
            // Assigning through *error autoreleases the new NSError,
            // which is exactly what the __autoreleasing qualifier means
            *error = [NSError errorWithDomain:@"ParserErrorDomain"
                                          code:1
                                      userInfo:nil];
        }
        return NO;
    }
    return YES;
}

How an Out Parameter Is Ownership-Qualified Under ARC

NSError * is the ordinary strong error value; NSError ** is the pointer-to-pointer used to write it back to the caller; ARC treats that pointee as __autoreleasing by default so ownership transfers safely across the call.

__unsafe_unretained NSError *error = nil; BOOL success = [loader loadData:&resultData error:&error]; // error: Passing 'NSError *__unsafe_unretained *' to parameter of // type 'NSError *__autoreleasing *' changes retain/release properties

NSError *error = nil; // implicitly __strong BOOL success = [loader loadData:&resultData error:&error]; // ARC bridges the plain &error idiom to __autoreleasing automatically

ARC's automatic bridging for out parameters only applies to a plain, implicitly __strong local variable. An explicitly __unsafe_unretained (or __weak) local cannot be silently converted to __autoreleasing, so the compiler refuses the call rather than risk incorrect memory management.

NSError * __strong *errorPtr = &someStrongError; [loader loadData:&resultData error:errorPtr]; // error: incompatible pointer types passing 'NSError *__strong *' // to parameter of type 'NSError *__autoreleasing *'

NSError * __autoreleasing *errorPtr = &someError; [loader loadData:&resultData error:errorPtr];

Once you take the address of a variable into a pre-declared pointer-to-pointer variable yourself, its ownership qualifier is fixed at that point -- ARC's implicit bridging only happens for the common inline &variable idiom at the call site, not for an already-typed pointer-to-pointer variable whose qualifier doesn't match what the parameter expects.

• (BOOL)loadData:(NSData )data error:(NSError )error { NSError *localError = [NSError errorWithDomain:@"Loader" code:1 userInfo:nil]; error = &localError; // reassigns the local parameter variable itself return NO; } - (BOOL)loadData:(NSData )data error:(NSError )error { NSError *localError = [NSError errorWithDomain:@"Loader" code:1 userInfo:nil]; if (error) { *error = localError; // dereferences and assigns through the pointer } return NO; } error is a local parameter variable holding an address; reassigning error itself only changes what that local variable points to and has no effect on the caller's variable. You must dereference it with *error =... to actually write the new value back through to the caller.

NSError *error = nil; [loader loadData:&resultData error:&error]; if (error != nil) { // handle failure }

NSData *resultData = nil; NSError *error = nil; BOOL success = [loader loadData:&resultData error:&error]; if (!success) { // handle failure using 'error', now guaranteed meaningful }

Well-behaved APIs only guarantee the error out parameter is populated when the method actually reports failure via its return value; checking error alone, without first checking the BOOL/nil return, is a common bug because some implementations leave error untouched on success rather than explicitly resetting it to nil.

Which ownership qualifier is the default for an Objective-C object pointer variable under ARC when no qualifier is written explicitly? A. __autoreleasing B. __unsafe_unretained C. __strong D. __weak

C. __strong — __strong is the default: an unqualified declaration like NSString *name is implicitly __strong NSString *name, and it keeps the referenced object alive for as long as the variable holds it.

What is the key difference between __weak and __unsafe_unretained? A. __weak keeps the object alive; __unsafe_unretained does not B. They behave identically in every case C. Both are non-owning references, but __weak automatically zeroes itself when the object is deallocated, while __unsafe_unretained does not and can dangle D. __unsafe_unretained is only valid on properties, never on local variables

C. Both are non-owning references, but __weak automatically zeroes itself when the object is deallocated, while __unsafe_unretained does not and can dangle — Neither __weak nor __unsafe_unretained keeps its object alive. The difference is safety: __weak becomes nil automatically the instant its object is deallocated, while __unsafe_unretained keeps holding the old, now-invalid address, making it undefined behavior to use afterward.

Under ARC, what ownership qualifier does the pointee of an NSError ** out parameter carry by default? A. __strong B. __weak C. __unsafe_unretained D. __autoreleasing

D. __autoreleasing — ARC treats NSError **error as NSError * __autoreleasing *error by default, which is what allows a freshly created NSError to be written back through the pointer and transferred safely to the calling scope.

Why does passing &error work seamlessly for a plain 'NSError *error = nil;' local, but fail to compile if error is instead declared __unsafe_unretained? A. It never actually fails to compile in either case B. ARC automatically bridges a plain, implicitly __strong local to the __autoreleasing type an out parameter expects, but cannot safely bridge an explicitly __unsafe_unretained or __weak variable, so the compiler rejects the mismatch C. __unsafe_unretained variables cannot have their address taken at all D. NSError specifically forbids __unsafe_unretained regardless of context

B. ARC automatically bridges a plain, implicitly __strong local to the __autoreleasing type an out parameter expects, but cannot safely bridge an explicitly __unsafe_unretained or __weak variable, so the compiler rejects the mismatch — The implicit &variable-to-__autoreleasing bridging ARC performs only applies to ordinary __strong locals. An explicitly-qualified __unsafe_unretained or __weak variable can't be silently converted, so the compiler produces an incompatible-pointer-types error instead of guessing.

Inside the implementation of - (BOOL)loadData:(NSData )data error:(NSError )error, what is the correct way to actually report an error back to the caller? A. Reassign the error parameter directly: error = newErrorObject; B. Dereference the pointer and assign through it: *error = newErrorObject; (after checking error is not NULL) C. Return the NSError object directly instead of using the BOOL return value D. Call [error setError:newErrorObject] as a message send

B. Dereference the pointer and assign through it: *error = newErrorObject; (after checking error is not NULL) — error is a local variable holding an address; reassigning error itself only changes that local copy and never reaches the caller. Writing through *error = newErrorObject (after confirming error isn't NULL, since callers may pass NULL) is what actually communicates the new value back to the caller's variable.

What is the primary purpose of __weak references such as a delegate property, as covered alongside the earlier ARC and Manual Reference Counting lessons? A. To make message sends faster by skipping the runtime's method lookup B. To allow an object to reference another without keeping it alive, most commonly to break retain cycles like a delegate pattern where two objects would otherwise keep each other alive indefinitely C. To guarantee an object is always deallocated immediately after use D. To convert an Objective-C object pointer into a plain C pointer

B. To allow an object to reference another without keeping it alive, most commonly to break retain cycles like a delegate pattern where two objects would otherwise keep each other alive indefinitely — A __weak reference doesn't affect an object's reference count, which is exactly what's needed to break a retain cycle: if a delegate property were __strong, two objects that reference each other (like a downloader and its delegate) would keep each other alive forever, exactly the class of leak the Manual Reference Counting and ARC lessons describe manual/automatic retain-count management as needing to avoid.

NSError *
   ↓
NSError **
   ↓
__autoreleasing out parameter