Modules, Frameworks and Code Organization

This section documents modern Objective-C as implemented by Apple Clang in the current Xcode release — Objective-C 2.0 plus ARC, literals and subscripting, instancetype, Clang modules, lightweight generics, nullability and @available — as published at Apple Developer Documentation and the Clang Objective-C specifications, which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against developer.apple.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Objective-C has no namespaces and no module system of its own — its unit of organisation is the header, and its unit of distribution is the framework. Clang modules, added later, fix the worst of the textual-include model without changing the language. Good organisation in Objective-C is therefore mostly a matter of header hygiene and naming discipline.

#import, #include and @import

Directive What it does

#include "file.h"

C’s textual inclusion. Includes the file every time; needs include guards.

#import "file.h"

The same, but never includes a file twice. The default for Objective-C headers. No guards needed.

@import Foundation;

Loads a precompiled Clang module. Not textual at all — it imports a semantic description of the framework.

#import <Foundation/Foundation.h>       // a framework umbrella header
#import "MyClass.h"                     // a project header
#include <math.h>                       // a plain C header (#import also works)

@import Foundation;                     // the modular form
@import UIKit;
@import Foundation.NSString;            // a submodule, if you want just a part

#import remains correct everywhere and is what most code still uses. Where modules are enabled (-fmodules, on by default in Xcode), Clang silently converts a framework #import into a module import anyway, so the practical difference is small.

The Header-Import Discipline

Two rules save more compile time and prevent more circular-dependency problems than anything else:

// In a HEADER: forward-declare whenever only the name is needed.
@class Customer;
@protocol Payable;

@interface Order : NSObject
@property (nonatomic, strong) Customer *customer;
@property (nonatomic, weak) id<Payable> payer;
@end
// In the IMPLEMENTATION: import the real headers.
#import "Order.h"
#import "Customer.h"
#import "Payable.h"

You must #import in the header only when you actually need the declaration there: inheriting from the class, adopting the protocol, using an enum or typedef, or embedding a struct by value.

Clang Modules

A module is a precompiled, self-contained description of a library’s interface. It is parsed once and reused, rather than re-read as text in every translation unit — which is both dramatically faster and immune to the order-dependence and macro leakage of textual includes.

@import Foundation;              // instead of #import <Foundation/Foundation.h>
@import CoreLocation;

Benefits worth knowing:

  • Build speed — the module is compiled once per configuration, not once per file.

  • Isolation — one header’s #define cannot alter how the next one parses.

  • Automatic linking — importing a module links its library, so no manual -framework flag is needed.

module.modulemap

A module map tells Clang what a module contains. For your own framework:

framework module MyFramework {
    umbrella header "MyFramework.h"

    export *
    module * { export * }
}

A module map for a plain (non-framework) library:

module MyLibrary {
    umbrella header "MyLibrary.h"
    export *

    explicit module Private {        // opt-in submodule
        header "MyLibraryPrivate.h"
        export *
    }
}

Xcode generates a module map for a framework target automatically when DEFINES_MODULE = YES; you write one by hand mainly to modularise a third-party C library.

Umbrella Headers

The umbrella header is a framework’s front door: it imports every public header, so one #import brings in the whole API.

// MyFramework.h -- the umbrella header
#import <Foundation/Foundation.h>

//! Project version number for MyFramework.
FOUNDATION_EXPORT double MyFrameworkVersionNumber;
FOUNDATION_EXPORT const unsigned char MyFrameworkVersionString[];

#import <MyFramework/Person.h>
#import <MyFramework/Downloader.h>
#import <MyFramework/MyFrameworkTypes.h>

Clang warns if a public header is missing from the umbrella (umbrella header does not include header …) — that warning is worth treating as an error, because a header outside the umbrella is unreachable through the module.

Frameworks

A framework is a bundle containing a library, its public headers, its module map and its resources.

Static framework / library Dynamic framework

Linked

At build time, code copied into the binary

At launch, loaded from the bundle

App size

Only the symbols actually used

The whole framework

Launch time

No cost

Small per-framework cost

Shared between targets

A separate copy in each

One copy, shared (app + extension)

Resources

Not bundled (needs a separate resource bundle)

Bundled with the code

Objective-C categories

Need -ObjC (see below)

Work normally

Prefer dynamic frameworks when the code is shared between an app and its extensions, or when the framework carries resources. Prefer static when launch time matters more than binary size and the framework is used by exactly one target.

Anatomy of a Framework Bundle

