TL;DR: Objective-C interviews in 2026 are maintenance and migration interviews: the runtime and message dispatch, ARC and block retain cycles, and how the code looks from Swift. Candidates should know that Swift 6.1 added @objc @implementation for moving a class to Swift one file at a time, and that apps built with the iOS 27 SDK must use the scene life cycle, which many older Objective-C app delegates never adopted.
Since Swift 6.1 (March 31, 2025), a Swift extension marked @objc @implementation can implement a class declared in an Objective-C header. Objective-C code can still subclass it and swizzle its methods.
That changed how teams retire Objective-C: class by class, with the header left untouched. Most Objective-C work today is keeping such codebases shipping while that migration happens.
- 1Swift imports an unannotated Objective-C object pointer as an implicitly unwrapped optional, a crash waiting in Swift callers.
- 2Since Swift 5.5, Objective-C methods that take a completion handler also import as async Swift methods.
- 3Since September 9, 2026, App Store Connect uploads must target iOS 13 or later.
- 4Xcode 27's LLDB can import explicitly built modules and precompiled bridging headers directly, which speeds up the first
poin mixed projects.
Runtime and Messaging
1. What happens when you send a message to an object?
The compiler turns [obj doThing] into a call to objc_msgSend(obj, @selector(doThing)). At run time, it looks up the selector in the receiver's class.
It checks a per-class method cache first, then the method lists of the class and each superclass. Then it jumps to the implementation (IMP) it finds.

If nothing is found, the class gets three chances before the "unrecognized selector" crash. +resolveInstanceMethod: can add a method. -forwardingTargetForSelector: can name another object. -forwardInvocation: can handle the message as an NSInvocation.
Apple's runtime guide on forwarding covers the last step.
2. What happens when you send a message to nil?
Nothing, and the call returns zero: nil for objects, 0 for integers, NO for BOOL, and a zero-filled struct for struct returns on current Apple platforms. No exception is raised.
That makes chains such as [[user profile] name] safe, and it also hides bugs: a nil delegate silently drops every callback. A candidate who has debugged "the delegate method never fires" knows to check for this first.
3. What is the difference between id, instancetype and NSObject *?
id is a pointer to any Objective-C object. The compiler lets you send it any message declared anywhere, with no type check. NSObject * is statically typed: only methods of NSObject compile without a cast.
instancetype is a return type for initializers and factory methods that means "an instance of the receiving class". [MyView viewWithFrame:frame] declared as returning instancetype is typed as MyView *, including when called on a subclass, while one returning id loses the type.
4. What is the difference between a category and a class extension?
A category adds methods to an existing class, including classes you do not own, in a separate file: @interface NSString (Trimming). It cannot add instance variables.
If two categories define the same method, which one wins is undefined, so category methods on framework classes should carry a prefix.
A class extension, @interface MyClass () in the .m file, is an anonymous category that can add properties and instance variables. It is the usual place for private properties and for redeclaring a public readonly property as readwrite.
When a category needs storage, associated objects (objc_setAssociatedObject) attach a value to an instance at run time.
@interface NSString (Trimming)- Works on classes you do not own
- Methods only, no instance variables
@interface MyClass ()in the.m- Only for your own classes
- Can add properties and instance variables
5. What is method swizzling, and how do you do it safely?
Swizzling swaps the implementations of two methods at run time with method_exchangeImplementations. Teams use it to add behaviour to a framework method, such as logging every viewDidAppear:.
+ (void)load {
static dispatch_once_t once;
dispatch_once(&once, ^{
Method original = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method swizzled = class_getInstanceMethod(self, @selector(st_viewDidAppear:));
method_exchangeImplementations(original, swizzled);
});
}
- (void)st_viewDidAppear:(BOOL)animated {
[self st_viewDidAppear:animated]; // calls the original
[Analytics trackScreen:NSStringFromClass([self class])];
}
Do it once, in +load, with a prefixed method name. Swizzling a method the class inherits but does not define changes the superclass for every subclass, so production code checks with class_addMethod first.
Swizzled framework methods break silently when Apple changes the implementation, which is why teams keep a list of them.
6. What is the difference between +load and +initialize?
+load runs once per class and per category when the image is loaded, before main, whether or not the class is ever used. Every category's +load runs, and slow code there delays app launch.
+initialize runs lazily, before the first message to the class, on the thread that sends it. A subclass that does not implement it triggers the superclass's version again, so implementations guard with if (self == [MyClass class]).
Use +initialize or dispatch_once for setup, and keep +load for swizzling.
Memory Management
7. What do strong, weak, copy and assign mean under ARC?
They set the ownership of a property's backing variable, per the Clang ARC specification. strong retains the object. weak does not retain it and is set to nil when the object is deallocated. copy stores a retained copy made with -copy. assign on an object pointer is unsafe_unretained: no retain and no zeroing.

