If you’re maintaining an Objective-C app and want to start using Swift for newer features, you don’t need to rebuild everything from scratch. Swift and Objective-C are designed to work together within the same Xcode project, allowing you to call Swift code directly from Objective-C.
This setup makes it easy to modernize existing codebases while taking advantage of Swift’s cleaner syntax and safety features without disrupting what already works.
How Swift and Objective-C Work Together?
Swift and Objective-C share the same runtime environment on Apple platforms, which allows both languages to interact naturally inside the same project. This means that an Objective-C class can call Swift methods, and Swift code can use Objective-C objects without any special conversion layer.
When you add Swift code to an Objective-C project, Xcode automatically creates a special header file named after your product module, followed by “-Swift.h”. This file acts like a bridge, exposing your Swift classes and methods to Objective-C. You simply need to import it in your .m file:
#import "MyApp-Swift.h"
int main() {
Greeter *greeter = [Greeter new];
[greeter sayHello];
return 0;
}
This auto-generated header contains all Swift declarations that are visible to Objective-C, so once imported, your existing code can interact with Swift objects just like it does with regular Objective-C classes.
This shared environment is what makes Swift integration straightforward, allowing both languages to exist in one project without major restructuring.
Making Swift Code Visible to Objective-C
To make Swift code usable in Objective-C, you need to make sure the Swift types are exposed correctly. Swift doesn’t automatically share everything with Objective-C, only specific kinds of declarations can cross that boundary.
For a Swift class to be visible, it must
- Inherit from NSObject (or another Objective-C compatible base class).
- Be marked with the @objc attribute (or wrapped in @objcMembers).
This tells the compiler that the class and its members should be available to the Objective-C runtime.
Here’s an example:
@objc class Greeter: NSObject {
func sayHello() {
print("Hello from Swift")
}
}
After you build the project, Xcode generates a header file named after your product module, such as MyApp-Swift.h. You can import it in any Objective-C .m file:
#import "MyApp-Swift.h"
Greeter *greeter = [Greeter new];
[greeter sayHello];
Only Swift declarations marked as public, open, or internal (when using a bridging header) will appear in this generated file. Anything marked as private or fileprivate stays hidden from Objective-C.
You can find or customize your module name under Build Settings > Packaging > Product Module Name. Keeping it consistent helps maintain clarity, especially in large mixed-language projects.
Note: Swift structs, enums, and generics are not exposed to Objective-C. Only class, @objc protocol, and compatible members can cross the bridge.
Using Forward Declarations
When your Objective-C header file needs to reference a Swift class, importing the entire generated Swift header directly inside it can cause circular dependencies. This happens because both languages try to load each other’s declarations before the project has fully compiled.
To avoid this, you can forward declare the Swift class or protocol in your Objective-C header file. This tells the compiler that the class exists, without importing its full definition. It keeps compilation fast and prevents reference loops.
Here’s a simple example:
// MyObjcClass.h
@class Greeter;
@interface MyObjcClass : NSObject
- (Greeter *)createGreeter;
@end
By using @class, you declare the existence of Greeter so you can use it as a return type or property without importing the Swift header here. You can then safely include “MyApp-Swift.h” inside the .m file instead:
// MyObjcClass.m
#import "MyObjcClass.h"
#import "MyApp-Swift.h"
@implementation MyObjcClass
- (Greeter *)createGreeter {
Greeter *greeter = [Greeter new];
[greeter sayHello];
return greeter;
}
@end
Wrapping Up
Mixing Swift and Objective-C lets you modernize an existing codebase step by step instead of rewriting it from scratch. It allows developers to gradually adopt Swift’s cleaner syntax and safety features while preserving the reliability of proven Objective-C components.
As a best practice, keep the boundaries between the two languages well-defined. Use @objc only for classes or methods that must be exposed, forward declare Swift classes in Objective-C headers to avoid circular imports, and include the auto-generated Swift header only in .m files.
Maintaining this discipline ensures faster builds, cleaner dependencies, and a smoother transition as your project moves toward a more Swift-based structure.