Norway


Libby the lifelike framework spews some common Xcode errors related to frameworks: "No such module", "ld: framework not found (use -v to see invocation)", "Module not found", and "file was built for archive which is not the architecture being linked (arm64)"  - libby 2 - It Looks Like You're Still Trying to Use a Framework

In It Looks Like You Are Trying to Use a Framework we learned about object files, static and dynamic libraries, frameworks, and their structure. It was a detailed, albeit theoretical view of frameworks.

We’re going to get our hands dirty to understand the inner-workings of frameworks. This will give you guidance on fixing esoteric build errors related to frameworks, and knowledge of how Swift and Objective-C interoperability works at the interface level. We’re going to build a framework without Xcode. Our good friend Libby is not impressed by this undertaking. Why? There is no reason to ever do this in practice. Xcode’s build process for frameworks abstracts most of the work of a framework into Cmd-B. We do this here so that when you come up against compile and link errors, obscured by absolute paths, muttering about dyld, “Module not found”, “Framework not at path”, or my personal least favorite “Here’s your exit code and use -v to see invocation”, you won’t go to StackOverflow (which many times doesn’t help because so many scenarios can yield the same error), but instead tap into your knowledge of frameworks to deduce what is wrong, fix the problem, and get on with coding.

Creating the Empty Framework Bundle

I recommend following along in your favorite shell and Finder to feel that rush when the handmade .framework appears as a functional framework bundle. To prove that the framework works, we’ll use an executable iOS project. Create an empty iOS project in Xcode called “The Shell”. We’re building a rowing . We will develop a framework called “Coxswain”. The Coxswain is the person who sits in the rear of the boat who steers and coordinates the rowers rowing together. We need a coxswain to steer and make calls in the boat (also called a shell) before our shell can get moving on the water.

The University of Washington and 1936 US Olympic Rowing Team, led by their Coxswain, Bob Moch  - boys in the boat - It Looks Like You're Still Trying to Use a Framework

In Xcode’s project navigator, create a folder-backed group called Frameworks. On the command line cd into that folder and type mkdir Coxswain.framework. In Xcode, use the File -> Add Files menu to add your framework to the project. Your filesystem should have this structure

The Shell
├── Frameworks
│   └── Coxswain.framework
├── The Shell
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   ├── Base.lproj
│   │   ├── LaunchScreen.storyboard
│   │   └── Main.storyboard
│   ├── Info.plist
│   └── ViewController.swift
└── The Shell.xcodeproj

and be mirrored in Xcode.

The Xcode Project Navigator mirroring the file system structure  - emptyframework - It Looks Like You're Still Trying to Use a Framework

Adding the Swift Module Definition

When you added the framework to the project, Xcode should have automatically added it to the Linked Frameworks and Libraries under the project settings. If we import Coxswain in ViewController.swift and try to build, we get the error

❗️ No such module 'Coxswain'

Which makes sense. Our linked empty framework doesn’t include a single line of code, much less a module. Let’s make one. Create a directory called Scratch Space, but don’t add it to Xcode. Use your favorite text editor to create a swift file.

CoxCalls.swift

import Foundation

@objc public class Coxswain: NSObject {

    public func steer(left: Bool) {
        print("Steering (left ? "left": "right")")
    }

    @objc public func stroke(count: Int) {
        print("Gimme (count) strong strokes!")
    }

    func talk(to seat: Int) {
        print("Adjust your technique seat (seat)")
    }
}

The name of this file doesn’t affect the module name, which is specified later.

We create a variety of basic functions and signatures to see how each is exposed in Swift and Objective-C interfaces. In Scratch Space, generate the interface files.

$ swiftc -module-name Coxswain -c CoxCalls.swift -target x86_64-apple-ios12.1-simulator 
  -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/
  Developer/SDKs/iPhoneSimulator12.1.sdk 
  -emit-module -emit-objc-header -emit-objc-header-path Coxswain-Swift.h

You might not have the SDK version and simulator listed above. You should safely be able to substitute the version of iOS with which you are working into those path arguments.

This command creates the Swift module and the Objective-C header for our single source file. Let’s break down the arguments:

  • -module-name Coxswain: The name of the module, what comes after import (or @import). Typically this will match the name of the framework.
  • -c CoxCalls.swift The source files to compile
  • -target x86_64-apple-ios12.1-simulator -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.1.sdk These are the target architecture and the SDK to link against. This will compile specifically for the iOS simulator and won’t run on any earlier simulator. This will use the iOS SDK, including the Foundation framework we linked to.
  • -emit-module will emit a .swiftdoc and .swiftmodule for the code we just wrote.
  • -emit-objc-header will emit an Objective-C header, including only those symbols marked @objc.
  • -emit-objc-header-path Notice that we choose to follow Xcode’s naming convention here for exposing Swift symbols to Objective-C by appending our framework name with -Swift.h.
  • If you look in Xcode’s build logs, you’ll see many more build flags. See our post Build Log Groveling for Fun and Profit for an analysis of these flags and additional build steps.

