Operators, Control Flow and Functions
|
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. |
Operators, control flow and functions are C’s, inherited wholesale. What is worth writing down is the handful
of places where Objective-C’s object model changes how you use them: BOOL truthiness, messages appearing
inside conditions, switch over an NS_ENUM, and the division of labour between C functions and Objective-C
methods.
Operators
The full C operator set applies — arithmetic (+ - * / %), comparison (== != < > ⇐ >=), logical
(&& || !), bitwise (& | ^ ~ << >>), assignment and its compound forms, increment/decrement, the comma
operator, sizeof, the address-of and dereference operators, and the conditional operator.
Precedence, associativity and short-circuit evaluation are unchanged from C — for the precedence table, value categories and the sequencing rules (including where unsequenced side effects are undefined behavior), see C Reference — Operators and Expressions. Two operators deserve an Objective-C note:
-
==on objects compares pointer identity, not value.[a isEqual:b](orisEqualToString:,isEqualToArray:…) compares contents. This is the most frequent source of surprising bugs in the language.NSString *a = @"hello"; NSString *b = [NSString stringWithFormat:@"%@", @"hello"]; BOOL samePointer = (a == b); // NO -- two distinct objects BOOL sameValue = [a isEqualToString:b]; // YES -- same characters(Constant strings written
@"hello"in the same binary are often interned to one object, so==may accidentally appear to work. Do not rely on it.) -
Dot syntax is not the C struct-member operator when the receiver is an object.
view.frameon an object is sugar for[view frame]; on astructit is ordinary C member access. See Properties and Encapsulation.
BOOL Truthiness
Objective-C tests truth the way C does: zero is false, anything else is true. nil is zero, so a nil object
pointer is false, which makes the idiomatic guard very short:
NSString *name = [self lookupName];
if (name) { // non-nil
NSLog(@"%@", name);
}
if (![array count]) { // count == 0, or array is nil (nil returns 0)
return;
}
That second example shows a genuine subtlety: array being nil and array being empty take the same
branch, because a message to nil returns 0. Usually that is exactly what you want; when it is not, test
for nil separately.
Never compare a BOOL against YES (see
Basic Syntax and Types).
Control Flow
Every statement form is C’s, with C’s semantics; see
C Reference — Control Flow for the full treatment and
Advanced Control Flow for goto, setjmp/longjmp
and signal handlers. This section covers what changes once message sends are in the picture.
if / else
Standard C. What is distinctive is how often a message send appears inside the condition — legal, common,
and safe even on nil:
if ([delegate respondsToSelector:@selector(didFinish:)]) {
[delegate didFinish:self];
} else if ([self.items count] > 0) {
[self processItems];
} else {
[self reportEmpty];
}
switch
C’s switch — integral values only, with fall-through unless you break. Objective-C’s characteristic use
is over an NS_ENUM, and the characteristic advice is to omit default: so the compiler warns about
unhandled cases:
typedef NS_ENUM(NSInteger, MyPlayerState) {
MyPlayerStateStopped,
MyPlayerStatePlaying,
MyPlayerStatePaused
};
switch (self.state) {
case MyPlayerStateStopped:
[self start];
break;
case MyPlayerStatePlaying:
[self pause];
break;
case MyPlayerStatePaused:
[self resume];
break;
// no default: -- adding a fourth state now produces a -Wswitch warning here
}
You cannot switch on an object. To branch on a string, either compare with isEqualToString: in an
if/else chain or — better for more than a few cases — look the behaviour up in an NSDictionary of
blocks.
Declaring a variable inside a case without braces is a C error (declaration not allowed after label);
wrap the case body in { … }.
Loops
All four C loop forms work, plus Objective-C’s own for…in:
for (NSUInteger i = 0; i < count; i++) { /* C for */ }
while ([self hasMoreData]) { /* … */ }
do { /* at least once */ } while (retries-- > 0);
for (NSString *name in names) { // fast enumeration -- Objective-C's addition
NSLog(@"%@", name);
}
Fast enumeration is the idiomatic way to walk a Foundation collection: shorter than an index loop, and
substantially faster than sending objectAtIndex: per element. It is covered in
Collections and Fast
Enumeration, along with the rule that you must not mutate a collection while enumerating it.
break and continue behave as in C. Objective-C has no labelled break; to leave nested loops, use a flag,
a goto, or — most idiomatically — factor the inner loop into its own method and return.
The Conditional Operator
cond ? a : b as in C, including GCC’s widely used ?: elision, which Clang supports:
NSString *display = name ? name : @"(unknown)";
NSString *shorter = name ?: @"(unknown)"; // evaluates `name` once
?: is Objective-C’s closest equivalent to a null-coalescing operator and reads well for defaulting.
Functions and Methods
Objective-C has both, and choosing between them is a real design decision. Plain C functions behave exactly as
they do in C — prototypes, pass-by-value, static linkage, inline, recursion and variadic functions via
<stdarg.h> are all covered in C Reference — Functions.
| C function | Objective-C method | |
|---|---|---|
Syntax |
|
|
Call |
|
|
Dispatch |
Static — resolved at link time |
Dynamic — resolved by selector at run time |
Receiver |
None |
|
Overridable |
No |
Yes, by any subclass |
Cost |
A direct call |
A call through |
Use a function for stateless computation, for performance-critical inner loops, and for anything that
must be callable from plain C. Use a method whenever behaviour belongs to an object, or must be
overridable, or forms part of a public API. Foundation itself does exactly this: NSMakeRange,
CGRectIntersectsRect and NSStringFromClass are functions; everything with a receiver is a method.
Method Syntax
A method declaration interleaves its name with its arguments, which is why Objective-C selectors read like sentences:
- (void)insertObject:(id)object atIndex:(NSUInteger)index;
//^ ^ ^ ^
//| | | `-- second argument
//| | `-- first argument
//| `-- return type
//`-- "-" = instance method, "+" = class method
The selector of that method is insertObject:atIndex: — the concatenated keywords with their colons. Two
methods differing only in argument types share a selector and therefore collide; there is no overloading by
type. See Classes and Objects.
Pointers, struct and union
Pointers are C’s, and every Objective-C object is accessed through one. NSString *s is a pointer; id is
already a pointer type. There are no stack-allocated Objective-C objects.
struct and union are C’s and are used freely for small value types — Foundation and the graphics
frameworks are full of them (NSRange, CGPoint, CGRect, CGSize). For the C semantics in depth see
C Reference — Pointers,
Structures, Unions and Type Aliases and
The Memory Model and Alignment:
NSRange r = NSMakeRange(0, 5);
NSLog(@"%lu…%lu", (unsigned long)r.location, (unsigned long)(r.location + r.length));
CGRect frame = CGRectMake(0, 0, 320, 480);
CGFloat mid = CGRectGetMidX(frame);
Two rules govern their interaction with the object system:
-
A
structis not an object. It cannot receive messages, is copied by value on assignment, and is invisible to the run time’s introspection. -
Under ARC, a C
structfield may be an object pointer — the compiler emits copy and destroy helpers for it — but those helpers never run for amalloc-allocated struct, so such a field must be__unsafe_unretainedwith a hand-managed lifetime. A small class or anNSValueis usually better. See Automatic Reference Counting.
To carry a struct inside a Foundation collection, box it in an NSValue — see
Strings, Numbers and Values.
The Preprocessor
The C preprocessor is present in full — macro definition, conditional compilation, stringification and token
pasting, variadic macros and VA_OPT are all covered in
C Reference — The Preprocessor and Macros. Three
points are specific to how Objective-C uses it.
#import versus #include
#import is #include that never includes the same file twice. It makes include guards unnecessary and is
what Objective-C code uses for headers, without exception:
#import <Foundation/Foundation.h> // framework header
#import "MyClass.h" // project header
#include <math.h> // plain C header: either works, #import is fine too
Prefer a @class forward declaration to an #import in a header when you only need the name of a type — it cuts compile times and breaks circular-import chains — and #import the real header in the .m:
// MyView.h
@class MyModel; // just the name is needed here
@interface MyView : NSObject
@property (nonatomic, strong) MyModel *model;
@end
// MyView.m
#import "MyModel.h" // the full declaration is needed here
Clang modules supersede both for framework imports: @import Foundation; parses a precompiled module instead
of re-reading text. See
Modules, Frameworks and
Code Organization.
#pragma mark
#pragma mark inserts a labelled divider into Xcode’s jump bar. It is pure organisation, costs nothing, and
is conventional in any implementation file long enough to scroll:
#pragma mark - Lifecycle
- (instancetype)init { /* … */ }
- (void)dealloc { /* … */ }
#pragma mark - MyDelegateProtocol
- (void)didFinish:(id)sender { /* … */ }
The leading - draws a separator line above the label.
Conditional Compilation
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
// iOS, iPadOS, tvOS, watchOS
#elif TARGET_OS_OSX
// macOS
#endif
#if TARGET_OS_SIMULATOR
NSLog(@"running in the simulator");
#endif
#ifdef DEBUG
NSLog(@"debug build only");
#endif
Compile-time conditionals answer "which platform was this built for". They cannot answer "which OS version
is this running on" — that is @available, a run-time check, covered in
Lightweight Generics and
Nullability.
Prefer static inline functions and const variables to function-like and object-like macros: macros are
untyped, invisible to the debugger, and re-evaluate their arguments.
See Also
-
Basic Syntax and Types —
BOOL,NSInteger,NS_ENUMand the@-directive table. -
Classes and Objects — method declaration syntax in full.
-
Collections and Fast Enumeration —
for…inand the block-based alternatives.