Mixing Swift and Objective‑C
I was responsible for migrating a very large Objective‑C app to Swift. To limit effort, we decided not to rewrite the entire thing in Swift (that would have taken years, probably). Instead we decided to do all new development in Swift, and to simply add Swift compatibility as-needed to the codebase.
The official docs are okay on this topic:
But I developed this guide to more quickly help other developers of the app with the tricky situations we often ran into.
Making Objective‑C nicer to use in Swift
- Annotate types in your headers with
nullable
ornonnull
so they do not get imported as implicitly unwrapped optionals (more info) - Declare enums using
NS_ENUM
and options sets usingNS_OPTIONS
(more info) - Declare custom NSError codes using
NS_ERROR_ENUM
(more info) - Declare methods that can return an NSError by making their last parameter a
NSError**
namederror
, such that Swift converts it to athrows
method - Swift should be able to import Objective‑C-style method
names
doThingWithArg1:AndArg2:
as Swift-style method namesdoThing(arg1:arg2:)
. If Swift imports something with an Objective‑C-style method name, useNS_SWIFT_NAME
to fix it (more info)
Swift class name gotchas
By default, Swift class names are prefixed by a module namespace and Objective‑C classes are not.
For example, if you add a class to MyFramework in Swift its full name is MyFramework.NewClass but in Objective‑C it’s just NewClass. Examples of where this matters and will cause errors:
- You’re trying to get the class name as a string in Objective‑C. You need to
use
NSStringFromClass(myClass).pathExtension
to drop the namespace - You’re trying to get the class from a string in Objective‑C. You either need
the namespace
NSClassFromString(@"NameSpace.MyClass")
or rename the Swift class in Objective‑C by marking it with@objc(MyClass) public class MyClass
- When using a Swift class for an entity in CoreData, the namespace matters.
Select the entity in the data model and check the right pane where it says
“Module”. This determines what namespace is used for looking up the class. If
you use the wrong one, CoreData will fail to construct entities of that type
(you’ll get “unrecognized selector” errors because the entities won’t be the
right class).
- For example, if you rename the Swift class using
@objc(MyClass)
you should clear the module field so that it says “Global namespace” - You can open the data model XML in a text editor to see what the actual
lookup will be. It’s the value of the
representedClassName
attribute
- For example, if you rename the Swift class using
Trying to use Swift stuff in an Objective‑C .m file
First make sure you aren’t trying to use something that is Swift-only and thus not visible in Objective‑C:
- Generics
- Tuples
- enums without Int raw value type
- structs
- Top-level functions (outside a class)
- Global variables (outside a class)
- Typealiases
- Swift-style variadic functions
- Nested types
- Curried functions
- Inheriting from Swift classes
If the thing is not on that list, then:
- If the Swift is in a framework, all Swift
classes/enums/inits/methods/properties you need to use must be marked public
and @objc
- You can omit the @objc if Swift is inheriting/overriding something from Objective‑C, but it never hurts to include it
- Import in the .m depending on where the Swift is located
- If in a framework,
#import <FrameworkModuleName/FrameworkModuleName-Swift.h>
- If in an app,
#import "Objective‑C_Generated_Interface_Header_Name.h"
(you can find this in Build Settings for the app target e.g."MyApp-Swift.h"
)
- If in a framework,
Error messages
- “Expected a type”
- “Cannot load nib in bundle xxxx”
Trying to use Swift stuff in an Objective‑C .h file
See the note in the previous section about Swift stuff that cannot be used in Objective‑C, then:
- If the Swift is in a different framework from the .h,
#import <FrameworkModuleName/FrameworkModuleName-Swift.h>
- Else if they’re in the same target, do not import. Instead forward-declare:
- For a class,
@class MyClass;
or protocol@protocol MyProtocol;
- For an enum,
typedef NS_ENUM(NSInteger, MyEnum);
(make sure the type matches the raw enum type)
- For a class,
- Note that Objective‑C classes cannot inherit from Swift classes
Error messages:
- “Expected a type”
- “Cannot find interface declaration”
Trying to use Objective‑C stuff in Swift, the Objective‑C is in a framework (not in an app)
- Import the Objective‑C header in the umbrella header for the framework as
#import <ModuleName/HeaderName.h>
- If there is no umbrella header add one. At the root of the framework add a
public header named
ModuleName.h
- You might need to import Foundation or UIKit in the umbrella header if your framework depends on those things
- If there is no umbrella header add one. At the root of the framework add a
public header named
- Ensure the header has public visibility in the framework
- Apply 1 and 2 recursively: if the header imports other headers from the framework they should also be public and in the umbrella header, and so on
- If your Swift file is not in that framework,
import ModuleName
Trying to use Objective‑C stuff in Swift, not in a framework (e.g. just in an app)
- Import the header into the Swift bridging header
- If there is no bridging header, you can check the filename for it in the target’s build settings. You can set it manually and create that file if none exists for some reason
- If Swift says it can’t find something that you put in the bridging header, try
building first to regenerate the header. Usually the error goes away.
- It won’t regenerate the header if something else is stopping it from building, like errors in framework dependencies. Xcode > Preferences > General > “Continue building after errors” can help here
Can’t call Objective-C method from Swift
There is only one exception I know of to Objective-C methods being automatically visible to Swift. If your Objective-C method involves Swift enums from the same target, you must forward-declare them (see above). Unfortunately this creates an incomplete type definition in the header.
When the compiler tries to use that header to create the Swift interface (which happens before the Swift is compiled), it sees only a method using an incomplete type, which it cannot translate to Swift. Thus the Objective-C method will be omitted from the generated Swift interface.
There are several workarounds to this:
- move the enum declaration to a public Objective-C header
- move the method using the enum to a Swift extension
- move the enum to a separate framework such that it can be built independently and imported into both Swift and Objective-C
Swift header not found
The -Swift header is only created if Swift files are in the framework
- Check that the Swift files are in the right location
- Make sure all Swift files have the right target membership
Error messages:
- “‘FrameworkModuleName/FrameworkModuleName-Swift.h’ file not found”
- “Header ‘FrameworkModuleName-Swift.h’ not found”