We know from Part 1 of this post that each generated file has a proper place in a
framework. We need to rename our .swiftmodule and .swiftdoc files created
by swiftc to the architecture they represent and keep them in a bundle called Coxswain.swiftmodule.
There is little documentation on this structure. I reverse engineered it by looking
at frameworks created by Xcode. Change the prefix of both of the Swift module files
from Coxswain to x86_64 keeping each extension, then put them both in a folder
called Coxswain.swiftmodule. Copy this to a new Modules/ directory at the
root of the framework directory. Then copy the Objective-C header into a Headers/
directory in the framework. The new framework structure should be:

Coxswain.framework
├── Headers
│   └── Coxswain-Swift.h
└── Modules
    └── Coxswain.swiftmodule
        ├── x86_64.swiftdoc
        └── x86_64.swiftmodule

Xcode now knows about our symbols. Add let cox = Coxswain() to ViewController.viewDidLoad() in the project; notice that Xcode can autocomplete that class name. Try to build again in Xcode. No more module error! Instead we have a new one.

❗️ld: framework not found Coxswain
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Creating the Library

This error looks more intimidating. ld and clang have chimed in. When you see errors like this, take a breath and remember, you know what is going on! The module definition is there, and the compiler knows what symbols it should be finding (the Coxswain class and its public methods). The problem is, at link time, they aren’t there. Thinking back again to a framework’s structure, what is missing? The library! Let’s make that. In Scratch Space, compile the code (run this command with the -v flag to see interesting verbose output and additional flags that will be familiar from Xcode build logs).

$ swiftc -module-name Coxswain -parse-as-library -c CoxCalls.swift -target 
  x86_64-apple-ios12.1-simulator -sdk /Applications/Xcode.app/Contents/
  Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.1.sdk -emit-object

We kept most of the arguments from the previous command, only now we pass -emit-object to output an object file, and -parse-as-library so the compiler interprets our file as a library, not an executable. An ls reveals the new object file, CoxCalls.o. Drawing on knowledge from Part 1, we will archive this into the library format that Xcode expects. We’ll use libtool which is intended to replace the classic Unix archive tool, ar.

$ libtool -static CoxCalls.o -arch_only x86_64 -o Coxswain

Normally, the extension for this file would be .a, but the default name Xcode expects in frameworks is the name of the module only, no extension. Without creating an Info.plist for our framework, we’re constrained to these defaults.

I’ve chosen to create a static framework, rather than the more common dynamically linked framework because it simplifies this step. For dynamic libraries, the location of the libraries used by the object files must be specified when the library is created; these include the Swift core libraries and the wrappers around the Objective-C runtime and Foundation. With a static library, Xcode will handle this linking when it builds the app.

Using the Framework

Copy the Coxswain library into the framework bundle. The structure of our operational, artisanal, organic, free-range, grass-fed framework is:

Coxswain.framework/
├── Coxswain
├── Headers
│   └── Coxswain-Swift.h
└── Modules
    └── Coxswain.swiftmodule
        ├── x86_64.swiftdoc
        └── x86_64.swiftmodule

In Xcode, build the application. The compilation errors should be gone and you will be left with an unused variable warning in ViewController.swift where we added the cox variable earlier. Add a method call to one of the framework’s methods to convince yourself that we were successful.

(1..<).reversed().forEach { cox.stroke(count: $0) }

Now run the app and look in the console for the coxswain’s calls.

Gimme 9 strong strokes!
Gimme 8 strong strokes!
Gimme 7 strong strokes!
...

Congratulations! Look back at the files and folders you’ve created with some pride, this is not easy.

Swift and Objective-C Interoperability

Throughout this process, we took care to make sure the framework would work with Swift and Objective-C code. Making frameworks work with both can be a delicate process. You must adopt modules if you want a hybrid project. When bringing old Objective-C code to a Swift project, this means setting the “Defines Module” (DEFINES_MODULE) build setting to YES in the framework target. This instructs Xcode to install a module.modulemap file (and possibly a module.private.modulemap) alongside the headers in the framework. Objective-C frameworks need to define a module to be used by Swift. Next, we put our framework to the test.

Add an Objective-C class to your app (allowing Xcode to create the bridging header for you). If you use the older #import <Coxswain/Coxswain-Swift.h> syntax for including the framework, the project will build and compile and Objective-C will have access to the functions you declared public and @objc. The newer module import syntax @import Coxswain however, will yield:

❗️Module 'Coxswain' not found

The .modulemap file is missing.

