Your First Objective-C Program: Reading Every Piece of Hello, SampleApp

Write, compile, and understand a complete Objective-C program, explaining in depth what #import, Foundation, main/argc/argv, @autoreleasepool, NSLog, and @-string literals each actually do.

In the C portion of this course, your first program was the smallest useful thing that could print text and exit cleanly. Objective-C's equivalent first program looks deceptively similar on the surface but introduces several genuinely new pieces of syntax and machinery at once. Rather than rushing past them because 'that's just boilerplate you always write,' this lesson takes the canonical Objective-C hello-world program apart piece by piece, explaining exactly what each new element does, why it exists, and how it connects to what you already know from C. By the end, nothing in this program will feel like magic you're copying without understanding.

The Complete Program

Here is the program this lesson is built around, in full, exactly as you'd type it into a file named main.m:

Every one of the six non-blank lines in this program does real, specific work, and several of them are entirely new syntax you haven't seen in the C module. The rest of this lesson works through them in the order they appear: #import, Foundation, main and its parameters, @autoreleasepool, and finally NSLog together with the @"..." string literal it's given.

#import: Like #include, But Smarter

#import <Foundation/Foundation.h> is the very first line, and it should look immediately familiar: it resembles #include <stdio.h> from the C module, and it plays almost exactly the same role — pulling in declarations from another file (here, Foundation's master header) so the compiler knows what NSString, NSLog, and every other Foundation symbol you're about to use actually are, before it tries to compile any code that references them. Like #include, #import is a preprocessor directive, handled in that same first pipeline stage covered back in the C module: it does not end in a semicolon, and it is resolved via textual inclusion before real compilation begins.

The difference — and the reason Objective-C introduced #import as a distinct directive rather than just reusing #include — is automatic protection against double-inclusion. With plain #include, if the same header ends up pulled in twice (a very common situation once a project has many files including each other, sometimes indirectly), the compiler sees every declaration in that header twice, which for certain kinds of declarations causes a 'redefinition' error unless the header author manually adds include-guard boilerplate (#ifndef HEADER_H / #define HEADER_H / ... / #endif) to prevent it. #import builds that same protection in automatically, at the compiler level: no matter how many times, directly or indirectly, the same file is imported across a project, its contents are only actually included once. This removes an entire, historically annoying category of C header-management bugs, and it's specifically why virtually all Objective-C code uses #import for its own and Foundation's headers, reserving plain #include mainly for genuinely plain-C headers where that's the established convention.

One practical consequence: because #import guarantees single-inclusion automatically, Objective-C header files (.h files) you write later in this course don't need old-style manual include guards the way C headers traditionally do — you'll still sometimes see them out of habit or cross-language caution, but they're functionally redundant once every file consistently uses #import.

• #import is a preprocessor directive, resolved via textual inclusion before compilation, just like #include. • Unlike #include, #import automatically prevents the same file from being included more than once, anywhere in the project. • This eliminates the need for manual include-guard boilerplate (#ifndef/#define/#endif) that plain C headers traditionally require. • Convention: use #import for Objective-C/Foundation headers; #include is still seen for genuinely plain-C-only headers.

Foundation: The Base Framework Beneath Everything

<Foundation/Foundation.h> is the master header for the Foundation framework — importing it pulls in declarations for essentially all of Foundation's core classes and functions at once: NSString, NSArray, NSDictionary, NSNumber, NSLog, and dozens more, all in one line. As the previous lesson established, Foundation is not part of the Objective-C language itself; it's a separate, pre-built framework, almost universally used alongside the language, that supplies the everyday data types and utility functions Objective-C code actually works with day to day. Without this import, none of NSLog, NSString, or the @"..." literal syntax used later in this same program would be recognized by the compiler — you'd get errors about undeclared identifiers, exactly the same category of error you'd get in plain C for using printf without #include <stdio.h>.

It's worth noting that Foundation exists on every Apple platform (macOS, iOS, watchOS, tvOS) with essentially the same core API, and a cross-platform open-source reimplementation, GNUstep (with its own Foundation-compatible libraries), lets Objective-C code using Foundation run on Linux and other non-Apple systems too — which is part of why this course's example programs, compiled with plain clang from the command line, work the way they do outside of Xcode entirely. Whatever platform you eventually target, this same #import line, pulling in the same core Foundation classes, will be at the top of nearly every Objective-C source file you write in this course from here forward.

