Choosing When to Enable ARC in Legacy Objective‑C Projects
Learn how to enable ARC per file in an Objective‑C project, verify the setup with build logs and Instruments, and understand the limits when bridging Core Foundation objects.
24 Sept 2026, 04:50 UTC

The Problem: Mixed Memory‑Management Models Cause Build Errors
When you inherit an Objective‑C codebase that still uses manual retain/release, adding a new class that relies on ARC can trigger compiler errors such as “ARC forbids explicit message send of ‘retain’” or linker warnings about missing CFRelease calls. The immediate symptom is a failure to compile, but the deeper issue is deciding whether to convert the whole project to ARC or keep manual management for specific files.
Thesis: Enable ARC per file and use ownership qualifiers for Core Foundation bridges
Instead of an all‑or‑nothing migration, you can keep the existing manual‑reference‑counted files unchanged and compile only the new files with the -fobjc-arc flag. This approach lets you adopt ARC incrementally while preserving the stability of legacy code.
Worked Example: Adding an ARC‑Enabled Helper Class
- Create a new file
NetworkHelper.mthat will use ARC. - In Xcode, select the file, open the Identity inspector, and set “Compiler Flags” to
-fobjc-arc. (Equivalently, add the flag to the project’s Build Settings under “Objective‑C Automatic Reference Counting → No” for that file.) - Implement a simple class that owns an
NSURLSessionobject:
#import <Foundation/Foundation.h>
@interface NetworkHelper : NSObject
@property (nonatomic, strong) NSURLSession *session;
- (void)fetchURL:(NSURL *)url completion:(void (^)(NSData *, NSError *))handler;
@end
@implementation NetworkHelper
- (instancetype)init {
self = [super init];
if (self) {
_session = [NSURLSession sharedSession];
}
return self;
}
- (void)fetchURL:(NSURL *)url completion:(void (^)(NSData *, NSError *))handler {
NSURLSessionDataTask *task = [_session dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (handler) handler(data, error);
}];
[task resume];
}
@end
Notice there are no explicit retain, release, or autorelease calls. The compiler synthesizes the needed retains and releases for the session property and the local task variable.
Verification Steps
- Check the compile command for
NetworkHelper.min the Build Log; you should see-fobjc-arcpresent. - Run the app on a simulator or device, trigger
fetchURL:, then open Instruments → Allocations. Look for theNetworkHelperinstance; it should disappear when you set the owning reference tonilor let it go out of scope. - Alternatively, add
-Warcto “Other Warning Flags” and ensure the build produces no warnings about illegal retain/release calls in this file.
Trade‑off and Limitation
The main limitation is that any manual‑reference‑counted file that imports the ARC‑enabled header must not violate ARC rules. For example, if a legacy file tries to retain an object returned from the ARC class, the compiler will error. The remedy is to expose only ownership‑qualified interfaces (e.g., return __strong objects) or to wrap legacy usage in a thin ARC‑compatible facade. Additionally, Core Foundation objects passed between ARC and manual code still require explicit __bridge_retained or __bridge_transfer casts; forgetting these leads to leaks or crashes.
Actionable Closing
Start by enabling ARC on a single new file, verify with the build log and Instruments, then gradually migrate more files as confidence grows. Keep a checklist: (1) set -fobjc-arc per file, (2) audit any Core Foundation bridges for proper qualifiers, (3) run the static analyzer to catch missing transfers. This incremental strategy lets you reap ARC’s safety benefits without destabilizing the existing codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.