For our Objective-C code to import Coxswain as a module, we need to define a module map, which “describes how a collection of existing headers maps on to the logical structure of a module” (Clang Documentation: Modules). For the benefits of Clang modules and how they fit into Xcode, there is a useful WWDC 2013 session

According to the clang modules documentation, the module map language isn’t guaranteed to be stable yet, so it’s best to let Xcode generate these as well. Our framework structure is simple, so creating a module map isn’t bad (still, I did look to a pre-existing Xcode project’s build products for the structure and syntax of the file):

module.modulemap

framework module Coxswain { // Define a module with framework semantics
    umbrella header "Coxswain.h" // Use the imported headers in the umbrella header to define the module
    export * // Re-export all symbols from submodules into the main module
    module * { export * } // Create a submodule for every header in the umbrella header
}

module Coxswain.Swift { // Define a submodule called Coxswain.Swift
    header "Coxswain-Swift.h" // Use the imported headers to define the module
    requires objc
}

Move this into the Modules directory within the framework, at the same level as Coxswain.swiftmodule.

Adding the Umbrella Header

An umbrella header is also required by the typical module map structure. It is possible to omit this header, but it complicates the module map. Here, we declare constants that clients of frameworks expect to be defined in the framework.

Coxswain.h

#import <UIKit/UIKit.h>

//! Project version number for Coxswain.
FOUNDATION_EXPORT double CoxswainVersionNumber;

//! Project version string for Coxswain.
FOUNDATION_EXPORT const unsigned char CoxswainVersionString[];

Move this into the Headers directory within the framework, at the same level as Coxswain-Swift.h.

We don’t strictly have to import the header Coxswain-Swift.h which declares the symbols
for the Coxswain framework. This is because we defined the Coxswain module
to include the Coxswain.Swift submodule which includes Coxswain-Swift.h in the modulemap. If
not for modules, we would have to import Coxswain-Swift.h.
The final structure of the framework on the filesystem is:

Coxswain.framework/
├── Coxswain
├── Headers
│   ├── Coxswain-Swift.h
│   └── Coxswain.h
└── Modules
    ├── Coxswain.swiftmodule
    │   ├── x86_64.swiftdoc
    │   └── x86_64.swiftmodule
    └── module.modulemap

Objective-C classes in the project should now be able to access Coxswain.framework using modules. If you are tracking this demo project with version control, you might notice that Xcode helpfully set the build setting “Enable Modules (C and Objective-C)” (CLANG_ENABLE_MODULES) to YES when you introduced Objective-C into the Swift project.

Wrapping Up

You’re done! You have successfully created a framework. You have little reason to tremble at load, link, and compile time errors when working with frameworks and hybrid applications. The source code for the sample project can be found on GitHub with tags corresponding to each section above.

For the More Curious

If you have read any of our Big Nerd Ranch Guides you have seen the For the More Curious sections. This is where you’re encouraged to dig deeper after completing a chapter. This post is getting a bit long, but I couldn’t resist including these questions that will solidify the concepts covered in this post and the previous one.

  1. If you navigate to the built products directory (In the Xcode Project Navigator, right-click on your app and select Show in Finder) and look for signs of the Coxswain framework, you won’t find it. Why?

  2. We declared the symbols CoxswainVersionNumber and CoxswainVersionString in
    the framework’s umbrella header, but never defined them. Luckily, no code ever referenced
    these symbols, so our app didn’t crash. Create a new framework project in Xcode.
    Xcode creates a similar umbrella header. Find the definition (where the actual value
    assignment is done) of the version number and string. A clue to where the
    missing symbols are can be found in the build logs. Look for a file ending in _vers.c.
    The DERIVED_FILE_DIR Build Settings Reference and our Understanding the Swift/Objective-C Build Pipeline posts may help.

  3. If you feel comfortable with this process. Dive even deeper. We built our framework for the x86_64 architecture. This allowed us to build and run on the iOS Simulator in Xcode. If we switch the deployment target in Xcode to a device and try to deploy our app, you’ll receive the error:

❗️file was built for archive which is not the architecture being linked (arm64): {absolute/path}/Coxswain.framework/Coxswain ld: warning: ignoring file {absolute/path}/Coxswain.framework/Coxswain

or perhaps

❗️'Coxswain' is unavailable: cannot find Swift declaration for this class

As we built the entire Framework, we know that we don’t have an object file or Swift module for a real device’s arm64 architecture, thus the compiler can’t find the Swift declaration for it. Compile CoxCalls.swift for the arm64 architecture. Use macOS’s libtool and lipo to build a fat binary that includes both the simulator and device architectures. If you make something you’re proud of, share your solution in the comments!



Source link

LEAVE A REPLY

Please enter your comment!
Please enter your name here