• Foundation.h is Foundation's master header, pulling in declarations for NSString, NSArray, NSNumber, NSLog, and most everyday Objective-C classes at once. • Without importing it, the compiler has no idea what NSLog or @"..." string literals even are. • Foundation exists across all Apple platforms with a consistent core API, and GNUstep provides a compatible implementation on Linux and elsewhere. • Almost every .m file you write in this course will start with this exact #import line.

main, argc, and argv: Inherited Straight from C

int main(int argc, const char * argv[]) { ... } is the program's entry point, and if it looks familiar, that's because it is exactly the same concept as C's main from the previous module, with the same role: the operating system loads the compiled executable, creates a process, and jumps to this function first. Everything about how main is found and invoked, how it fits into the compile/link/execute pipeline, and what its return value means (0 for success, nonzero for an error/failure code returned to the OS) is unchanged from plain C — Objective-C didn't touch this part of the language at all, exactly as the previous lesson's 'strict superset' point predicts.

This version of main takes two parameters instead of the parameterless int main(void) used in the C module's earliest examples: argc (argument count) and argv (argument vector). These let a program see the command-line arguments it was launched with — for example, running ./hello world foo would give this program argc = 3 and argv containing the three strings "./hello", "world", and "foo" in order. argc is a plain int counting how many arguments were passed (always at least 1, since the program's own invocation name counts as argv[0]), and argv is an array of C strings (const char *, plain C character pointers, not NSStrings) holding the actual argument text. This particular hello-world program never reads argc or argv, but they're included because this exact main signature is the standard, conventional way Xcode and command-line Objective-C tooling generate a new command-line tool's entry point, and because you will use them in later, more interactive lessons.

Nothing about argc or argv is Objective-C-specific in any way — they are pure C, taken completely unchanged from the C standard, which is worth pausing on: this is a direct, concrete illustration of the superset relationship from the previous lesson. Objective-C's entry point isn't a special new Objective-C construct; it's literally C's own main function, doing exactly what it always does, with Objective-C's object-oriented code simply living inside its body alongside (or instead of) plain C code.

• main is exactly C's entry point, unchanged: the OS loads the executable and jumps here first, and its int return value is the process's exit status. • argc (argument count) is an int; argv (argument vector) is an array of plain C strings (const char *), holding the command-line arguments. • argv[0] is conventionally the program's own invocation path/name, so argc is always at least 1. • argc and argv are pure, unmodified C — a direct example of Objective-C being a strict superset of C rather than a separate language.

@autoreleasepool: A First Look at a Memory-Management Scope

@autoreleasepool { ... } is the first genuinely Objective-C-specific piece of syntax in this program (the @ prefix marks it, along with @interface, @property, and other keywords you'll meet across this course, as belonging to Objective-C rather than to plain C). At an introductory level — this course has an entire dedicated Memory Management module later that will explain this properly and in full — an autorelease pool is a mechanism the Objective-C runtime uses to manage the lifetime of certain temporary objects: some objects, when created, are marked for deferred cleanup rather than immediate cleanup, and an autorelease pool is what actually performs that deferred cleanup, releasing those objects when the pool block ends (the closing }).

Wrapping your program's body in @autoreleasepool { ... } establishes exactly that kind of cleanup scope for the whole program, which is why it's the conventional, expected wrapper around the contents of main in essentially every command-line Objective-C program (and, in different but related forms, around event-loop iterations in full iOS/macOS apps). For now, the practical takeaway is simple and requires no deep memory-management theory yet: any Objective-C program's main function should wrap its body in @autoreleasepool { ... }, this is standard, idiomatic practice, and you'll understand precisely why and exactly what's happening inside that block once you reach the Memory Management module later in this course.

It's worth noting explicitly that @autoreleasepool is a block scope, syntactically similar to the curly-brace blocks you already know from C (an if-block, a for-loop body) — variables declared inside it follow the same block-scope rules from the C module: they exist only within that block and are not visible outside its closing brace. The addition here is purely the memory-management behavior layered on top of that familiar block-scoping concept, not a change to how scoping itself works.

• @autoreleasepool { ... } is Objective-C-specific syntax (note the @ prefix), establishing a memory-management scope. • It manages deferred cleanup for certain temporary objects, releasing them when the block ends -- explained fully in the later Memory Management module. • Convention: wrap the body of main in @autoreleasepool in essentially every command-line Objective-C program. • It is still an ordinary block scope for variable visibility purposes (like an if-block in C), with memory-management behavior layered on top.

NSLog: Formatted Logging, Related to but Distinct from printf

NSLog(@"Hello, SampleApp!"); is how this program actually produces visible output, and it deliberately echoes printf from the C module both in purpose and in its format-string-based calling convention — but it is not the same function, and the differences matter. NSLog is a Foundation function (declared in Foundation.h, which is exactly why importing Foundation was necessary) that accepts a format string, using the same family of %-based format specifiers you learned for printf (%d for int, %f for a floating-point number, and so on), plus one new specifier you haven't seen yet, %@, used specifically to print an Objective-C object by asking it to describe itself (covered properly once you've met real objects in the next module).

