The 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:

CreateNewTarget

and name it ExampleSwiftFramework:

DetailsNewTarget

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:

HostApplicationNone

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:

  1. Mark your Swift framework class with @objc annotation, and make it inheritting from NSObject (if you do not, compiler will tell you that you can’t use @objc annotation otherwise).
  2. Make sure your class, the constructor, and the method you want to access from another target are public. The default visibility is internal 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 use public 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:

EmbeddedContentContainsSwiftCode

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:

  1. Main application target - UsingSwiftFrameworksFromObjectiveC. We have to make sure that target depends on the framework, the framework is included in the Link Binary With Libraries build phase, and that the Embed Frameworks build phase contains the framework with the Destination set to Frameworks:

    MainApplicationTargetSettings

  2. The framework - ExampleSwiftFramework target. In our case we do not want it to depend on the main application target UsingSwiftFrameworksFromObjectiveC:

    ExampleSwiftFrameworkTargetSettings

  3. ExampleSwiftFrameworkTests target. It depends on ExampleSwiftFramework only and includes ExampleSwiftFramework in the Link Binary With Libraries build phase:

    ExampleSwiftFrameworkTestsTargetSettings

Additional references

  1. Technical Q&A QA1881 v2 - Embedding Content with Swift in Objective-C
  2. Swift and Objective-C in the Same Project
  3. How to Create a Framework for iOS
  4. Build your own Cocoa Touch Frameworks, in pure Swift