Delegates are weak to avoid retain cycles. assign belongs on scalars such as NSInteger and CGFloat. On an object it leaves a dangling pointer that crashes later, far from the cause.
8. Why should an NSString property use copy?
Because the caller may pass an NSMutableString. With strong, the property holds the same mutable object. A later change by the caller changes your object's name behind its back. copy stores an immutable copy.
For an object that is already immutable, -copy on NSString returns the same instance with an extra retain, so the protection costs little. The same rule applies to NSArray, NSDictionary and block properties.
9. How do blocks cause retain cycles, and how do you break them?
A block retains every object it captures. If self keeps the block, directly or through an object it owns, and the block uses self, neither is ever freed. Referencing an instance variable inside a block captures self too.
__weak typeof(self) weakSelf = self;
self.loader.completion = ^(NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) { return; }
[strongSelf render:data];
};
The strong reference inside the block keeps self alive for the rest of the block once it starts. A candidate should also know __block. It lets a block change a captured local variable.
Under ARC a __block object variable is retained, so it does not break cycles on its own.
10. When do you need an @autoreleasepool?
Autoreleased objects are released when the enclosing pool drains, which on the main thread happens once per run-loop turn. A loop that makes many temporary autoreleased objects can grow memory until it ends.
Images and strings built from a large file are typical.
for (NSURL *url in urls) {
@autoreleasepool {
UIImage *image = [UIImage imageWithContentsOfFile:url.path];
[self processImage:image];
}
}
Wrapping the loop body drains the pool on every iteration. Threads you create yourself also need a pool; GCD queues provide their own.
11. What is toll-free bridging, and what do the bridge casts do?
Some Core Foundation and Foundation types share a memory layout. CFStringRef and NSString * are one pair, so a pointer can be cast between them. ARC manages Objective-C objects but not Core Foundation ones.
The cast must say who owns the reference:
(__bridge NSString *)cfString: no change in ownership.(__bridge_transfer NSString *)cfStringorCFBridgingRelease: ARC takes over a +1 Core Foundation reference, for example from aCreateorCopyfunction.(__bridge_retained CFStringRef)nsStringorCFBridgingRetain: hands a +1 reference to Core Foundation, which you must laterCFRelease.
The wrong one causes a leak or an over-release. It is a common source of crashes in older code that calls C APIs.
12. Does atomic make a property thread-safe?
No. atomic, the default, guarantees that a getter returns a whole value even while another thread runs the setter. It does not make a sequence of steps safe. self.count = self.count + 1 from two threads still loses updates.
An atomic NSMutableArray property does not protect the array's contents.
Most code declares nonatomic because atomic accessors cost a lock and give a false sense of safety. Real thread safety comes from a serial queue, a lock or confining the object to one thread.
Concurrency and Patterns
13. Why does dispatch_sync to the main queue from the main thread deadlock?
dispatch_sync waits for the block to finish, and the main queue runs one block at a time. Called on the main thread, it waits for a block that cannot start until the current one, the one waiting, returns.
The same deadlock happens with any serial queue that dispatches synchronously to itself. For a read-write pattern, use a concurrent queue with dispatch_sync for reads and dispatch_barrier_async for writes.
Reads then run in parallel and each write runs alone.
14. What goes wrong with Key-Value Observing?
KVO notifies an observer when a property changes, by creating a subclass of the observed object at run time and overriding its setters. Three classic failures:
- Forgetting to remove the observer, which crashed on older iOS versions when the observed object was freed.
- A typo in the key path, which fails silently.
- Writing the instance variable directly, which never notifies.
A single observeValueForKeyPath:ofObject:change:context: method receives every observation, so a unique static context pointer is needed to tell yours apart from a superclass's.
15. When do you use a delegate, a block or a notification?
- Delegate: one object needs to answer several related questions or events over time, such as a table view's data source. The relationship is one-to-one and typed by a protocol.
- Block: a single callback for a single operation, such as a network request's completion.
- Notification: many unrelated observers need to hear about one event, such as the keyboard appearing. The sender does not know who listens.
Overusing notifications makes data flow hard to trace, which is often what makes a legacy codebase hard to change.
16. What is a designated initializer?
The initializer that fully sets up an object and calls the superclass's designated initializer. Other initializers of the class, the convenience ones, must call it through self.
Mark it with NS_DESIGNATED_INITIALIZER and the compiler warns when a subclass breaks the chain.
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
Marking init unavailable forces callers through the designated initializer. These annotations also decide which initializers Swift subclasses inherit.
Swift Interoperability
17. How do Objective-C and Swift see each other in one target?
Swift sees Objective-C through the bridging header, where you import the Objective-C headers Swift should use.
Objective-C sees Swift through the generated header, ModuleName-Swift.h, which declares every Swift class and member marked @objc or inherited from NSObject.
Import the generated header only in .m files. Importing it in a header can create a cycle, because the generated header itself depends on the bridging header. In a framework target, the umbrella header replaces the bridging header.
18. How do nullability annotations change the Swift API?
Without annotations, Swift imports object pointers as implicitly unwrapped optionals, such as String!, according to Apple's nullability guide. A Swift caller can use the value directly and crash when it is nil.
NS_ASSUME_NONNULL_BEGIN
@interface MyList : NSObject
- (nullable MyListItem *)itemWithName:(NSString *)name;
@property (nonatomic, copy) NSString *title;
@end
NS_ASSUME_NONNULL_END
Inside the audited region everything is non-null unless marked nullable, so Swift sees itemWithName(_:) -> MyListItem? and title: String.
Adding these annotations is usually the first step before any Swift migration, and it surfaces real nil bugs on the Objective-C side too.
NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END, then mark only the values that can really be nil. The compiler warns when a header mixes annotated and unannotated pointers.19. How do you give an Objective-C API a better Swift name or signature?
Three tools. NS_SWIFT_NAME renames a declaration for Swift, for example turning a C function into a method on a type.
NS_REFINED_FOR_SWIFT imports the original with a double-underscore prefix so a Swift extension can wrap it in a nicer API with the original name. Lightweight generics, such as NSArray<NSString *> *, import as [String] instead of [Any].
Apple documents the first two in Renaming Objective-C APIs for Swift and Improving Objective-C API Declarations for Swift. A candidate who has done this can make an Objective-C framework feel native to Swift callers without rewriting it.
20. How do Objective-C completion handlers appear in Swift concurrency?
Since Swift 5.5, a method whose last parameter is a completion block imports twice. One version takes the closure and one is an async method. See Apple's page on calling Objective-C APIs asynchronously.
If the block has a nullable NSError * parameter, the async version throws. A block with several values returns a tuple.
The method must return void, and the block must be called exactly once. An Objective-C method that sometimes never calls its completion will hang the Swift caller forever, which is a bug worth asking about.
21. How would you plan migrating an Objective-C app to Swift?
Write new code in Swift and move old code when you touch it. Start from the leaves of the dependency graph: models and utilities before the view controllers that import them.
Before moving anything, add nullability and generics annotations so the Swift side gets real types.
Keep both languages building and tested at every step, and avoid a big-bang rewrite. Ask the candidate which classes they would not migrate yet, such as heavily swizzled or subclassed ones. Then ask how they would measure progress.
What Changed Recently
@objc @implementation22. What does @objc @implementation let you do?
Implement a class declared in an Objective-C header with a Swift extension, instead of an Objective-C @implementation block.
The Swift 6.1 release post notes that you can port a class one category at a time without breaking backward compatibility.
// MyFormatter.h stays as it is:
// @interface MyFormatter : NSObject
// - (NSString *)format:(NSNumber *)value;
// @end
@objc @implementation extension MyFormatter {
func format(_ value: NSNumber) -> String { ... }
}
Unlike a normal @objc Swift class, the result is a true Objective-C class, per SE-0436. Objective-C code can subclass it and swizzle its methods.
The hand-written header stays as the documentation, and the compiler checks that every declared method is implemented.
23. Why do Objective-C APIs now produce Swift 6 concurrency warnings?
Apple has been annotating its Objective-C frameworks with concurrency information that Swift reads. The iOS 26 release notes give an example.
Core Data marks NSManagedObject as NS_SWIFT_NONISOLATED NS_SWIFT_NONSENDABLE, marks NSManagedObjectContext as Sendable, and requires Sendable closures for performBlock: and its relatives.
These changes are ABI compatible, so existing binaries behave the same. The new warnings appear when Swift code that uses the API is rebuilt. They point at code that already broke Core Data's threading rules.
Your own headers can use the same macros so Swift callers get the same checks.
24. Why might an older Objective-C app fail to launch after moving to Xcode 27?
Many Objective-C apps set up their window in application:didFinishLaunchingWithOptions: and never adopted scenes. The iOS 27 release notes say apps built with the latest SDK must adopt the scene-based life cycle or they fail to launch.
Apple's TN3187 describes the fix. Add UIApplicationSceneManifest to Info.plist and a class that conforms to UIWindowSceneDelegate. Create the window in scene:willConnectToSession:options:.
Then move foreground and background handling from the app delegate to the scene delegate.
25. Which 2026 App Store requirements affect a legacy Objective-C app?
Three, from Apple's upcoming requirements page and the iOS 27 notes:
- Since April 28, 2026, uploads must be built with Xcode 26 or later and an OS 26 SDK. An app that only built with an old Xcode has to be brought up to date before its next update.
- Since September 9, 2026, iOS apps must target iOS 13 or later.
- Apps built with the iOS 27 SDK must declare a launch screen in Info.plist, which catches very old projects that still rely on launch images.
A candidate who has kept an old codebase shipping will have hit at least one such deadline and can describe what the upgrade broke.
Signs of a Strong Answer
- They can trace a message through
objc_msgSendto forwarding without notes. - They spot the
assigndelegate and the ivar captured in a block in a code review exercise. - They add nullability annotations before writing any Swift that calls the code.
- They know the difference between a category and a class extension, and when associated objects are worth the cost.
- They treat swizzling as a tracked risk with an owner, not a trick.
- They have a plan for migration that names what not to migrate yet.
Hiring Objective-C Developers
Few engineers start new projects in Objective-C, so the useful hire knows it well and also writes modern Swift. Second Talent places pre-vetted iOS developers from Asia, including engineers with years of Objective-C maintenance behind them.
See what an iOS developer costs by country.
Pair this guide with our Swift and iOS developer interview questions, or tell us what you need and we send a shortlist.
Ready to hire? Hire iOS Developers: vetted AI-native talent, with first profiles in 24 hours.





