Xcode 6 iOS Creating a Cocoa Touch Framework - Architectures issues

54.3k views Asked by At

I'm trying to make a dynamic framework for an iOS app. Thanks to the new version of Xcode (6) we can select a Cocoa Touch Framework when we create a new project and there is no more need to add an aggregate target, run script and so to make one. I have no issue when i build the framework. But when I'm trying to use it inside an iOS app I get some architectures issues.

ld: warning: ignoring file /Library/Frameworks/MyFramework.framework/MyFramework, file was built for x86_64 which is not the architecture being linked (arm64): /Library/Frameworks/MyFramework.framework/MyFramework
Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_MyFrameworkWebService", referenced from:
      objc-class-ref in AppDelegate.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

ld: warning: ignoring file /Library/Frameworks/MyFramework.framework/MyFramework, file was built for x86_64 which is not the architecture being linked (armv7): /Library/Frameworks/MyFramework.framework/MyFramework
Undefined symbols for architecture armv7:
  "_OBJC_CLASS_$_MyFrameworkWebService", referenced from:
      objc-class-ref in AppDelegate.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Well I have tried to change the settings of the framework project and target (Architectures & Build valid architecture only & Valid architectures). I've done the same thing for the iOS app project but nothing works. I think there is something I didn't understand.

For example when I build a framework for only i386 (iOS Simulator) check with the command line "xcrun lipo -info MyFramework", I have an issue that

ld: warning: ignoring file /Library/Frameworks/MyFramework.framework/MyFramework, file was built for x86_64 which is not the architecture being linked (i386)...

If someone can help me to get a framework that works for all iOS architectures including simulators.

11

There are 11 answers

0
François Delaplace On BEST ANSWER

Sorry I've let this topic open for such a long time... Some of you has correctly answered to my question. When I wrote this question I didn't catch there were two slices in a complete (also called fat) framework (ready to use). One for the iOS devices and one for the simulator. When I discovered that I made a script using lipo command to fusion the two slices and got automatically a complete framework.

2
naz On

An old video I did since the above link earlier is not available anymore.

How to Create a Cocoa Touch Framework Using Xcode 6

2
Tokuriku On

Finally, I got it to work for me! And sorry for the big yellow frame, I have no idea how to format it better.

The solution came from Claudio Romandi but the script linked has a minor problem in it. I can't comment on his post for I need 50 reputation so I'm left with little choice but to copy his post to have a full solution..

  1. Create an new (Aggregate) Target in your Framework Project
  2. Select the Aggregate in the Workspace and add a "New Run Script Phase"
  3. Put the Following Code in it:

    #!/bin/sh
    
    UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
    
    # make sure the output directory exists mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
    
    # Step 1. Build Device and Simulator versions xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION}
    -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
    
    # Step 2. Copy the framework structure (from iphoneos build) to the universal folder cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
    
    # Step 3. Copy Swift modules (from iphonesimulator build) to the copied framework directory cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
    
    # Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory lipo -create
    -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"
    
    # Step 5. Convenience step to copy the framework to the project's directory cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"
    
    # Step 6. Convenience step to open the project's directory in Finder open "${PROJECT_DIR}"
    
  4. Select the Aggregate in the Scheme Selection Drop Down

  5. Build, you're done!

The problem was that the simulator directory was pointing to a non existant directory, changing "Framework" to "${PROJECT_NAME}" in 2 places did the trick :)

21
vladof81 On

Try the following steps to create a workspace that contains a framework project and an app project.

Workspace:

  • Create a Workspace.

Framework project:

  • Create an iOS Cocoa touch Framework project inside Workspace.
  • Add a simple Objective C class MyClass (header .h and implementation file .m), and create a method - (void)greetings.
  • Go project Build Phases > Headers > Move MyClass.h from Project to Public.
  • Change scheme to framework project and choose iOS simulator, then build. (Choose iOS Device if the app that integrates this framework runs on device. I will continue to use simulator in this example)
  • You should have no issue building, the framework build is found in your Derived Data directory which you can find in Organizer.

