Skip to content
Modern Objective-C and Swift Bridging
step 1/5

Reading — step 1 of 5

Learn

~2 min readMemory, Runtime, Modern OC

Modern Objective-C (post-2011) added many features that brought it closer to Swift. Knowing them helps maintain code that bridges OC and Swift.

Object literals

%% Old:
NSArray *array = [NSArray arrayWithObjects:@"a", @"b", nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"v1", @"k1", nil];
NSNumber *n = [NSNumber numberWithInt:42];

%% Modern (literals):
NSArray *array = @[@"a", @"b"];
NSDictionary *dict = @{@"k1": @"v1"};
NSNumber *n = @42;
NSNumber *pi = @(M_PI);              %% boxed expression

Much more readable.

Subscripting

NSArray *arr = @[@"a", @"b", @"c"];
arr[0]                                %% @"a" (read)

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"key"] = @"value";              %% set
NSString *v = dict[@"key"];            %% get

Classes can implement -objectAtIndexedSubscript: (read array-style), -setObject:atIndexedSubscript:, -objectForKeyedSubscript:, -setObject:forKeyedSubscript: to make custom types subscriptable.

Generics (lightweight)

ObjC 2.0 (Xcode 7+) added generic type parameters:

NSArray<NSString *> *names = @[@"Ada", @"Bob"];
NSDictionary<NSString *, NSNumber *> *ages = @{@"Ada": @36};

for (NSString *name in names) {       %% compiler knows this is NSString *
    int len = (int)[name length];
}

The <...> is lightweight generics — type info exists in headers and Swift bridging, but ObjC itself still uses id at runtime. Doesn't affect performance, just helps the compiler check types.

Nullability annotations

@interface MyClass : NSObject
- (nullable NSString *)findUser:(NSString *)name;
- (NSString * _Nonnull)mustHaveName;
- (nullable NSData *)readData:(NSError * _Nullable * _Nullable)error;
@end

Helps Swift bridge cleanly: nullable becomes String?, nonnull becomes String.

Auto-synthesized properties

@interface MyClass : NSObject
@property NSString *name;
@end

@implementation MyClass
%% No need to write @synthesize — auto-generated since ObjC 2.0
@end

The compiler creates the ivar (named _name) and getter/setter automatically.

Swift bridging

ObjC code is callable from Swift if exposed via headers. Modern Swift uses modulemaps but the basics:

Swift code calling ObjC:

import MyObjCFramework

let obj = MyClass()
obj.someMethod()

ObjC code calling Swift:

#import "MyApp-Swift.h"      %% auto-generated by Xcode
[swiftClass someMethod];

Swift classes need @objc and class : NSObject to be visible to ObjC.

Mixed-language project tips

  • Use Swift for new code; let ObjC for legacy maintenance
  • Bridge with clean interfaces — don't expose internals across the boundary
  • Use nullability annotations religiously — Swift's optionals depend on them
  • For data classes, prefer Swift structs with @objc if needed
  • Avoid id/AnyObject at the boundary — use specific types

What's gone

  • Manual memory management — ARC handles it
  • @synthesize — auto-synthesized
  • Garbage collection — never widely adopted, removed in 10.12
  • + (id)alloc returning id — modern returns instancetype for type-safe inheritance

Why ObjC still matters

  • Vast existing codebases (Apple frameworks, third-party libraries)
  • Some features still ObjC-only (some KVO, distributed objects, sandbox xpc)
  • Lower-level access to Apple's runtime
  • Performance tuning where Swift overhead matters (rare, but exists)

Apple's strategy: Swift for new features, ObjC for back-compat. Most app developers won't write new ObjC, but they'll read it constantly.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…