How do pointer and out-parameter conventions such as BOOL *stop import into Swift?

4.9.5 · L3 · Interview

Predict first

What does this print?

// Objective-C: - (void)enumerateItems:(void (^)(NSInteger item, BOOL *stop))block;
//   calls the block with 1, 2, 3, 4, 5 and stops when *stop is YES
var seen: [Int] = []
Walker().enumerateItems { item, stop in
    seen.append(item)
    if item == 3 { stop.pointee = true }
}
print(seen)
  • [1, 2, 3]
  • [1, 2, 3, 4, 5]
  • [3]

Mental model. The stop pointer is a red button mounted on the machine’s side. You may press it during the call, but you may not take it home.

Objective-C passes extra results through pointers, and Swift maps each shape to a type. NSError ** becomes throws (see the error question). A BOOL * “stop” flag, as in enumerateObjects(options:using:), becomes UnsafeMutablePointer<ObjCBool>: inside the closure you write stop.pointee = true to end the loop early. An id * out parameter becomes AutoreleasingUnsafeMutablePointer, which you pass as &variable; NSInteger * becomes UnsafeMutablePointer<Int> and a const pointer becomes UnsafePointer. Nullable pointers are optionals of those types, so passing nil is allowed.

The key habit is to treat those pointers as valid only during the call. The pointee is the caller’s variable, not a long-lived object, so do not store the pointer, and do not capture stop in an escaping closure. ObjCBool is a separate type from Bool (on some platforms BOOL is a signed char), so convert with .boolValue when you read it. When a block gives you the pointer you can only request a stop; the Objective-C side decides whether it honours the flag.

Writing through pointee changes the caller’s flag, and the Objective-C loop checks it after each call.

Common traps

  • Assigning stop = true instead of stop.pointee = true. The first is a type error, but writing it to a local copy of the pointer would silently do nothing.
  • Keeping the pointer after the block returns. It then points at a dead stack slot.

They might follow up with

  • How does Swift’s own for-in stop early? With break; there is no flag to pass.
  • Why is ObjCBool not Bool? The underlying C type differs by platform, so the importer keeps a distinct, layout-compatible type.

Related questions

  • 4.4.3 How do Swift errors and Objective-C NSError translate across the border?
  • 4.7.2 How do Objective-C blocks differ from Swift closures, especially for capture?

← What are NSNotFound and the other sentinel values, and how do they…   Are Objective-C block parameters escaping in Swift, and what does… →