App project:

  • Create a Swift Single View application inside Workspace.
  • Drag above iOS simulator framework build (Found in Debug-iphonesimulator or Release-iphonesimulator) to your project.
  • Create a bridging header to expose Objective C class methods to Swift.
  • Import MyClass.h in bridging header file.
  • Note that if MyClass definition is not found, then add framework Headers path of to Build Settings Header Search Paths.
  • Create instance of MyClass in viewDidLoad of ViewController.swift, then call greetings.
  • Add framework to Target > Embedded Binaries
  • Change scheme to app project and choose iOS simulator, then build.
  • You should be able to see greeting messages.

Note that above example demonstrates how to build an app that runs in simulator. If you need to create universal static library that runs on both simulator and devices, then general steps are:

  • Build library for simulator
  • Build library for device
  • Combine them using lipo

There are good references on the web about it, here for example.

Create universal binary for framework: navigate to framework Derived Data directory then /Build/Products, following command should help you create a universal binary in Products directory:

lipo -create -output "framework-test-01-universal" "Debug-iphonesimulator/framework-test-01.framework/framework-test-01" "Debug-iphoneos/framework-test-01.framework/framework-test-01" 

Note that framework-test-01 is my framework project name.

2
pizzafilms On

The way I did it is similar to vladof, but hopefully a little simpler. I made the framework a subproject of the app project.

Framework project

  • Create a new iOS Cocoa Touch Framework. Call it MyLib. This will create a single MyLib.h
  • Add a simple Cocoa Touch Obj-C class, MyClass (.h & .m) and in the implementation of the .m, create a method that returns a string, - (NSString *)greetings;
  • In MyLib.h, add this near the bottom, #import "MyClass.h"
  • In the target's Build Phases/Headers section, Move MyClass.h from the Project section to the Public section.
  • Do a Build (cmd-B)
  • Close the the project

App project

  • Create a new Single View application project, either Swift or Obj-C. Call it MyApp.
  • From the Finder, drag your MyLib project file to the left hand organizer section of you MyApp window and make sure the insertion line is just below MyApp. This makes MyLib a subproject of MyApp. (It can still be used the same way in other projects)
  • Click on MyApp in the organizer and then select the MyApp target and select Build Phases.
  • In Target Dependancies, click the + sign and add MyLib.
  • In Link with Libraries, click the + sign and add MyLib.framework.

For an Obj-C app

  • In ViewController.m, add #import
  • In viewDidLoad, add the following lines:
  • MyLib *x = [[MyLib alloc] init];
  • NSLog(@"%@", x.greetings);
  • Run the project and you should see the message in the debug window. -

For a Swift app

  • In ViewController.swift, add import MyLib
  • in viewDidLoad, add the following lines:
  • var x: MyLib = MyLib()
  • println("(x.greetings())") -

By doing it this way, the app is dependent on the framework so if you make a change in the framework classes, you don't have to change targets to build the framework separately, it will just compile the framework first, then the app.

0
nyus2006 On

This question was posted awhile ago(Xcode 6) but I encountered the same problem recently with Xcode 10.

So the problem is: the built framework doesn't support enough architectures.

In case one doesn't know what architectures stand for, different iPhone devices have different architectures, here's a complete list:

  • arm6: iPhone 1, 2, 3G
  • arm7: Used in the oldest iOS 7-supporting devices[32 bit]. iPhone 4, 4s
  • arm7s: As used in iPhone 5 and 5C[32 bit]. iPhone 5, 5c
  • arm64: For the 64-bit ARM processor in iPhone 5S[64 bit]. iPhone 5s and above.
  • arm64e: used on the A12 chipset. iPhone XS/XS Max/XR
  • i386: For the 32-bit simulator
  • x86_64: Used in 64-bit simulator

So, if you are using the framework on simulator, the framework needs to support either i386 or x86_64; if you are running your app on iPhone 6, the framework needs to support arm64 architecture.

Therefore, in most cases, a framework needs to support all the aforementioned architectures.

Now back to how to solve the problem. We need to build the framework for both devices and simulators.

