Objective-C
The language behind classic Apple apps.
Objective-C adds Smalltalk-style messaging to C. It was the main language for macOS and iOS apps for decades, and much of Apple's frameworks and many large apps still contain Objective-C. Swift has replaced it for new code, but reading and maintaining Objective-C is still a valuable skill.
Why learn Objective-C?
Many big iOS and macOS codebases mix Objective-C and Swift, and Apple's lower-level frameworks still expose Objective-C APIs.
- Legacy apps: large production apps still need Objective-C developers.
- C compatible: any C code works inside Objective-C.
- Apple frameworks: Core ML, Metal and Foundation all have Objective-C APIs.
Set up and run your first program
Install Xcode on a Mac. On Linux you can experiment with GNUstep, but Apple frameworks need macOS.
xcode-select --install
clang -fobjc-arc -framework Foundation hello.m -o hello
./hello#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
NSString *name = @"Ada";
NSLog(@"Hello, AI! Welcome to Objective-C, %@.", name);
}
return 0;
}-fobjc-arc turns on Automatic Reference Counting, so you do not free objects manually.Types and strings
Objective-C has all of C's types plus objects. Object variables are pointers (NSString *). String literals start with @.
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
int layers = 32;
double params = 8.03e9;
NSString *model = @"llama3.1";
BOOL fits = YES;
NSLog(@"%@: %d layers, %.1f GB at FP16, fits=%d", model, layers, params * 2 / 1e9, fits);
}
return 0;
}Sending messages
Methods are called by sending messages in square brackets: [object method]. Arguments are labelled, which makes calls read like sentences.
NSString *s = @"tensor cores";
NSString *upper = [s uppercaseString];
NSUInteger len = [s length];
NSString *joined = [s stringByAppendingString:@" are fast"];
NSString *swapped = [s stringByReplacingOccurrencesOfString:@"tensor" withString:@"CUDA"];
NSLog(@"%@ (%lu) | %@ | %@", upper, (unsigned long)len, joined, swapped);Arrays and dictionaries
NSArray and NSDictionary hold objects. Literal syntax @[...] and @{...} makes them short; numbers are boxed with @.
NSArray *gpus = @[@"H100", @"H200", @"B200"];
NSDictionary *vram = @{@"H100": @80, @"H200": @141, @"B200": @180};
for (NSString *g in gpus) {
NSNumber *gb = vram[g];
if (gb.intValue > 100) NSLog(@"%@ has %@ GB", g, gb);
}
NSMutableArray *queue = [NSMutableArray array];
[queue addObject:@"job-1"];
NSLog(@"%lu jobs", (unsigned long)queue.count);Classes
A class has an @interface (what it offers) and an @implementation (how it works). @property creates getters and setters automatically.
@interface Gpu : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSInteger vram;
- (instancetype)initWithName:(NSString *)name vram:(NSInteger)vram;
- (BOOL)fits:(NSInteger)gb;
@end
@implementation Gpu
- (instancetype)initWithName:(NSString *)name vram:(NSInteger)vram {
if ((self = [super init])) { _name = [name copy]; _vram = vram; }
return self;
}
- (BOOL)fits:(NSInteger)gb { return gb <= self.vram; }
@end
// Gpu *g = [[Gpu alloc] initWithName:@"RTX 5090" vram:32];
// NSLog(@"%@ fits 16 GB: %d", g.name, [g fits:16]);Blocks and asynchronous code
Blocks are closures, written with ^. Grand Central Dispatch runs blocks on background queues, which keeps apps responsive.
int (^square)(int) = ^int(int x) { return x * x; };
NSLog(@"%d", square(12));
NSArray *nums = @[@3, @1, @2];
NSArray *sorted = [nums sortedArrayUsingComparator:^NSComparisonResult(NSNumber *a, NSNumber *b) {
return [a compare:b];
}];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
// heavy work here, e.g. running a model
dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"done: %@", sorted); });
});AI with Objective-C
On Apple devices, AI runs through Core ML (on-device models using the Neural Engine and GPU), Vision (images), Natural Language and Metal Performance Shaders. All have Objective-C APIs, so older apps can add on-device AI without rewriting in Swift.
The essential libraries
Detect language and sentiment on device
The NaturalLanguage framework works offline with built-in models.
#import <Foundation/Foundation.h>
#import <NaturalLanguage/NaturalLanguage.h>
int main(void) {
@autoreleasepool {
NSString *text = @"Tensor cores make training so much faster!";
NLLanguage lang = [NLLanguageRecognizer dominantLanguageForString:text];
NLTagger *tagger = [[NLTagger alloc] initWithTagSchemes:@[NLTagSchemeSentimentScore]];
tagger.string = text;
NLTag score = [tagger tagAtIndex:0 unit:NLTokenUnitParagraph
scheme:NLTagSchemeSentimentScore tokenRange:NULL];
NSLog(@"language=%@ sentiment=%@", lang, score); // en, ~0.8
}
return 0;
}Build an AI agent in Objective-C
NSURLSession and NSJSONSerialization are enough to run a tool-calling loop against a local model. A semaphore keeps this command-line example simple.
#import <Foundation/Foundation.h>
static NSDictionary *chat(NSArray *messages, NSArray *tools) {
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:@"http://localhost:11434/v1/chat/completions"]];
req.HTTPMethod = @"POST";
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
req.HTTPBody = [NSJSONSerialization dataWithJSONObject:@{@"model": @"llama3.1", @"messages": messages, @"tools": tools} options:0 error:nil];
__block NSDictionary *out = nil;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[[NSURLSession.sharedSession dataTaskWithRequest:req completionHandler:^(NSData *d, NSURLResponse *r, NSError *e) {
if (d) out = [NSJSONSerialization JSONObjectWithData:d options:0 error:nil];
dispatch_semaphore_signal(sem);
}] resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return out[@"choices"][0][@"message"];
}
int main(void) {
@autoreleasepool {
NSArray *tools = @[@{@"type": @"function", @"function": @{@"name": @"get_weather", @"description": @"Weather for a city",
@"parameters": @{@"type": @"object", @"properties": @{@"city": @{@"type": @"string"}}, @"required": @[@"city"]}}}];
NSMutableArray *messages = [@[@{@"role": @"user", @"content": @"Should I take an umbrella in Lahore today?"}] mutableCopy];
for (int round = 0; round < 5; round++) {
NSDictionary *msg = chat(messages, tools);
[messages addObject:msg];
NSArray *calls = msg[@"tool_calls"];
if (![calls isKindOfClass:NSArray.class] || calls.count == 0) { NSLog(@"%@", msg[@"content"]); break; }
for (NSDictionary *c in calls) {
NSData *a = [c[@"function"][@"arguments"] dataUsingEncoding:NSUTF8StringEncoding];
NSString *city = [NSJSONSerialization JSONObjectWithData:a options:0 error:nil][@"city"];
[messages addObject:@{@"role": @"tool", @"tool_call_id": c[@"id"],
@"content": [NSString stringWithFormat:@"It is 24°C and sunny in %@.", city]}];
}
}
}
return 0;
}In an iOS app, never block the main thread with a semaphore; use the completion handler directly.
ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.Objective-C learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
Objective-C cheat sheet
| Task | Syntax |
|---|---|
| String | NSString *s = @"hi"; |
| Message | [obj doThing:arg] |
| Array | NSArray *a = @[@1, @2]; |
| Dictionary | NSDictionary *d = @{@"k": @1}; |
| Log | NSLog(@"%@", obj); |
| Block | ^(int x){ return x * 2; } |
Common mistakes
- Sending a message to
nilsilently does nothing, which can hide bugs. - Collections can only hold objects; wrap numbers as
@(n). - Retain cycles with blocks and delegates leak memory; use
__weakreferences.
Where to go next
- Apple: Programming with Objective-C — official guide
- Apple developer docs — framework reference
- Core ML docs — on-device ML
On TensorCore.si: Swift volume, C volume.
Keyboard: [ previous language · ] next language · / search the library