Skip to content
Volume O · The TensorCore.si Library

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.

Prologue

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.
Prologue

Set up and run your first program

Install Xcode on a Mac. On Linux you can experiment with GNUstep, but Apple frameworks need macOS.

terminal
xcode-select --install
clang -fobjc-arc -framework Foundation hello.m -o hello
./hello
hello.m
#import <Foundation/Foundation.h>

int main(void) {
    @autoreleasepool {
        NSString *name = @"Ada";
        NSLog(@"Hello, AI! Welcome to Objective-C, %@.", name);
    }
    return 0;
}
Tip-fobjc-arc turns on Automatic Reference Counting, so you do not free objects manually.
Chapter 1

Types and strings

Objective-C has all of C's types plus objects. Object variables are pointers (NSString *). String literals start with @.

basics.m
#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;
}
Chapter 2

Sending messages

Methods are called by sending messages in square brackets: [object method]. Arguments are labelled, which makes calls read like sentences.

messages.m
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);
Chapter 3

Arrays and dictionaries

NSArray and NSDictionary hold objects. Literal syntax @[...] and @{...} makes them short; numbers are boxed with @.

collections.m
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);
Chapter 4

Classes

A class has an @interface (what it offers) and an @implementation (how it works). @property creates getters and setters automatically.

Gpu.m
@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]);
Chapter 5

Blocks and asynchronous code

Blocks are closures, written with ^. Grand Central Dispatch runs blocks on background queues, which keeps apps responsive.

blocks.m
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); });
});
Part II · AI with Objective-C

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

Core MLRun trained models on device.
VisionImage classification, text and face detection.
NaturalLanguageTokenising, language detection, embeddings.
NSURLSessionCall cloud LLM APIs.

Detect language and sentiment on device

The NaturalLanguage framework works offline with built-in models.

nl.m
#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;
}
Part III · Build an AI agent

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.

Your goal→Model plans→Tool call→Your code runs the tool→Result back to model→Final answer
agent.m
#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.

Run it locallyInstall Ollama, run 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.
Part IV · Your path

Objective-C learning roadmap

Tick items off as you go. Your progress is saved in this browser.

Beginner

Intermediate

Advanced

Part IV · Your path

Projects to build

Notes appA simple iOS notes app with UIKit.
Image classifierClassify camera photos with Vision and Core ML.
Swift bridgeCall Objective-C code from a Swift app.
Reference

Objective-C cheat sheet

TaskSyntax
StringNSString *s = @"hi";
Message[obj doThing:arg]
ArrayNSArray *a = @[@1, @2];
DictionaryNSDictionary *d = @{@"k": @1};
LogNSLog(@"%@", obj);
Block^(int x){ return x * 2; }
Reference

Common mistakes

  • Sending a message to nil silently does nothing, which can hide bugs.
  • Collections can only hold objects; wrap numbers as @(n).
  • Retain cycles with blocks and delegates leak memory; use __weak references.
Reference

Where to go next

On TensorCore.si: Swift volume, C volume.

Keyboard: [ previous language · ] next language · / search the library