Objective-C File Types: .h, .m, .mm, and How Projects Mix C, C++, and Objective-C
Identify and explain the purpose of each Objective-C-related file extension (.h, .m, .mm) alongside plain C and C++ files (.c, .cpp), and understand how a single real-world project can freely mix all five file types together.
So far, this course's examples have all lived in a single file, main.m, but real Objective-C projects are organized across multiple files with different extensions, each signaling something specific to both the compiler and to any human reading the project's file listing. This lesson is a practical, structural one: it walks through every file extension you'll encounter in Objective-C development — .h, .m, .mm — and places them alongside the plain .c and .cpp files you may already have from other work, so that when you open a real Xcode project (or this course's later, larger multi-file examples) and see a mixture of these extensions sitting side by side in the same file list, you'll immediately understand what each one is for and why a project would need more than one kind.
.h — Header Files: Declarations Shared Across Files
A .h file is a header file, and the concept is carried over directly from C: a header contains declarations — what a class, function, or constant is, and how it's used — without necessarily containing the actual implementation code that carries it out. In plain C, you might have written a header declaring a function's signature (int addNumbers(int a, int b);) so that other .c files could #include it and call that function, even though the function's actual body lived in a separate .c file. Objective-C headers extend this exact idea to classes: a .h file typically contains an @interface block (covered in depth starting in the next module) declaring a class's name, its properties, and the methods it responds to — the class's public 'menu' of capabilities — without the actual code that implements each method.
The reason this separation matters, in both C and Objective-C, is compilation independence and information hiding: other files that need to use a class only need to #import its header, seeing what the class can do, without needing (or being allowed) to see or recompile the actual implementation code every time it changes internally. This lets large projects compile faster (changing a method's internal implementation doesn't require recompiling every file that merely uses that class, only the one file that implements it) and lets you deliberately expose only what you intend calling code to see, keeping internal implementation details out of the public declaration. A .h file, by itself, is never compiled directly into an object file or executable — it exists purely to be imported/included by other files.
In a typical Objective-C class, you'll pair one .h file with one implementation file (a .m or .mm file, covered next) sharing the same base name — Account.h alongside Account.m — a one-to-one naming convention so strong in practice that browsing a project's file list, you can usually assume every .h file has a corresponding implementation file of the exact same name sitting right beside it, and vice versa.
• A .h file is a header: declarations (what a class/function is and how it's used), typically without full implementation code. • Objective-C headers commonly declare a class's public interface (@interface block: properties and method signatures) covered starting next module. • Separating declaration (.h) from implementation lets other files depend only on the public interface, improving compile times and hiding internal details. • A .h file is never compiled directly into an executable on its own -- it exists to be imported by other files. • Convention: one .h paired with one same-named .m or .mm implementation file (Account.h + Account.m).
.m — Objective-C Implementation Files
A .m file is where actual Objective-C implementation code lives — the real, executable body of a class's methods, or, as in this course's earlier hello-world example, a plain main function with Objective-C code inside it. The .m extension is what tells clang 'compile this file understanding Objective-C's syntax' — message sends in square brackets, @interface/@implementation blocks, @"..." string literals, and every other @-prefixed keyword this course covers. A .m file typically pairs with a same-named .h file: the header declares what the class can do, and the .m file contains an @implementation block providing the actual method bodies that carry those declarations out.
Because Objective-C is a strict superset of C, a .m file can also contain any amount of plain C code with zero object-oriented syntax anywhere in it — a .m file with no @ symbols at all, containing only C functions, structs, and loops, is completely valid and compiles exactly as C would, just through clang's Objective-C-aware front end rather than being rejected for 'not being Objective-C enough.' This is precisely the mixing this course's earlier lessons demonstrated: a single .m file freely combining C functions like squareOf with Objective-C message sends and @"..." literals in the very same function body.
Practically, .m is by far the most common file extension you'll see across an Objective-C project's file list, since it's where essentially all of your actual class implementations, along with your program's entry point (main), live.
• A .m file is where Objective-C implementation code actually lives -- method bodies, @implementation blocks, and typically main. • .m tells clang to compile understanding full Objective-C syntax: message sends, @interface/@implementation, @-string literals, etc. • A .m file can also contain pure, unmixed plain C code with zero @ symbols -- fully valid, since Objective-C is a strict superset of C. • .m is the most common extension across a typical Objective-C project, holding the bulk of a program's real implementation code.
.mm — Objective-C++ Files: Mixing Objective-C and C++
A .mm file is an Objective-C++ file — it tells clang to compile understanding both Objective-C's message-passing syntax and C++'s object-oriented syntax (classes, templates, operator overloading, and the rest of C++) in the very same file, letting the two coexist and even directly interoperate. This is a genuinely different situation from .m (Objective-C, a superset of plain C only) and from plain .cpp (C++, a superset of plain C only, but with no Objective-C support at all) — .mm specifically exists for the case where a project needs both object systems available simultaneously, in the same translation unit.
Why would a real project need this? The most common real-world reason is bridging: a team has an existing C++ codebase — a cross-platform game engine, a scientific computing library, a piece of shared logic that also needs to run on Android or Windows where C++ but not Objective-C is available — and they want to call into that C++ code from an iOS or macOS app written primarily in Objective-C. Renaming the specific .m files that need to touch that C++ code to .mm lets those files #include C++ headers, instantiate C++ classes, and call C++ functions directly, right alongside ordinary Objective-C message sends to NSString or NSArray in the same function, without an intermediate translation layer.
A .mm file does not require you to actually use both languages' object systems together — a .mm file containing only Objective-C code compiles exactly as if it were .m, and a .mm file containing only C++ code compiles exactly as if it were .cpp. The extension simply grants permission for both, at the compiler level; you use exactly as much of that permission as any given file actually needs. In practice, teams tend to keep the number of .mm files in a project deliberately small and localized — often just a handful of dedicated 'bridge' files — specifically so that Objective-C++'s combined complexity doesn't spread unnecessarily across a whole codebase that otherwise doesn't need it.
• .mm is an Objective-C++ file: clang compiles it understanding both Objective-C (message sends, @interface) and full C++ (classes, templates) at once. • Common real reason: bridging an existing C++ codebase (engine, shared library) into an Objective-C app without an intermediate translation layer. • A .mm file behaves exactly like .m if it uses no C++, and exactly like .cpp if it uses no Objective-C -- the extension just permits both. • Convention: keep .mm files few and localized to dedicated 'bridge' files rather than spreading Objective-C++ across an entire project.
.c and .cpp — Plain C and Plain C++ Still Belong Too
Nothing about adopting Objective-C requires abandoning plain .c or .cpp files entirely, and real projects routinely keep both around for good reasons. A .c file is compiled as plain C, with clang applying strict C rules and no awareness of Objective-C's @-syntax at all — you'd reach for this when a piece of code is genuinely platform-independent, low-level C with no reason to touch objects (a math routine, a data-parsing algorithm shared with a non-Objective-C project), and you want the compiler to enforce that it stays that way, catching any accidental Objective-C syntax as an error rather than silently allowing it.
A .cpp file is compiled as plain C++ — full C++ object-oriented and template features, but again with no Objective-C awareness. This is the natural home for C++ code that has no reason to ever touch Objective-C directly: a self-contained algorithm, a third-party C++ library vendored into the project unchanged, or a piece of shared cross-platform logic maintained separately from the Objective-C-specific application layer. Code in a .cpp file that needs to eventually be called from Objective-C typically goes through a .mm bridge file, rather than the .cpp file itself being renamed — keeping the boundary between 'plain C++ shared logic' and 'Objective-C application code' clear and deliberate.
Put together, a single well-organized project can, and often does, contain all five of these file types side by side: .h headers declaring interfaces, .m files implementing the bulk of the Objective-C application logic, a small number of .mm bridge files connecting to C++, .cpp files holding self-contained C++ logic or libraries, and .c files holding self-contained plain-C logic or libraries. Clang compiles each one according to its extension — this is the mechanism, not a coincidence or a convention you have to enforce by hand — and the linker, exactly as covered back in the C module's pipeline lesson, stitches the resulting object files together into one final executable, regardless of which combination of languages produced each individual piece.
• .c files compile as strict plain C, with no Objective-C @-syntax recognized -- good for genuinely platform-independent, object-free code. • .cpp files compile as plain C++, with no Objective-C awareness -- good for self-contained C++ logic or vendored C++ libraries. • C++ code that needs to be called from Objective-C typically goes through a dedicated .mm bridge file, rather than converting the .cpp file itself. • A single project can freely mix .h, .m, .mm, .c, and .cpp files; clang compiles each per its extension, and the linker unifies the resulting object files into one executable.
Choosing the Right Extension in Practice
As a practical rule of thumb for organizing your own files going forward in this course: default to .h + .m pairs for essentially everything — every class you write, and your program's main, should live in this pairing unless you have a specific, concrete reason to reach for something else. Reach for .mm only for the specific files that genuinely need to call C++ code directly from Objective-C code in the same file — and keep that set of files as small as the actual bridging need requires, rather than converting files to .mm preemptively 'just in case.' Reach for standalone .c or .cpp files specifically for logic that is deliberately meant to remain independent of Objective-C entirely — shared algorithms, vendored libraries, code you might reuse in a completely different, non-Objective-C project someday.
One more useful mental habit: the extension is a promise to the compiler and to anyone reading the file about what kind of syntax to expect inside it. Seeing .c tells you, before opening the file, 'no objects of any kind live in here.' Seeing .mm tells you 'this file specifically bridges Objective-C and C++, so expect both.' This is genuinely useful information conveyed by a filename alone, and it's part of why, even once you're comfortable with all these languages individually, disciplined use of the right extension for each file remains a real, practical habit worth maintaining rather than defaulting everything to the most permissive extension available.
• Default choice: .h + .m pairs for nearly everything -- classes and main, unless there's a concrete reason otherwise. • Reach for .mm only for the specific, minimal set of files that must bridge Objective-C and C++ directly. • Reach for standalone .c/.cpp for logic deliberately kept independent of Objective-C -- shared algorithms, vendored libraries. • A file's extension is a promise about what syntax to expect inside it -- disciplined use of the narrowest correct extension keeps a project easy to navigate.
Code Examples
Account.h declares that a class named Account exists, inherits from NSObject, and responds to a deposit: message -- but contains no actual implementation code for what deposit: does. Any file that wants to use Account only needs to #import this header, without seeing or depending on how deposit: is actually implemented.
Account.m imports its own header (using quotes rather than angle brackets, since it's a local project file rather than a system framework header) and provides the real method body inside an @implementation block. This file, not Account.h, is what clang actually compiles into machine code for the deposit: method. Expected result: (compiles to an object file; no output until deposit: is actually called elsewhere)
This is an ordinary C++ header, compiled as part of a .cpp file elsewhere in the project. It knows nothing about Objective-C and has no reason to -- it's exactly the kind of self-contained, language-independent logic that's a good candidate for staying in plain .cpp/.hpp files rather than being converted to Objective-C++.
Because this file has the .mm extension, clang compiles it understanding both languages at once: LegacyMath::square(value) is a direct C++ static method call (C++'s :: scope-resolution syntax), and NSLog(@"...") is an ordinary Objective-C message send, sitting in the very same method body with no translation layer needed between them.
// Account.h -- declares the interface, no implementation code
#import <Foundation/Foundation.h>
@interface Account : NSObject
- (void)deposit:(double)amount;
@end// Account.m -- provides the actual implementation
#import "Account.h"
@implementation Account
- (void)deposit:(double)amount {
NSLog(@"Deposited: %.2f", amount);
}
@end// LegacyMath.hpp -- a plain C++ header, no Objective-C awareness at all
class LegacyMath {
public:
static double square(double x);
};// MathBridge.mm -- Objective-C++: both languages in one file
#import <Foundation/Foundation.h>
#import "LegacyMath.hpp"
@interface MathBridge : NSObject
- (double)squareUsingLegacyMath:(double)value;
@end
@implementation MathBridge
- (double)squareUsingLegacyMath:(double)value {
double result = LegacyMath::square(value); // direct C++ call
NSLog(@"Result: %.2f", result); // Objective-C message send
return result;
}
@endHow Objective-C File Types Relate
A .h header commonly pairs with either a .m implementation file (pure Objective-C, itself a superset of plain C) or, when C++ interoperability is specifically needed, a .mm Objective-C++ file. Plain .c and .cpp files sit alongside these, deliberately independent of Objective-C, and a single project can freely mix all of them.
// A file named Account.m that tries to #include <vector> and use std::vector directly
// Rename the file Account.mm if it genuinely needs C++ standard library types like std::vector
A plain .m file is compiled as Objective-C (a superset of C only), with no C++ support -- attempting to use C++-only syntax like std::vector or class templates in a .m file produces compiler errors. If a file genuinely needs both Objective-C and C++, it must be renamed with the .mm extension.
// Converting every file in a project to .mm 'just in case it might need C++ someday'
// Keep files as .m by default; convert only the specific files that actually need to call C++ directly
Objective-C++ compilation is more complex than plain Objective-C, and unnecessarily broad use of .mm spreads that complexity across files that don't need it. Convention favors keeping .mm limited to a small, deliberate set of bridge files.
Angle brackets (<Foundation/Foundation.h>) tell the compiler to search system/framework include paths; quotes ("Account.h") tell it to look in the local project directory first. Using angle brackets for your own project's headers can cause the compiler to fail to find the file, or to find an unrelated file of the same name elsewhere.
What is the primary purpose of a .h file in an Objective-C project? A. It contains the actual, executable implementation code for a class's methods B. It declares a class's interface (properties and method signatures) without necessarily containing the implementation code C. It is only used for plain C code, never for Objective-C classes D. It is compiled directly into machine code independently of any .m file
B. It declares a class's interface (properties and method signatures) without necessarily containing the implementation code — A .h header declares what a class looks like from the outside -- its properties and method signatures -- typically without the actual method bodies, which live in a paired .m (or .mm) implementation file. A .h file alone is never compiled directly into an executable.
What specifically distinguishes a .mm file from a plain .m file? A. There is no real difference; .mm is just an alternate spelling of .m B. .mm files cannot contain any Objective-C syntax at all C. .mm tells clang to compile understanding both Objective-C and full C++ syntax in the same file, enabling direct interoperability between them D. .mm files are only used for header declarations, never implementations
C. .mm tells clang to compile understanding both Objective-C and full C++ syntax in the same file, enabling direct interoperability between them — The .mm extension marks a file as Objective-C++: clang compiles it recognizing both Objective-C's message-passing syntax and C++'s object-oriented features (classes, templates) simultaneously, which is specifically useful for bridging existing C++ code into an Objective-C project.
Why might a project deliberately keep some logic in plain .c or .cpp files rather than converting everything to .m or .mm? A. Because .c and .cpp files compile faster in every single case, with no exceptions B. Because Objective-C cannot call functions from plain C or C++ files under any circumstances C. To keep code that is deliberately meant to remain platform-independent or reusable outside Objective-C free of any Objective-C-specific syntax D. Because .c and .cpp files are required by Apple's App Store submission rules
C. To keep code that is deliberately meant to remain platform-independent or reusable outside Objective-C free of any Objective-C-specific syntax — Keeping platform-independent or reusable logic (shared algorithms, vendored libraries) in plain .c/.cpp files, untouched by Objective-C syntax, preserves its ability to be reused in non-Objective-C contexts, with .mm bridge files providing the connection point when Objective-C code needs to call into it.
A .mm file that happens to contain only Objective-C syntax and no C++ at all will: A. Fail to compile, since .mm requires actual C++ code to be present B. Compile exactly as if it were a plain .m file -- the .mm extension only grants the option to use C++, it doesn't require it C. Automatically be treated as a plain .c file instead D. Produce a warning on every line for 'unused C++ permission'
B. Compile exactly as if it were a plain .m file -- the .mm extension only grants the option to use C++, it doesn't require it — The .mm extension simply permits both Objective-C and C++ syntax in the same file; a .mm file using none of C++'s features compiles exactly as a .m file would, and one using none of Objective-C's features compiles exactly as a .cpp file would.
.h
│
├── .m → Objective-C
│
└── .mm → Objective-C + C++