Reading — step 1 of 5
Learn
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
Swiftfor new code; letObjCfor 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
@objcif needed - Avoid
id/AnyObjectat 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)allocreturningid— modern returnsinstancetypefor 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…