using swift frameworks from objective-c target
Jan 17, 2016The code in this post was tested with Xcode Version 7.2 (7C68).
The accompanying source code can be found at github.com/marcinczenko/UsingSwiftFrameworksFromObjectiveC
When working with legacy iOS products it may happens that you need to integrate with main application target that contains only Objective-C code. You want to take advantage of Swift but you want this nicely separated from the old legacy code. Creating a framework that will contain your Swift code is the answer.
There is no something like a Swift framework. A framework can include both Objective-C and Swift code. In this short post we focus on a framework containing only Swift code.
If you want to share your Swift code, a framework is necessary. You can’t just use a static library as they do not support Swift code.
The following steps describe the whole process of creating a framework and adding it to an Objective-C-only target.
If you want to follow with the source code from github.com/marcinczenko/UsingSwiftFrameworksFromObjectiveC please clone the repository and checkout the very first commit with comment
Initial commit.
(commit 73e8dde) and then follow the steps. The last commit corresponds to the final state.
Step 1 - add a taget
Select your project in the Project Navigator and create a new target hey:
and name it ExampleSwiftFramework
:
Step 2 - make the tests logical
The idea of Logical Unit Tests is less widely used in the Xcode community, but you can still make sure that your framework tests execute only against your framework code without depending on the application code (no application instance, no app delegate, no views, etc). Even though Apple does not seem to distinguish anymore between application unit tests and logic unit tests, the latter is what we want here: your tests working only with the Swift code in your framework. For this to work you need to make sure that the Host Application
seeting for the ExampleSwiftFrameworkTests
is set to None
:
Logic unit tests cannot be run on the device. They may only run in the simulator.
Step 3 - add example framework class
Now inside our example framework, add an example swift class:
import Foundation
class ExampleSwiftFrameworkClass {
let message: String
init(withHelloMessage message: String) {
self.message = message
}
func sayHello() -> String {
return self.message
}
}
The tests for this class can be found in the ExampleSwiftFrameworkTests.swift
file in the ExampleSwiftFrameworkTests
target.
Step 4 - referring to the Swift framework from the main application target
Now, let’s try to reference the ExampleSwiftFrameworkClass
from our main application target - which does not contain any Objective-C code.
We will be modifying the viewDidLoad
method of the ViewController
class from the main application target. You will quickly realise that Xcode code completion does not work for our class - in this case it right - our class is not visible yet in the main application target.
To make our class visible you need to import the module it comes from. Add the following line somewhere on the top of your ViewController.m
file:
@import ExampleSwiftFramework;
You may realise that the Xcode’s completion already recognized the module, but the our ExampleSwiftFrameworkClass
remains invisible to Xcode. To fix this, we need to do two things:
- Mark your Swift framework class with
@objc
annotation, and make it inheritting fromNSObject
(if you do not, compiler will tell you that you can’t use@objc
annotation otherwise). - Make sure your class, the constructor, and the method you want to access from another target are
public
. The default visibility isinternal
which means your class and all its methods are accessible to any other class from the same target. But our framework and our main application are from different targets - that’s why we need to usepublic
for access control.
After the modifications, our ExampleSwiftFrameworkClass
should look like this:
@objc
public class ExampleSwiftFrameworkClass : NSObject {
let message: String
public init(withHelloMessage message: String) {
self.message = message
}
public func sayHello() -> String {
return self.message
}
}
If you try to run the app or the application tests (not framework tests) you may be surprised seeing that the app crashes with the following log output:
dyld: Library not loaded: @rpath/libswiftCore.dylib
Referenced from: /Users/usename/Library/Developer/Xcode/DerivedData/UsingSwiftFrameworksFromObjectiveC-befsyfovlmeizgeypzhxolfxbaco/Build/Products/Debug-iphonesimulator/ExampleSwiftFramework.framework/ExampleSwiftFramework
Reason: image not found
Xcode is quite intelligent in fixing most of the stuff for us (we will discuss this still in more details below), but there are somethings we have to take care for ourselves.
Step 5 - Fixing the missing reference to libswiftCore.dylib
If you are building an app that does not use Swift but embeds content such as a framework that does, Xcode will not include these libraries in your app. As a result, your app will crash upon launching…
The text quoted above comes from the Technical Q&A QA1881
Embedding Content with Swift in Objective-C. As resultion it instructs to set Embedded Content Contains Swift Code
build setting to YES
for the target that does not include Swift code itself but depends on a framework that does use Swift. Additionally, it recommends setting the Embedded Content Contains Swift Code
to NO
for the dependent framework target so that we do not get multiple copies of Swift libraries in the app.
In our example therefore, the only target where we need to set Embedded Content Contains Swift Code
to YES
is the main application target UsingSwiftFrameworksFromObjectiveC
:
Other settings
We mentioned already that Xcode takes care for pretty mych everything. In some cases, however, especially when you deal with a legacy project, it may turn out handy to know what else may require our attention. We summarize the important points below:
Main application target -
UsingSwiftFrameworksFromObjectiveC
. We have to make sure that target depends on the framework, the framework is included in theLink Binary With Libraries
build phase, and that theEmbed Frameworks
build phase contains the framework with theDestination
set toFrameworks
:The framework -
ExampleSwiftFramework
target. In our case we do not want it to depend on the main application targetUsingSwiftFrameworksFromObjectiveC
:ExampleSwiftFrameworkTests
target. It depends onExampleSwiftFramework
only and includesExampleSwiftFramework
in theLink Binary With Libraries
build phase: