Operator Precedence Gotchas When Moving Between Swift and Objective-C
I once spent an embarrassing amount of time debugging a calculation that broke during a Swift migration. The original Objective-C code had result = a - (b - c) for a specific formula. During the port, someone "simplified" it to result = a - b - c, assuming the parentheses were just defensive coding. They weren't—the formula needed a - b + c, not a - b - c. The math was wrong in production for two weeks.
This wasn't actually an operator precedence issue—subtraction is left-associative in both languages. But it got me thinking about what is different between Swift and Objective-C operator precedence, and there are some genuine surprises.
Bitwise Operators and Comparisons
The most significant difference involves bitwise operators. In C (and therefore Objective-C), bitwise AND, OR, and XOR have lower precedence than comparison operators. This is widely considered one of C's design mistakes.
// Objective-C - probably not what you intended
if (flags & FLAG_ENABLED == YES) {
// This evaluates as: flags & (FLAG_ENABLED == YES)
// If FLAG_ENABLED is 0x02 and YES is 1, you're checking flags & 0
}
The fix in Objective-C requires explicit parentheses:
// Objective-C - correct
if ((flags & FLAG_ENABLED) == YES) {
// Now correctly checks if the flag is set
}
Swift reorganized precedence so this works intuitively:
// Swift - works as expected
if flags & flagEnabled == true {
// Evaluates as: (flags & flagEnabled) == true
}
In Swift, &, |, and ^ are in the MultiplicationPrecedence and AdditionPrecedence groups, both higher than ComparisonPrecedence. If you're porting bit manipulation code from Objective-C, you might be able to remove some of those defensive parentheses—but think twice before doing so in code that needs to compile in both languages.
Type Casting
Type casts work quite differently. In Objective-C, C-style casts are high-precedence prefix operators:
// Objective-C
CGFloat width = (CGFloat)pixelWidth / scale;
// Casts pixelWidth to CGFloat, then divides
In Swift, casting operators (as, as?, as!) are lower precedence than arithmetic:
// Swift - this doesn't compile
let width = pixelWidth as CGFloat / scale // Error
// You need parentheses
let width = (pixelWidth as CGFloat) / scale
// Or use a constructor
let width = CGFloat(pixelWidth) / scale
This rarely causes silent bugs because Swift's type system will usually complain, but it can trip you up when translating code.
Assignment Doesn't Return a Value
In C and Objective-C, assignment is an expression that returns the assigned value:
// Objective-C - idiomatic pattern
while ((c = getchar()) != EOF) {
// Process character
}
// Or chained assignment
a = b = c = 0;
In Swift, assignment returns Void, so these patterns don't work:
// Swift - doesn't compile
while (c = getchar()) != EOF { } // Error: Cannot convert value of type '()' to expected type 'Int32'
// Chained assignment doesn't compile either
a = b = c = 0 // Error
Swift made this change deliberately to prevent the common bug of writing = when you meant ==. You'll need to restructure such code during migration:
// Swift equivalent
while true {
let c = getchar()
if c == EOF { break }
// Process character
}
The Comma Operator
C has a comma operator that evaluates expressions left-to-right and returns the rightmost value:
// Objective-C - valid (though unusual)
int result = (a++, b++, a + b);
Swift has no comma operator. Commas are purely syntactic separators for argument lists, tuple elements, and similar constructs. Any code using the comma operator needs restructuring during migration.
Nil-Coalescing and Optional Chaining
Swift introduced ?? for nil-coalescing and ?. for optional chaining, neither of which exist in Objective-C. The nil-coalescing operator has its own precedence level, lower than comparison operators but higher than the ternary:
let name = username ?? "Anonymous"
let isValid = count ?? 0 > threshold // Evaluates as: (count ?? 0) > threshold
When adding these to code that interoperates with Objective-C, remember they'll need to be replaced with explicit nil checks on the Objective-C side.
Custom Operators
Swift lets you define operators with custom precedence, something Objective-C can't do at all. If you're working in a mixed codebase and encounter unfamiliar operators, check whether they're custom-defined:
// Someone might define this
infix operator ?= : AssignmentPrecedence
func ?=<T>(lhs: inout T?, rhs: T) {
if lhs == nil { lhs = rhs }
}
These operators can't cross the Swift/Objective-C boundary directly.
Practical Advice
When porting arithmetic or bitwise code between Swift and Objective-C:
Keep parentheses that clarify intent, even if they're technically unnecessary.
(a + b) * cis clearer than relying on precedence rules, and it works correctly in both languages.Be especially careful with bitwise flag checks. The different precedence of
&,|, and^relative to==is the most likely source of subtle bugs.Watch for assignment-as-expression patterns. These are common in Objective-C networking and file I/O code.
When in doubt, add parentheses. The compiler will optimize them away, and future readers (including yourself) will thank you.
For the complete operator precedence tables, see Apple's documentation on Swift operators and the C operator precedence reference.