flowchart TD F["MyFramework.framework
the bundle directory"] F --> B["MyFramework
the binary: compiled code"] F --> H["Headers/"] F --> P["PrivateHeaders/"] F --> M["Modules/"] F --> R["Resources/"] F --> I["Info.plist
identifier, version, min OS"] H --> H1["MyFramework.h
umbrella header
imports every public header"] H --> H2["Person.h
Downloader.h
Public headers — your API surface"] P --> P1["Downloader+Internal.h
Private: reachable but unsupported"] M --> M1["module.modulemap
names the umbrella header,
enables @import MyFramework;
"] M --> M2["MyFramework.swiftmodule
present if the target contains Swift"] R --> R1["Assets, .nib/.storyboard,
.strings, .bundle
dynamic frameworks only"] N["Project-visibility headers
(most of them)"] -.->|"not copied
into the bundle"| F style H1 fill:#dce9f7,stroke:#2f6fa8 style H2 fill:#dce9f7,stroke:#2f6fa8 style M1 fill:#dbe9d5,stroke:#4a7a3a style P1 fill:#f6e9cf,stroke:#b08a34 style N fill:#eeeeee,stroke:#999999

The three header roles in the next section map directly onto this layout: Public headers land in Headers/ and must appear in the umbrella header, Private headers land in PrivateHeaders/, and Project headers are not copied at all.

The -ObjC Flag

A classic failure: an app links a static library, calls a category method, and crashes with unrecognized selector. The reason is that a category adds no new linker symbol, so the object file containing it looks unused and is dropped.

# Force every Objective-C class and category from static libraries to be linked in.
-ObjC

# The bigger hammer (loads every object file, including non-Objective-C ones):
-all_load
-force_load /path/to/libMyLibrary.a

Add -ObjC to Other Linker Flags whenever you link a static library containing categories. See Categories and Extensions.

Header Visibility

Xcode’s Build Phases ▸ Headers sorts each header into one of three roles:

Role Meaning

Public

Copied into the framework bundle and reachable by clients. Must be listed in the umbrella header. This is your API surface — changing it is a breaking change.

Private

Copied into the bundle’s PrivateHeaders directory. Reachable, but marked as "unsupported, may change". Use for headers other targets you own need, but third parties should not.

Project

Not copied at all. Visible only within the framework’s own compilation. The default, and the right choice for most headers.

The corresponding language-level tools:

// Class extension in the .m -- entirely private (see Properties and Encapsulation)
@interface Downloader ()
@property (nonatomic, strong) NSURLSession *session;
@end

// A "+Internal" header for things other files in the framework need, kept Project-visibility
// Downloader+Internal.h
@interface Downloader (Internal)
- (void)beginNextChunk;
@end

Attribute-level visibility also exists for C symbols:

FOUNDATION_EXPORT NSString * const MyPublicConstant;              // exported
__attribute__((visibility("hidden"))) void myInternalFunction(void);

Public API Hygiene

Two annotations belong on nearly every public class, because they convert conventions into compiler-checked rules:

NS_ASSUME_NONNULL_BEGIN

@interface Downloader : NSObject

/// The designated initializer. Subclasses must chain to this one.
- (instancetype)initWithSession:(NSURLSession *)session NS_DESIGNATED_INITIALIZER;

/// A Downloader cannot be created without a session.
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new  NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END

NS_DESIGNATED_INITIALIZER makes Clang verify that every subclass’s designated initializer chains to it and that convenience initializers call self, not super. NS_UNAVAILABLE removes an inherited initializer that would leave the object in an invalid state — a far better failure than a run-time assertion. See Classes and Objects.

Round out a public header with NS_ASSUME_NONNULL_BEGIN/END, lightweight generics, API_AVAILABLE where relevant, and doc comments — together these are what make the API import cleanly into Swift, per Swift Interoperability.

@compatibility_alias

Gives an existing class a second name, which the compiler resolves transparently:

@compatibility_alias MYLegacyPerson Person;

// Old code continues to compile unchanged:
MYLegacyPerson *p = [[MYLegacyPerson alloc] init];    // really a Person

It is a renaming aid: alias the old name to the new class and existing call sites keep working while you migrate. Note that it affects the compiler only — NSStringFromClass still reports the real name, and NSClassFromString(@"MYLegacyPerson") returns Nil.

Organising a Class Across Files

A class too big for one file can be split with categories, each in its own pair:

MyViewController.h                  // the public interface
MyViewController.m                  // lifecycle and core behaviour
MyViewController+TableView.h/.m     // UITableViewDataSource/Delegate conformance
MyViewController+Networking.h/.m    // request handling
MyViewController+Internal.h         // shared internals, Project visibility

Within a file, #pragma mark provides structure in Xcode’s jump bar at no cost:

#pragma mark - Lifecycle
- (instancetype)init { … }
- (void)dealloc { … }

#pragma mark - Public API
- (void)start { … }

#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)s { … }

#pragma mark - Private
- (void)recomputeLayout { … }

The leading - draws a separator. A consistent ordering — lifecycle, public API, protocol conformances grouped by protocol, then private helpers — makes any file in the project navigable the same way.

A note of caution: splitting a class across categories cannot move state, since categories cannot add ivars. If the split is mostly about state rather than behaviour, the class probably wants decomposing into collaborating objects instead. See Categories and Extensions.

Class Prefixes as Namespaces

Objective-C has one flat global namespace for class names, and two classes with the same name in one process is undefined behaviour — the runtime logs "Class X is implemented in both …" and picks one arbitrarily. Prefixes are the only defence.

Scope Convention

Apple’s frameworks

Two letters: NS, UI, CG, CA, AV, MK, SK. Reserved — never use a two-letter prefix of your own.

Your library or framework

Three or more letters, ideally distinctive: MYKPerson, ACMEDownloader.

An application (not a library)

A prefix is optional but still helpful.

Category methods

A lowercase prefix plus underscore on every method: my_trimmedString.

C functions, constants, typedefs

The same prefix: MYKMakeRange, MYKErrorDomain.

// A library's public surface, consistently prefixed:
@interface MYKDownloader : NSObject
@end

typedef NS_ENUM(NSInteger, MYKDownloadState) {
    MYKDownloadStateIdle,
    MYKDownloadStateRunning
};

extern NSErrorDomain const MYKErrorDomain;
extern NSTimeInterval MYKDefaultTimeout(void);

Swift has real modules, so a Swift class needs no prefix — and @objc(MYKAnalytics) is how you give a Swift class an Objective-C-safe name when it is exposed. That is the one context in which prefixes are gradually disappearing.

See Also