The most important behavioral difference from printf is where the output goes and what accompanies it: printf writes plain text to standard output (stdout), with nothing added beyond exactly what you asked it to print. NSLog writes to the console/standard error (stderr) and automatically prefixes every line with a timestamp and the process's name and ID, producing output that looks something like 2026-09-20 10:15:03.123 hello[1234:56789] Hello, SampleApp! rather than a bare Hello, SampleApp!. This makes NSLog considerably more useful for real debugging and diagnostic logging (you can see exactly when a log line fired and from which process), but means it's the wrong tool when you specifically need clean, unadorned program output — for that, plain C's printf (still fully available, since Objective-C is a strict superset of C) remains the right choice.

NSLog also automatically appends a newline to its output, so — unlike printf, where you must remember to write \n yourself — you never need to add one manually at the end of an NSLog format string. Getting comfortable with NSLog now, including this timestamp/process-prefix behavior and its automatic newline, will save real confusion the first time you look at real console output from an app and wonder why there's extra text surrounding the message you actually logged.

• NSLog is a Foundation function for formatted logging, using printf-style %-specifiers plus %@ for printing objects. • NSLog writes to the console/stderr and automatically prefixes each line with a timestamp and process name/ID; printf writes plain, unadorned text to stdout. • NSLog automatically appends a trailing newline; printf does not, which is why C code manually includes \n. • Use printf when you need clean, unadorned output; use NSLog for debugging/diagnostic logging, which is by far the more common case in real Objective-C code.

@"...": String Literals That Are Actual Objects

The text @"Hello, SampleApp!" inside the NSLog call is another @-prefixed piece of Objective-C-specific syntax: an Objective-C string literal. In plain C, "Hello, SampleApp!" (no @) is a plain C string — technically, an array of char values terminated by a null byte, exactly as covered in the C module. Prefixing a string literal with @ instead, @"Hello, SampleApp!", creates something categorically different: a real Objective-C object, specifically an instance of the class NSString, with its own methods you can send messages to (checking its length, converting its case, searching within it, and much more — covered in depth once you formally meet NSString in a dedicated later lesson).

This distinction — plain C string versus NSString object — is one of the most consequential 'gotchas' for anyone transitioning from pure C into Objective-C, and it's worth internalizing right now, in this very first program: "text" and @"text" are not interchangeable, do not have the same type, and cannot generally be passed to a function expecting the other. A plain C string is a const char * — a pointer to raw bytes, with no built-in methods of its own; you'd need separate C library functions (like strlen, strcpy) to do anything with it. An @"text" NSString is a full Objective-C object — you send it messages like [myString length] to ask its character count, rather than calling a separate free function on it.

NSLog specifically expects its format-string argument to be an NSString (an object), which is exactly why the format string in this program is written @"Hello, SampleApp!" with the @ prefix rather than plain "Hello, SampleApp!" — passing a plain C string where NSLog expects an NSString would be a type mismatch the compiler would flag. As a broad rule going forward in this course: whenever you're working with Objective-C's own APIs (Foundation, Cocoa, or your own classes), you'll almost always want @"..." NSString literals; plain, unprefixed "..." C strings remain useful specifically when you're calling directly into plain C functions that expect them (like certain low-level C library calls), which is a comparatively rare situation in typical day-to-day Objective-C application code.

• "text" (no @) is a plain C string: a const char * pointer to raw, null-terminated bytes, with no methods of its own. • @"text" (with @) is an Objective-C string literal: a real NSString object, with its own methods you send it messages to. • NSLog's format-string parameter expects an NSString, which is exactly why the string in this lesson's example is written with the @ prefix. • Use @"..." NSString literals for Objective-C/Foundation APIs; reserve plain "..." C strings for direct calls into plain C functions that specifically expect them.

Code Examples

plainCString is a const char * -- ordinary C, printed with printf and the %s specifier, exactly as covered in the C module. objcString is a real NSString object, created with the @ prefix, printed via NSLog's object-oriented %@ specifier. Mixing printf and NSLog like this in the same block is entirely legal, since Objective-C is a strict superset of C -- but notice each function is given the kind of string it actually expects. Expected result: I am a C string 2026-09-20 10:15:04.001 hello[1234:56790] I am an NSString object

