What does mixing Swift and Objective-C cost, and how do you keep the cost down?

4.8.2 · L5 · Interview

Predict first

A hot loop calls a small Objective-C method a million times from Swift. What is the best first fix?

// A: add more @objc attributes
// B: move the loop to one side so it crosses the boundary once
// C: mark the method dynamic
  • A
  • B
  • C

Mental model. Every border crossing costs a queue and a stamp. One trip with a full suitcase beats a hundred trips with a postcard.

Costs arrive in four places. Runtime: a call that crosses into Objective-C goes through objc_msgSend and retain and autorelease traffic, and bridging String, Array and Dictionary to their Foundation forms can allocate or copy. Binary size: every @objc member gets a thunk and runtime metadata, so a blanket @objcMembers adds weight you may never use. Optimisation: the compiler cannot inline or devirtualise across dynamic and Objective-C boundaries. Build time: mixed targets regenerate headers, and a large bridging header invalidates a lot of Swift on each change.

Keep it down by keeping the seam coarse and narrow: expose a few well-typed entry points rather than many chatty calls, keep hot loops entirely in one language, avoid NSObject as a default base class, mark only what must be @objc, and convert to Swift types once at the boundary instead of repeatedly inside loops. Measure before and after: compare binary size with and without @objcMembers, and look for objc_msgSend, objc_retain and bridging calls in Time Profiler.

Crossing the border once, with a coarse call, removes per-call message sends and retain traffic. More attributes or dynamic would add cost.

Common traps

  • Calling an Objective-C getter inside a tight Swift loop. Each call pays message-send and retain costs.
  • Making everything @objcMembers for convenience. It inflates size and slows dispatch for code that never needed it.

They might follow up with

  • How do you check binary size impact? Compare the linkmap or app thinning report with and without the attribute.
  • What is the cheapest design fix? A single coarse-grained method on the boundary that does the whole job on one side.

Related questions

  • 4.4.1 What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?
  • 4.8.1 How do Swift and Objective-C see each other: bridging header, umbrella header, and the -Swift.h file?
  • 3.6.4 What hidden retains and autoreleases does calling Objective-C add?

← How do Swift and Objective-C see each other: bridging header…   How would you plan the migration of a large Objective-C app to Swift? →