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

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

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, NSMutableDictionary in 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 self implicitly through the block capture and are hard to reason about.
  • In Swift, prefer for-in loops 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 / let ceiling and surface a timeout as an error.
  • Replace while (!ready) { poll() } with bounded retries plus a backoff schedule.
  • In SwiftUI, never compute body based on an unbounded iteration; ForEach requires 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 body grows 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 precondition for invariants that must hold in release builds; assert for debug-only checks.
  • Use NSParameterAssert and NSAssert in 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 let over var; an immutable binding is the narrowest scope of all (no rebinding).
  • In SwiftUI, place @State at 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 let will 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 throws and Result rather than discardable optionals. Never try? 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 onChange or 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 42 with static const NSInteger kConstName = 42. Replace function-like macros with static inline functions.
  • In Objective-C++, use constexpr for compile-time constants and constexpr functions instead of macros.
  • In Swift, the language has no traditional preprocessor; use #if DEBUG sparingly and prefer runtime configuration.
  • In SwiftUI, never #if view branches inside body — 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 the withUnsafe… 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_ptr and std::shared_ptr over raw new/delete. Pass by reference, not pointer-to-pointer.
  • In SwiftUI, use @EnvironmentObject and @Environment rather 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 -Weverything and selectively silence noisy categories with reasoned comments.
  • For Objective-C++, additionally set CLANG_CXX_LANGUAGE_STANDARD = c++20 and add -Wall -Wextra -Wpedantic -Wconversion to OTHER_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.

Acknowledgements


© Gerald Oluoch 2026. All rights reserved.