NASA Power of 10 - Objective-C, Swift, SwiftUI Approach

The Power of 10 rules were created in 2006 by Gerard J. Holzmann of the NASA/JPL Laboratory of Reliable Software
The most serious software development projects are governed by a set of coding guidelines that aim to be the backbone of how exactly code should be submitted inorder to guarantee utmost success.
- Rule 1: Restrict Use of Dynamic Memory Allocation
- Rule 2: Restrict Code to Simple Control Flow Constructs
- Rule 3: Give All Loops a Fixed Upper Bound
- Rule 4: Keep Functions Short
- Rule 5: Use Assertions Liberally
- Rule 6: Declare Data Objects at the Smallest Possible Scope
- Rule 7: Check Return Values and Validate Parameters
- Rule 8: Limit Preprocessor Use
- Rule 9: Restrict the Use of Pointers
- Rule 10: Compile with All Warnings Enabled, Always
- Closing Thought
- Acknowledgements
Rule 1: Restrict Use of Dynamic Memory Allocation
Dynamic memory allocation (e.g., malloc, free, new, delete) can lead to memory fragmentation, leaks, and unpredictable behavior. iOS apps should favor static or stack-based allocation and use ARC (Automatic Reference Counting) in Swift.
Best Practices in iOS
- Use value types (
structs) in Swift instead of reference types (classes) where possible. - Avoid excessive use of
NSMutableArray,NSMutableDictionaryin Objective-C; prefer immutable collections. - Utilize Swift’s ARC to manage memory efficiently.
- Avoid allocating memory in loops or frequently called functions.
Swift
struct User {
let id: Int
let name: String
}
// Prefer value types (struct) to avoid unnecessary heap allocation.
let user = User(id: 1, name: "Alice")
Good iterative approach to Swift
Objective-C
@interface User : NSObject
@property (nonatomic, assign) int userId;
@property (nonatomic, strong) NSString *name;
@end
@implementation User
@end
User *user = [[User alloc] init];
user.userId = 1;
user.name = @"Alice";
Good iterative approach to Objective-C
Objective-C++
#import <array>
// Prefer `std::array<T, N>` (stack-allocated, fixed size) over `std::vector`
// or `new[]` when bridging C++ into iOS code. Zero heap traffic, deterministic
// lifetime ending at scope exit, and the size is part of the type.
static void processFixedBuffer() {
std::array<uint8_t, 1024> buffer{}; // stack-allocated, no malloc
// ... use buffer ...
}
Good iterative approach to Objective-C++
SwiftUI
struct ContentView: View {
let users = ["Alice", "Bob", "Charlie"] // Avoids dynamic allocation in loops
var body: some View {
List(users, id: \ .self) { user in
Text(user)
}
}
}
Good iterative approach to SwiftUI
Rule 2: Restrict Code to Simple Control Flow Constructs
Avoid goto, setjmp / longjmp, and direct or indirect recursion. Deep call chains and recursive descents make static analysis hard and risk stack overflows on adversarial input. Prefer bounded iteration with explicit data structures.
Best Practices in iOS
- Replace recursive tree/graph walks with an explicit stack or queue.
- Avoid recursive blocks in Objective-C; they retain
selfimplicitly through the block capture and are hard to reason about. - In Swift, prefer
for-inloops over reduce-style recursion in performance-critical paths. - In SwiftUI, prefer flat, declarative composition over deeply nested recursive view trees.
Swift
// Iterative tree-sum using an explicit stack — no recursion, predictable stack depth.
func sumIterative(_ root: TreeNode?) -> Int {
var sum = 0
var stack: [TreeNode] = []
if let root = root { stack.append(root) }
while let node = stack.popLast() {
sum += node.value
if let l = node.left { stack.append(l) }
if let r = node.right { stack.append(r) }
}
return sum
}
Good iterative approach to Swift
Objective-C
- (NSInteger)sumOfTree:(TreeNode *)root {
NSInteger sum = 0;
NSMutableArray<TreeNode *> *stack = [NSMutableArray array];
if (root) [stack addObject:root];
while (stack.count > 0) {
TreeNode *node = stack.lastObject;
[stack removeLastObject];
sum += node.value;
if (node.left) [stack addObject:node.left];
if (node.right) [stack addObject:node.right];
}
return sum;
}
Good iterative approach to Objective-C
Objective-C++
#import <stack>
- (NSInteger)sumOfTree:(TreeNode *)root {
NSInteger sum = 0;
std::stack<TreeNode *> work;
if (root) work.push(root);
while (!work.empty()) {
TreeNode *node = work.top();
work.pop();
sum += node.value;
if (node.left) work.push(node.left);
if (node.right) work.push(node.right);
}
return sum;
}
Good iterative approach to Objective-C++
SwiftUI
// Pre-flatten hierarchical data with depth metadata, then render with List/ForEach.
// Avoid building view trees through recursive function calls in `body`.
struct OutlineRow: Identifiable {
let id: UUID
let title: String
let depth: Int
}
struct OutlineView: View {
let rows: [OutlineRow] // flat, bounded
var body: some View {
List(rows) { row in
HStack {
Spacer().frame(width: CGFloat(row.depth) * 16)
Text(row.title)
}
}
}
}
Good iterative approach to SwiftUI
Rule 3: Give All Loops a Fixed Upper Bound
Every loop must have an upper bound that a checking tool can prove statically. Unbounded while loops are the classic source of infinite spins; in iOS this typically appears as polling, retries, or “wait until ready” code that locks the main thread on adversarial backends.
Best Practices in iOS
- Cap retry counts with a
static const/letceiling and surface a timeout as an error. - Replace
while (!ready) { poll() }with bounded retries plus a backoff schedule. - In SwiftUI, never compute
bodybased on an unbounded iteration;ForEachrequires a bounded data source by construction. - Use
Task.sleep(for:)with a maximum cumulative deadline rather than infinite polling loops.
Swift
enum PollingError: Error { case timeout }
func waitUntilReady() throws {
let maxRetries = 10
for attempt in 0..<maxRetries {
if isReady() { return }
poll()
if attempt == maxRetries - 1 { throw PollingError.timeout }
}
}
Good iterative approach to Swift
Objective-C
static const NSInteger kMaxRetries = 10;
- (BOOL)waitUntilReady:(NSError **)error {
for (NSInteger i = 0; i < kMaxRetries; i++) {
if ([self isReady]) return YES;
[self poll];
}
if (error) {
*error = [NSError errorWithDomain:@"Polling" code:-1 userInfo:nil];
}
return NO;
}
Good iterative approach to Objective-C
Objective-C++
static constexpr int kMaxRetries = 10;
- (BOOL)waitUntilReady {
for (int i = 0; i < kMaxRetries; ++i) { // bounded, statically provable
if ([self isReady]) return YES;
[self poll];
}
return NO;
}
Good iterative approach to Objective-C++
SwiftUI
// ForEach is bounded by the data source. Cap the data source explicitly
// when it could otherwise grow unbounded.
struct AttemptList: View {
let attempts: [Attempt]
private static let maxVisible = 50
var body: some View {
ForEach(attempts.prefix(Self.maxVisible)) { attempt in
Text(attempt.title)
}
}
}
Good iterative approach to SwiftUI
Rule 4: Keep Functions Short
No function should exceed what fits on a single sheet of paper — about 60 lines. A long function is almost always doing more than one thing and is harder to test, review, and statically analyze. Decompose into single-responsibility units.
Best Practices in iOS
- A method that scrolls off screen in Xcode is too long.
- Extract validation, fetch, transform, and render into separate methods.
- In SwiftUI, when
bodygrows past ~50 lines, extract subviews. - In Objective-C++, push helpers into anonymous namespaces rather than free-standing C functions in headers.
Swift
// Each function does one thing and fits well under 60 lines.
func processUser(_ raw: RawUser) async throws -> Profile {
let validated = try validate(raw)
let user = try await persist(validated)
return try await fetchProfile(for: user)
}
private func validate(_ raw: RawUser) throws -> User { /* ... */ }
private func persist(_ user: User) async throws -> User { /* ... */ }
private func fetchProfile(for user: User) async throws -> Profile { /* ... */ }
Good iterative approach to Swift
Objective-C
- (void)processUser:(User *)user {
User *validated = [self validateUser:user];
if (!validated) return;
Profile *profile = [self fetchProfileFor:validated];
[self renderProfile:profile];
}
- (User *)validateUser:(User *)user { /* short */ }
- (Profile *)fetchProfileFor:(User *)u { /* short */ }
- (void)renderProfile:(Profile *)profile { /* short */ }
Good iterative approach to Objective-C
Objective-C++
// Anonymous-namespace helpers keep linkage local and the .mm file short.
namespace {
bool validate(const UserRecord &u) { return !u.name.empty() && u.id > 0; }
Profile buildProfile(const UserRecord &u) { /* ... */ }
}
- (void)processUser:(UserRecord *)user {
if (!validate(*user)) return;
Profile p = buildProfile(*user);
[self renderProfile:p];
}
Good iterative approach to Objective-C++
SwiftUI
// Extract subviews aggressively. A 200-line body is a code smell.
struct DashboardView: View {
var body: some View {
VStack {
HeaderSection()
ContentSection()
FooterSection()
}
}
}
private struct HeaderSection: View { var body: some View { /* short */ Text("") } }
private struct ContentSection: View { var body: some View { /* short */ Text("") } }
private struct FooterSection: View { var body: some View { /* short */ Text("") } }
Good iterative approach to SwiftUI
Rule 5: Use Assertions Liberally
The Power of 10 calls for an average of at least two assertions per function — one for preconditions, one for postconditions or invariants. Assertions document expected behavior in code and crash the program loudly in debug builds rather than corrupting state silently in production.
Best Practices in iOS
- Use
preconditionfor invariants that must hold in release builds;assertfor debug-only checks. - Use
NSParameterAssertandNSAssertin Objective-C for parameter validation and invariants. - Pair every non-trivial public API with at least one parameter assertion and one invariant assertion.
- In SwiftUI, assertions in computed views are valid but should not have side effects; use them to validate state.
Swift
func divide(_ numerator: Int, by denominator: Int) -> Int {
precondition(denominator != 0, "Denominator must be non-zero")
let result = numerator / denominator
assert(result * denominator + (numerator % denominator) == numerator,
"Division postcondition violated")
return result
}
Good iterative approach to Swift
Objective-C
- (NSInteger)divide:(NSInteger)num by:(NSInteger)denom {
NSParameterAssert(denom != 0);
NSInteger result = num / denom;
NSAssert(result * denom + (num % denom) == num,
@"Division postcondition violated");
return result;
}
Good iterative approach to Objective-C
Objective-C++
#include <cassert>
- (NSInteger)divide:(NSInteger)num by:(NSInteger)denom {
NSParameterAssert(denom != 0); // Foundation-style precondition
NSInteger result = num / denom;
assert(result * denom + (num % denom) == num); // C++-style invariant
return result;
}
Good iterative approach to Objective-C++
SwiftUI
struct ProgressBar: View {
let progress: Double // expected 0.0 ... 1.0
var body: some View {
assert(progress >= 0.0 && progress <= 1.0,
"ProgressBar received out-of-range progress: \(progress)")
return GeometryReader { geo in
Rectangle()
.frame(width: geo.size.width * max(0, min(1, progress)),
height: geo.size.height)
}
}
}
Good iterative approach to SwiftUI
Rule 6: Declare Data Objects at the Smallest Possible Scope
A variable should be visible only where it is used. Wider scope means more places the value can be mutated, more cognitive load for the reader, and more surface for accidental misuse. Tighten scope until the compiler complains, then back off one step.
Best Practices in iOS
- Declare locals immediately before their first use, not at the top of a method.
- Prefer
letovervar; an immutable binding is the narrowest scope of all (no rebinding). - In SwiftUI, place
@Stateat the smallest view that genuinely owns the data; lift only when a sibling truly needs to read it. - Avoid file-scope or class-scope globals when a method-scope
static letwill do.
Swift
func updateUI(items: [Item]) {
// ... preceding work that doesn't need processedCount ...
let processedCount = items.filter(\.isProcessed).count // declared at point of use
log("processed: \(processedCount)")
}
Good iterative approach to Swift
Objective-C
- (void)updateUIWithItems:(NSArray<Item *> *)items {
// ... preceding work ...
NSUInteger processedCount = 0;
for (Item *item in items) {
if (item.isProcessed) processedCount++;
}
NSLog(@"processed: %lu", (unsigned long)processedCount);
}
Good iterative approach to Objective-C
Objective-C++
- (void)updateUIWithItems:(NSArray<Item *> *)items {
// ... preceding work ...
const auto processedCount = [items filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:@"isProcessed == YES"]].count;
NSLog(@"processed: %lu", (unsigned long)processedCount);
}
Good iterative approach to Objective-C++
SwiftUI
// @State at the smallest possible view; not lifted to the parent.
struct CounterButton: View {
@State private var count = 0 // owned exactly here
var body: some View {
Button("\(count)") { count += 1 }
}
}
// Parent doesn't know or care about count — narrowest scope.
struct ContentView: View {
var body: some View {
VStack { CounterButton(); CounterButton() }
}
}
Good iterative approach to SwiftUI
Rule 7: Check Return Values and Validate Parameters
Every non-void function returns a value because the caller needs it; ignore the return and you discard information the function paid to compute. Every function receives parameters because they shape behavior; fail to validate them and you propagate bad state. Check both ends.
Best Practices in iOS
- In Swift, use
throwsandResultrather than discardable optionals. Nevertry?away an error you would have wanted to know about. - In Objective-C, the canonical pattern is
NSError **out-parameters; always check the return value before reading*error. - In Objective-C++, prefer
std::optional<T>for “might fail” and exceptions for “should not fail.” - In SwiftUI, validate state in
onChangeor in the binding setter before it propagates to a child.
Swift
do {
let user = try JSONDecoder().decode(User.self, from: rawData)
process(user)
} catch DecodingError.dataCorrupted(let context) {
log(.error, "Data corrupted: \(context.debugDescription)")
} catch {
log(.error, "Decode failed: \(error)")
}
Good iterative approach to Swift
Objective-C
NSError *error = nil;
id parsed = [NSJSONSerialization JSONObjectWithData:rawData options:0 error:&error];
if (!parsed) {
NSLog(@"Parse failed: %@", error);
return;
}
if (![parsed isKindOfClass:[NSDictionary class]]) {
NSLog(@"Unexpected JSON shape: %@", [parsed class]);
return;
}
[self processDictionary:parsed];
Good iterative approach to Objective-C
Objective-C++
#include <optional>
- (std::optional<int>)parseIntFromString:(NSString *)s {
NSScanner *scanner = [NSScanner scannerWithString:s];
int value = 0;
if ([scanner scanInt:&value] && [scanner isAtEnd]) {
return value;
}
return std::nullopt;
}
- (void)consume:(NSString *)input {
auto v = [self parseIntFromString:input];
if (!v.has_value()) { [self handleInvalid:input]; return; }
[self useValue:*v];
}
Good iterative approach to Objective-C++
SwiftUI
struct UserDetail: View {
let user: User
var body: some View {
Group {
if user.name.trimmingCharacters(in: .whitespaces).isEmpty {
Text("Invalid user").foregroundColor(.red)
} else {
Text(user.name)
}
}
}
}
Good iterative approach to SwiftUI
Rule 8: Limit Preprocessor Use
The original Power of 10 rule restricts the C preprocessor to header inclusion and trivial macros. Aggressive #define and conditional compilation make code opaque to static analyzers and humans alike. In iOS, this generalizes to “avoid build-config-dependent code paths in your view tree” — they obscure runtime behavior across build flavors.
Best Practices in iOS
- In Objective-C, replace
#define CONST_NAME 42withstatic const NSInteger kConstName = 42. Replace function-like macros withstatic inlinefunctions. - In Objective-C++, use
constexprfor compile-time constants and constexpr functions instead of macros. - In Swift, the language has no traditional preprocessor; use
#if DEBUGsparingly and prefer runtime configuration. - In SwiftUI, never
#ifview branches insidebody— the static shape of the view tree should not depend on build configuration.
Swift
// Runtime-driven configuration is clearer than compile-time conditionals.
enum Environment {
case staging, production
var apiBase: URL {
switch self {
case .staging: return URL(string: "https://staging.api.example.com")!
case .production: return URL(string: "https://api.example.com")!
}
}
}
Good iterative approach to Swift
Objective-C
// Bad:
// #define MAX_ITEMS 100
// #define SQUARE(x) ((x) * (x))
// Good:
static const NSInteger kMaxItems = 100;
static inline NSInteger square(NSInteger x) { return x * x; }
Good iterative approach to Objective-C
Objective-C++
// constexpr replaces every reasonable #define in modern C++.
constexpr int kMaxItems = 100;
constexpr int square(int x) { return x * x; }
Good iterative approach to Objective-C++
SwiftUI
// Avoid #if/#endif inside `body`. Prefer environment-driven runtime branches.
struct FeatureFlaggedView: View {
@Environment(\.featureFlags) private var flags
var body: some View {
if flags.contains(.newDashboard) {
NewDashboard()
} else {
LegacyDashboard()
}
}
}
Good iterative approach to SwiftUI
Rule 9: Restrict the Use of Pointers
The original Power of 10 allows at most one level of dereferencing — no **, no function pointers, no chains of indirection. In iOS this maps to “use safe abstractions over raw pointers” and “avoid passing references through arbitrarily deep view hierarchies.”
Best Practices in iOS
- In Swift, avoid
UnsafePointer,UnsafeMutablePointer, and thewithUnsafe…family except at well-defined C-interop boundaries. - In Objective-C, the canonical
NSError **pattern is the one acceptable double pointer; everything else should return a struct or a Foundation collection. - In Objective-C++, prefer
std::unique_ptrandstd::shared_ptrover rawnew/delete. Pass by reference, not pointer-to-pointer. - In SwiftUI, use
@EnvironmentObjectand@Environmentrather than threading reference types through every initializer.
Swift
// Safe value-type composition; no Unsafe pointers anywhere in app code.
struct Buffer { let bytes: [UInt8] }
struct PacketGroup { let buffers: [Buffer] }
func summarize(_ group: PacketGroup) -> Int {
group.buffers.reduce(0) { $0 + $1.bytes.count }
}
Good iterative approach to Swift
Objective-C
// Bad: chain of pointer-to-pointer for general data
// - (void)processData:(uint8_t **)dataOut count:(NSUInteger **)countOut;
// Good: return a typed struct or Foundation collection.
@interface PacketSummary : NSObject
@property (nonatomic, assign, readonly) NSUInteger totalBytes;
@property (nonatomic, assign, readonly) NSUInteger packetCount;
@end
- (PacketSummary *)summarizePackets:(NSArray<Packet *> *)packets;
Good iterative approach to Objective-C
Objective-C++
#include <memory>
// Owning resources: std::unique_ptr. No manual new/delete.
- (void)consume:(const std::unique_ptr<Buffer> &)buffer {
// single-level dereference via reference; no pointer-to-pointer
buffer->process();
}
// Factory returns owning unique_ptr; caller's lifetime is explicit.
- (std::unique_ptr<Buffer>)makeBuffer {
return std::make_unique<Buffer>(/* args */);
}
Good iterative approach to Objective-C++
SwiftUI
// Environment objects flatten deep dependency chains — no threading
// of references through every initializer in the view hierarchy.
@MainActor
final class AppSession: ObservableObject {
@Published var user: User = .placeholder
}
struct RootView: View {
@StateObject private var session = AppSession()
var body: some View {
FeatureView().environmentObject(session)
}
}
struct DeepView: View {
@EnvironmentObject var session: AppSession // accessible at any depth
var body: some View {
Text(session.user.name)
}
}
Good iterative approach to SwiftUI
Rule 10: Compile with All Warnings Enabled, Always
The final rule: every build, from day one, runs the compiler at its most pedantic setting with warnings-as-errors. Suppress nothing without a written comment explaining why. Warnings are the cheapest static analyzer you have; ignoring them is wasting the team that built the compiler.
Best Practices in iOS
- In Xcode, set Treat Warnings as Errors (
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES,GCC_TREAT_WARNINGS_AS_ERRORS = YES). - Enable Strict Concurrency Checking = Complete in Swift build settings.
- Run Product → Analyze on every CI build; the Clang Static Analyzer catches bugs the compiler misses.
- For Objective-C, enable
-Weverythingand selectively silence noisy categories with reasoned comments. - For Objective-C++, additionally set
CLANG_CXX_LANGUAGE_STANDARD = c++20and add-Wall -Wextra -Wpedantic -WconversiontoOTHER_CPLUSPLUSFLAGS. - Treat any new warning introduced by an Xcode update as a blocking issue, not noise to ignore.
Swift
// Build Settings → Swift Compiler – Warning Policies
// SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
// SWIFT_STRICT_CONCURRENCY = complete
//
// Other Swift Flags (for tighter analysis):
// -warnings-as-errors
// -Xfrontend -warn-concurrency
// -Xfrontend -warn-implicit-overrides
// -Xfrontend -warn-long-function-bodies=100
// -Xfrontend -warn-long-expression-type-checking=100
Good iterative approach to Swift
Objective-C
// Build Settings → Apple Clang – Warnings
// GCC_TREAT_WARNINGS_AS_ERRORS = YES
// CLANG_WARN_DOCUMENTATION_COMMENTS = YES
// CLANG_WARN_EVERYTHING (-Weverything) — then suppress noisy ones in code:
//
// #pragma clang diagnostic push
// #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis"
// /* reason: legacy framework header pulls this in; cannot fix locally */
// #pragma clang diagnostic pop
//
// CLANG_ANALYZER_NONNULL = YES
// Run Product → Analyze on every CI build.
Good iterative approach to Objective-C
Objective-C++
// Build Settings → Apple Clang – Custom Compiler Flags
// OTHER_CPLUSPLUSFLAGS = -Wall -Wextra -Wpedantic -Wconversion -Wshadow
// CLANG_CXX_LANGUAGE_STANDARD = c++20
// CLANG_CXX_LIBRARY = libc++
//
// And in code, prefer:
// [[nodiscard]] on non-void return values (so callers can't silently ignore)
// [[maybe_unused]] only with a comment explaining why
[[nodiscard]] static int riskyOperation(int input);
Good iterative approach to Objective-C++
SwiftUI
// Keep SwiftUI Previews compiling for every file — they catch regressions early.
// Heed runtime console warnings from SwiftUI:
// "ForEach with non-Identifiable data; this will result in undefined behavior."
// "Bound preference key was set during a view update; this will cause undefined behavior."
//
// Treat each of these as a build-breaking signal in CI even though they appear at runtime.
struct ContentView: View {
var body: some View {
// The compiler + previews + runtime console together act as a triad of warnings.
Text("Pedantic mode means listening to all three.")
}
}
#Preview { ContentView() }
Good iterative approach to SwiftUI
Closing Thought
NASA’s Power of 10 was written for safety-critical flight software where a crash is not a one-star review but a lost mission. iOS apps are not that, but the discipline transfers cleanly. Most production iOS bugs that bite users are not exotic concurrency races or kernel-level surprises — they are the predictable consequences of dynamic allocation in hot paths, unbounded loops, sprawling functions, missing assertions, ignored return values, and warnings that nobody read. The ten rules above are not a stylistic preference; they are a checklist for whether your code can survive contact with users on flaky networks, low memory, and hostile inputs.
Adopt them gradually. Start with Rule 10 — it is free and pays back immediately. Then Rule 7 (check returns, validate inputs); then Rule 5 (assertions); then Rule 4 (short functions). The rest will follow.