Compiling Objective-C with clang follows the exact same preprocess-compile-link pipeline from the C module, with one addition: -framework Foundation tells the linker to link against the Foundation framework, since this program calls Foundation functions (NSLog) and uses Foundation classes (NSString) that must be resolved at link time, just as any external library dependency would be.

clang -framework Foundation main.m -o hello NSLog("Hello, SampleApp!"); // missing @ before the string

NSLog(@"Hello, SampleApp!"); // @ creates an NSString object

NSLog's format-string parameter expects an NSString object, not a plain C string. Omitting the @ prefix passes a const char * where an NSString is expected, a type mismatch the compiler will flag -- always prefix string literals passed to Objective-C/Foundation APIs with @.

Omitting @autoreleasepool around main's body skips the memory-management scope that Objective-C's conventions expect a program to establish. It's standard, idiomatic practice to wrap the entire body of main in @autoreleasepool, covered fully in the later Memory Management module.

// Forgetting -framework Foundation when compiling: $ clang main.m -o hello Undefined symbols for architecture ...: "_NSLog", "_OBJC_CLASS_$_NSString"

$ clang -framework Foundation main.m -o hello Importing Foundation.h only gives the compiler declarations (what NSLog and NSString are); the linker still needs to be told, separately, to actually link against the Foundation framework's compiled code, or you'll get linker errors ('undefined symbols') for every Foundation function or class you use, exactly the same category of error a missing library link would cause in plain C.

What key advantage does #import have over plain C's #include? A. #import compiles faster in every case B. #import automatically prevents the same file from being included more than once, without needing manual include guards C. #import can only be used with Foundation headers D. #import skips the preprocessor stage entirely

B. #import automatically prevents the same file from being included more than once, without needing manual include guards — #import builds automatic protection against double-inclusion directly into the directive, eliminating the need for the #ifndef/#define/#endif include-guard boilerplate traditionally required by plain C headers using #include.

Which parts of int main(int argc, const char * argv[]) { ... } are Objective-C-specific additions, versus inherited unchanged from C? A. argc and argv are new Objective-C additions; main itself is from C B. main, argc, and argv are all unchanged, pure C -- none of it is Objective-C-specific C. main is Objective-C-specific; argc and argv are from C D. All three are entirely new to Objective-C

B. main, argc, and argv are all unchanged, pure C -- none of it is Objective-C-specific — main as the program's entry point, along with its argc/argv parameters, are exactly C's own concepts, completely unmodified by Objective-C -- a direct illustration of Objective-C being a strict superset of C rather than a new, separate language.

What is the key difference between the plain C string "text" and the Objective-C string literal @"text"? A. There is no real difference; @ is purely cosmetic B. @"text" is slower to create than "text" but otherwise identical C. "text" is a const char * (raw bytes, no methods); @"text" is a real NSString object you can send messages to D. @"text" can only be used inside NSLog calls

C. "text" is a const char * (raw bytes, no methods); @"text" is a real NSString object you can send messages to — Plain "text" is a C string -- a pointer to raw, null-terminated character data with no methods of its own. @"text" creates an actual NSString object, an instance of a Foundation class with its own methods, which is why NSLog (expecting an NSString) requires the @ prefix.

How does NSLog's output differ from printf's output for the same message? A. They are functionally identical in every respect B. NSLog writes to the console/stderr with an automatic timestamp and process-name/ID prefix, plus an automatic trailing newline; printf writes plain, unadorned text to stdout with no automatic newline C. printf can print objects with %@ but NSLog cannot D. NSLog only works inside an @autoreleasepool block, while printf works anywhere

B. NSLog writes to the console/stderr with an automatic timestamp and process-name/ID prefix, plus an automatic trailing newline; printf writes plain, unadorned text to stdout with no automatic newline — NSLog automatically prepends a timestamp and process name/ID to every line and appends a trailing newline, and it writes to the console/stderr rather than stdout -- making it well suited for debugging/diagnostic logging, whereas printf gives you exactly, and only, the text you specify.

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, SampleApp!");
    }

    return 0;
}
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        const char *plainCString = "I am a C string";       // no @: plain char array
        NSString *objcString = @"I am an NSString object";   // @: real Objective-C object

        printf("%s\n", plainCString);       // printf works with plain C strings
        NSLog(@"%@", objcString);             // NSLog's %@ prints an object's description
    }
    return 0;
}
$ clang -framework Foundation main.m -o hello
$ ./hello
2026-09-20 10:15:03.123 hello[1234:56789] Hello, SampleApp!