How to build for devices:

  1. In "General", specify "Deployment Target".
  2. In "Build Settings", turn "Build Active Architectures Only" to "No"
  3. In "Build Settings", make sure "Valid Architectures" lists all the architectures you need. Usually we just use the defaults options(arm64, arm64e, armv7, armv7s).
  4. Go to "Edit Scheme" -> "Run" -> "Build Configuration", change "Build Configuration" to "Release"
  5. Select active Scheme "Generic iOS Device". Hit Cmd+B to build the project.
  6. Right click on [MyFrameworkProject].framework under "Products" folder in your Xcode project, and hit "Show in Finder".[MyFrameworkProject].framework supports all the architectures specified in step 2.
  7. Drag [MyFrameworkProject].framework to the project that needs to use this framework. Additionally, drag [MyFrameworkProject].framework to "Embedded Binaries" as well.

How to build for simulators:

  1. Step 1 to 4 same as above.
  2. Select any simulator as active Scheme . Hit Cmd+B to build the project.
  3. Step 6 to 7 same as above.

How to build for both devices and simulators:

After you have the framework for devices, you will need to combine the framework for simulators and the framework for devices together with "lipo" command. Rename the framework for simulators to [MyFrameworkProject]_sim.framework and copy both frameworks to the same folder. Run command below in Terminal(make sure you are in the folder):

lipo -create -output [MyFrameworkProject].framework/[MyFrameworkProject] [MyFrameworkProject].framework/MyFrameworkProject [MyFrameworkProject]_sim.framework/MyFrameworkProject

Now [MyFrameworkProject].framework is the final product that supports both simulators and devices.

9
cromandini On

Based on all the responses, the post on raywenderlich.com and the gist created by Chris Conway I came up with this.

Executing the following steps I was able to build a Cocoa Touch framework (including Swift and Objective-C files) that contains all architectures for both simulator and device:

  1. Create a new (Aggregate) target in your framework's project
  2. Under "Build Phases" select "Add Run Script" and copy the contents of this file
  3. Select the Aggregate target in the Scheme Selection drop down
  4. Build the target for the aggregate scheme

Hope it helps :)

UPDATE: Fixed an error in the gist where the paths in step #3 were incorrect. Thanks to Tokuriku!!

UPDATE In Xcode 7 and 8, click File>New>Target... and there select "Other" group to select Aggregate target

0
Nadzeya On

I've changed someone's script a bit to support all Simulator's architectures:

UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal

# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

# Step 1. Build Device and Simulator versions
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator VALID_ARCHS="x86_64 i386" BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

# Step 2. Copy the framework structure to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"

# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"

# Step 4. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"

# Step 5. Delete temporary build directory in the project's directory
rm -rf "${PROJECT_DIR}/build"

# Step 6. Convenience step to open the project's directory in Finder
open "${PROJECT_DIR}"
0
yousefnjr On

Wanted to add something to the lipo script answer provided by skywinder here. I followed the steps but still couldn't get my framework to run on simulator, only device. To fix that:

-I went into the debug-iphonesimulator version of the framework, and in Modules/[FrameworkName].swiftmodule, I copied all of the i386 and X86 files.

-I then went into the newly-created fat version of the framework, and navigated to that same folder. I pasted in the i386 and X86 files (to go with the ARM files already in there), and then added my fat framework to my project.

Voila, she works on simulator and real device! This is Xcode 10 / Swift 4.2 btw

0
yoAlex5 On

You are able to create aggregate target[About] with lipo[About] script

As a next step take a loot at XCFramework[About]

[Vocabulary]

0
jhash On

Few additional points around the approach shared by vladof that are more applicable to swift based Frameworks

  1. The script in the associated link needs modification to copy all files from both iphonesimulator and iphoneos since the swift has separate compiled files for arm and i386
  2. Ensure that you have ARCHS="x86_64" ONLY_ACTIVE_ARCH=NO in the build for iphonesimulator to avoid linker issues for simulator
  3. Ensure that your interface/class extends NSObject otherwise you will run into issues when you try to use the code in swift (it will complain about not being able to create object using ().