Basic Syntax and Types
|
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, 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’s lexical layer is C’s: the same tokens, the same comments, the same identifier rules, the same
preprocessor. What the language adds on top is a set of @-prefixed directives and a handful of typedefs and
object types that the Foundation framework and the run time depend on.
Tokens, Comments and Identifiers
Comments are C’s: // to end of line and /* … */ for blocks. Identifiers are case-sensitive, start with a
letter or underscore, and by convention are camelCase for variables and methods, UpperCamelCase for types.
Because Objective-C has no namespaces, class names are conventionally prefixed with two or three letters
identifying the framework or project — NS (Foundation/AppKit, historically NeXTSTEP), UI (UIKit), CG
(Core Graphics). Prefixes are a convention enforced only by discipline; see
Coding Conventions and Style.
Every C keyword is a keyword here too. Objective-C’s own additions are spelled with a leading @ precisely so
they cannot collide with an existing C identifier. For C’s own token grammar, identifier rules, comment forms
and the full keyword list, see
C Reference — Lexical Structure and Style.
The @ Directives
| Directive | Purpose |
|---|---|
|
Declare and define a class or category. |
|
Declare a protocol (or, in an expression, produce a |
|
Declare a property. |
|
Control accessor generation for a property. |
|
Forward-declare a class name without importing its header. |
|
Instance-variable visibility. |
|
Mark the following protocol methods as mandatory or not. |
|
Produce a |
|
Produce the run-time type-encoding string for a type. |
|
Object literals and boxing. |
|
Exception handling. |
|
Recursive mutex around a block, keyed on an object. |
|
Bound the lifetime of autoreleased temporaries. |
|
Run-time OS-version check. |
|
Import a Clang module (rather than textually including a header). |
|
Give an existing class a second name. |
|
Legacy/rare; |
The C Scalar Types, in Brief
Objective-C inherits C’s types unchanged: char, short, int, long, long long and their unsigned
variants; float, double, long double; _Bool; and the <stdint.h> fixed-width types (int32_t,
uint64_t). Sizes are implementation-defined in exactly the C way — on Apple’s 64-bit platforms int is 32
bits, long and pointers are 64 bits.
Pointers, arrays, struct, union, enum and function pointers all behave as in C. That material is C’s, not
Objective-C’s, and is summarised here only far enough to read the examples in this section; the
Objective-C-specific consequences are what the pages below foreground. For the C detail in depth, go to the
C Reference:
-
Basic Types and Values — the scalar types, their representations,
<stdint.h>, and the promotion and conversion rules. -
Arrays and Strings — array declaration, decay to pointers, and NUL-terminated
charstrings. -
Structures, Unions and Type Aliases —
struct,union, bit-fields,typedef, alignment and padding. -
Pointers — pointer arithmetic,
void *,constplacement and function pointers.
The practical advice is simple and it is Apple’s: in Objective-C code, prefer the Foundation-defined typedefs below to raw C types when the value crosses an API boundary, because they are defined to widen correctly on 64-bit platforms.
Objective-C’s Own Types
BOOL, YES and NO
BOOL is Objective-C’s Boolean. Historically it was signed char with YES as 1 and NO as 0; on
64-bit Apple platforms it is the C _Bool. Two consequences matter:
BOOL flag = YES;
if (flag) { /* … */ }
// Do NOT compare against YES: on a signed-char BOOL any non-zero value is "true",
// but only the exact value 1 equals YES.
if (flag == YES) { /* fragile */ }
if (flag) { /* correct */ }
// A pointer is not a BOOL, but it is usable as a truth value, as in C:
NSString *s = nil;
if (s) { /* not reached: nil is 0 */ }
Never store a value wider than a BOOL into one — (BOOL)256 truncates to NO under the signed char
representation.
NSInteger, NSUInteger and CGFloat
These are width-adaptive typedefs: 32 bits on a 32-bit platform, 64 bits on a 64-bit one. Use them for sizes, counts, indexes and coordinates in API signatures.
NSUInteger count = [array count]; // counts and indexes are NSUInteger
NSInteger offset = -3; // signed positions
CGFloat width = 42.5; // double on 64-bit, float on 32-bit
// Format specifiers must match the width. The portable spelling casts explicitly:
NSLog(@"count = %lu", (unsigned long)count);
NSLog(@"offset = %ld", (long)offset);
Mismatched NSLog/printf specifiers are the single most common warning in Objective-C code; -Wall -Wextra
catches them.
NSNotFound (an NSInteger equal to NSIntegerMax) is the conventional "no such index" sentinel returned by
searches such as indexOfObject:. Test for it explicitly — it is not -1.
id, Class, SEL and IMP
These four are the run time’s own vocabulary.
| Type | Meaning |
|---|---|
|
A pointer to any Objective-C object. Already a pointer — write |
|
A pointer to a class object. |
|
A selector — the run time’s interned name for a method, e.g. |
|
A function pointer to a method implementation: |
id anything = @"a string"; // any object
Class cls = [anything class]; // NSString (in practice __NSCFConstantString)
SEL sel = @selector(length);
IMP imp = [anything methodForSelector:sel];
NSUInteger len = ((NSUInteger (*)(id, SEL))imp)(anything, sel); // direct call, no dispatch
Class and SEL are covered in depth in
Messaging and Selectors and
The Objective-C Runtime.
instancetype
instancetype is a contextual keyword meaning "an instance of the receiving class". It exists so that
initializers and factory methods return a correctly typed result in subclasses:
@interface Shape : NSObject
+ (instancetype)shape; // in Circle, this returns Circle *
+ (id)legacyShape; // returns id -- no type checking at the call site
@end
Circle *c = [Circle shape]; // type-checked: the compiler knows this is a Circle *
Circle *d = [Circle legacyShape]; // compiles, but any mistake goes unnoticed
Always use instancetype — never id — as the return type of init… and of convenience constructors.
nil, Nil, NULL and NSNull
Four spellings of "nothing", each with its own domain. Confusing them is a classic beginner’s error:
| Spelling | Type | Use for |
|---|---|---|
|
|
A null object pointer. |
|
|
A null class pointer. |
|
|
A null C pointer (including |
|
object |
A real, non-null object standing in for "no value" inside a collection. |
The last one exists because Foundation collections cannot store nil — inserting nil into an NSArray
raises an exception, and nil also terminates the legacy arrayWithObjects: argument list. [NSNull null]
is a singleton placeholder:
NSArray *withHole = @[ @"a", [NSNull null], @"c" ]; // a three-element array
id value = withHole[1];
if (value == [NSNull null]) {
NSLog(@"explicitly no value here");
}
The defining property of nil, and one Objective-C leans on constantly, is that sending a message to nil
is legal and does nothing, returning zero/nil/a zeroed struct. That is why Objective-C code contains far
fewer null guards than C++ or Java code; see
Messaging and Selectors.
Variables, Constants and Literals
Declaration syntax, storage classes, the C literal forms and initializer rules are C’s; see C Reference — Constants, Enumerations and Initialization and Storage Duration, Scope and Linkage. What follows is how the C forms sit alongside Objective-C’s object literals.
// C literals work unchanged
int i = 42;
double d = 3.14159;
char c = 'x';
const char *cstr = "a C string"; // NUL-terminated bytes, NOT an NSString
// Objective-C object literals
NSString *s = @"an NSString"; // compile-time constant string object
NSNumber *n = @42; // boxed int
NSNumber *b = @YES; // boxed BOOL
NSNumber *e = @(i * 2); // boxed expression
NSArray *arr = @[ @"a", @"b" ]; // NSArray literal
NSDictionary *dict = @{ @"key": @"value" }; // NSDictionary literal
Constants come in two flavours, and the object-typed one is preferred in headers because it has a single
address that can be compared with ==:
// In the header -- declared, not defined:
extern NSString * const MyLibraryErrorDomain;
extern const NSTimeInterval MyLibraryDefaultTimeout;
// In the implementation -- defined once:
NSString * const MyLibraryErrorDomain = @"com.example.MyLibrary";
const NSTimeInterval MyLibraryDefaultTimeout = 30.0;
Note the placement of const: NSString * const name is a constant pointer to an (immutable) string, which
is what you want. const NSString *name is a pointer to a const NSString and produces awkward warnings at
every use.
Prefer these to #define for values: they are typed, visible to the debugger, and do not leak through
headers as text.
typedef, NS_ENUM and NS_OPTIONS
Plain C typedef and enum are available, but Foundation’s two macros are what Objective-C APIs actually
use. They give the enumeration a fixed underlying type, which both keeps the ABI stable and lets Swift import
the type properly.
// A set of mutually exclusive values.
typedef NS_ENUM(NSInteger, MyPlayerState) {
MyPlayerStateStopped = 0,
MyPlayerStatePlaying,
MyPlayerStatePaused
};
// A bit-mask of independently combinable flags.
typedef NS_OPTIONS(NSUInteger, MyLayoutOptions) {
MyLayoutOptionsNone = 0,
MyLayoutOptionsAlignLeft = 1 << 0,
MyLayoutOptionsAlignRight = 1 << 1,
MyLayoutOptionsWrap = 1 << 2
};
MyLayoutOptions opts = MyLayoutOptionsAlignLeft | MyLayoutOptionsWrap;
if (opts & MyLayoutOptionsWrap) { /* … */ }
Two conventions carry real weight. Name each constant with the type name as its prefix
(MyPlayerStatePaused, not Paused) — there being no namespaces, this is what keeps the global identifier
space navigable. And switch over an NS_ENUM without a default: clause: the compiler will then warn about
any case you forget to handle, which is the main practical benefit of using the macro at all.
NS_ENUM imports into Swift as an enum, NS_OPTIONS as an OptionSet. Two related macros, NS_STRING_ENUM
and NS_ERROR_ENUM, give string constants and error codes the same treatment — see
Errors and Exceptions.
Casts and Conversions
The usual arithmetic conversions, integer promotion and C’s explicit cast syntax all apply unchanged — see C Reference — Basic Types and Values for the conversion ladder in full. Two Objective-C-specific notes:
-
Object casts are unchecked.
(NSString *)someObjecttells the compiler to stop complaining; it performs no run-time verification. Guard withisKindOfClass:when the type is genuinely uncertain. -
Casting between an Objective-C object and a CoreFoundation type requires a bridging cast under ARC (
bridge,bridge_retained,__bridge_transfer), because ownership must be stated explicitly. See Automatic Reference Counting.
id maybe = [dict objectForKey:@"name"];
if ([maybe isKindOfClass:[NSString class]]) {
NSString *name = (NSString *)maybe; // now a safe cast
NSLog(@"%@", [name uppercaseString]);
}
Lightweight generics (NSArray<NSString *> *) narrow these casts at compile time without changing the run-time
representation — see
Lightweight Generics and
Nullability.
See Also
-
Operators, Control Flow and Functions — the rest of the C-level language as it is used from Objective-C.
-
Strings, Numbers and Values —
NSString,NSNumberandNSValuein depth. -
Messaging and Selectors —
SEL,IMPand messages tonil.