Xcode 6 iOS Creating a Cocoa Touch Framework - Architectures issues
Asked Answered
M

11

50

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.

Middling answered 4/6, 2014 at 13:57 Comment(2)
If you intend to publish a universal fat framework to CocoaPods, I wrote an extensive guide for this: eladnava.com/…Wolford
I have faced same issue in my one of the project task and i have fixed it after lots of R&D and steps mentioned in link here.Ammadas
M
0

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.

Middling answered 5/3, 2018 at 16:54 Comment(0)
S
69

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

Sallet answered 1/11, 2014 at 16:2 Comment(9)
Great post! Question: What if I want to build a framework for Objective-C ? Can you provide appropriate script?Minotaur
Yes, I'm interested too in an ObjectiveC aggregator scriptFinochio
This script not works with xcworkspace. And unfortunately there is no way to get Scheme name from Xcode Build Settings. If someone found solution - please, let me know!Taffrail
Hi! Just updated the gist so that it checks for the existence of the FrameworkName.swiftmodule folder before attempting to copy it. This prevents the error when the framework contains no Swift code and make it usable for Objective-C only frameworks.Sallet
works great but i had to add varius -arch parameters in order to get all architectures built as armv7s was not build somehow.Vegetarian
It is there. In Xcode 7 click File>New>Target... and there select "Other" group, you'll see it.Sallet
@Sallet I tried your instructions i'm always getting error like this "The following build commands failed: Ld build/sampleSDK.build/Debug-iphonesimulator/sampleSDK.build/Objects-normal/i386/sampleSDK normal i386 Ld build/sampleSDK.build/Debug-iphonesimulator/sampleSDK.build/Objects-normal/x86_64/sampleSDK normal x86_64 (2 failures) fatal error: lipo: can't open input file: (No such file or directory) Command /bin/sh emitted errors but did not return a nonzero exit code to indicate failure"Pettifog
Hi, I successfully build framework with your instructions in Xcode 8.3. Will there be any problem when we release framework to appStore?Rustication
followed the steps!! working fine with one attempt.... Thanks, guys for the help.Hent
P
47

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.

Photomap answered 18/6, 2014 at 21:14 Comment(21)
Is it possible to create the framework using swift files instead of the Objective C class as indicated in the second step of the Framework project above?Plumber
@Chris Conway, it appears not possible as discussed here, but I have seen articles about importing swift modules, e.g here. Hope it helps.Photomap
Thanks @vladof, I had seen both discussions you mentioned above, but was hoping things were progressing with each beta incrementation. One would think that Apple would support building Frameworks using Swift since it is one of the options available in the project creation UI.Plumber
@Chris Conway, I agree, it's imperative to support swift framework. (It may be on hold in consideration of beta software distribution.)Photomap
thanks for your comments! I've gone and tried to create the universal framework using the lipo tool you suggested, but when I add the generated executable file to a project, it can't find the headers. Adding a single framework works fine, but I can't switch between device and simulator without compile errors. Have you been able to successfully use the universal executable in an app?Plumber
@Chris Conway, I recall I had a bit luck run the app (embedded framework is built against iphoneos) on devices with Beta 1, and same app runs fine on simulator. I didn't even need to lipo the binaries. I haven't tried other betas since then. I would presume that Product Archive in framework does distribution packaging, which however only works for non-swift framework.Photomap
Thanks @vladof, I was able to get it to work with the latest beta (5) using the lipo tool and some finagling of the files. Let me know if you ever run into issues and I can send you a gist of what I did.Plumber
@Chris Conway, that's awesome, I do want to look into that. Would you mind? Thank you.Photomap
So what I had to do was add an Aggregate target to my project, add a run script to the build phases, and I used this gist as the script: gist.github.com/cconway25/7ff167c6f98da33c5352Plumber
Just as a follow up, basically the lipo tool generates an executable, but that file is not sufficient enough to use as a framework by itself. So I used the framework folder and file structure of the regular build of the framework, and replaced the executable file within the framework folder with the one that the lipo tool generates. This allows me to drag the framework into another project and build against both simulator and device.Plumber
@ChrisConway - Just to be sure I understand you. We had been using Universal Frameworks for over a year now and the latest version broke our SDKs. The latest word is that you no longer need to add an Aggregate target and run script build phase. So you say you needed to still add this to your Universal Framework? So is the real only difference, the script? Or can you tell me other differences, so I can quickly change my existing Universal Frameworks to work using the latest version of iOS and Xcode. Thanks a million for your reply.Countrywide
@ChrisConway - I just asked a question about this topic here: https://mcmap.net/q/206119/-has-anybody-upgraded-existing-universal-framework-for-ios-8-and-xcode-6/1735836Countrywide
@Photomap - I tried to follow your example to just create the Workspace and Framework project, but when I try to clean or build I receive this error: google.com/… I can't find any information on this. How did you get yours to build?Countrywide
@Lucy you might have special characters in Product Name $(TARGET_NAME) in Build Settings, try avoid using them.Photomap
You are correct! My project used to contain "-iOS" in the name; latest version doesn't seem to like dashes.Countrywide
@Photomap - Did you investigate / use an Aggregate target with the build run script that Chris mentions above?Countrywide
@Photomap - Did that work for you? I followed Chris' instructions and added the Aggregate target with the build run script but I still do not see the (Debug/Release)-iphoneuniveral product. Am I supposed to have that product? Did you ever get this working? I'm confused.Countrywide
@Photomap - Ah-ha! I forgot to select the Aggregate scheme to build and run! But I'm still having issues with the build script: fatal error: lipo: can't open input file: /Users/pdl/Library/Developer/Xcode/DerivedData/License-blnxjfvoxnqfelftmzojgdwhvazk/Build/Products/Debug-iphonesimulator/License.framework/License (No such file or directory)Countrywide
@Photomap - Hey, me again! I have created a new question that you might be able to answer. Me and a coworker can't seem to get on the same page. Can you help solve our confusion? Here: https://mcmap.net/q/206120/-confused-about-different-procedures-for-creating-a-fat-static-library-in-xcode-6/1735836Countrywide
@chris-conway It seems, that this script not works with xcworkspace. And unfortunately there is no way to get Scheme name from Xcode Build Settings. If someone found solution - please - let me know!Taffrail
@Photomap why only Debug- is used? Is the framework built with this command suitable to be used with AppStore / AdHoc builds?Malvie
S
10

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.

Sassaby answered 24/6, 2014 at 17:39 Comment(2)
This seems suitable for a small number of projects you want to include the framework in. However you don't have the ability to maintain different versions of the compiled framework what will be an issue in bigger projects.Wooten
Works well for me! The only thing I needed to change was setting "Defines Module" to YES in building settings on the parents target (in this case it would be the MyApp target)Panache
T
7

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}"
Trapes answered 10/7, 2015 at 20:21 Comment(0)
A
2

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

How to Create a Cocoa Touch Framework Using Xcode 6

Allistir answered 2/12, 2014 at 10:15 Comment(2)
This link is not available anymore.Lent
@Brduca Try the youtube videoAllistir
A
1

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 ().
Autrey answered 6/7, 2014 at 15:50 Comment(0)
S
1

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

Scevour answered 17/1, 2015 at 16:2 Comment(2)
line 9: -sdk: command not found ------ line 16: -output: command not foundFinochio
is there a way to run such script for xcworkspace?Taffrail
F
1

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

Fourpenny answered 25/10, 2018 at 18:31 Comment(0)
L
1

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.

Lachrymal answered 8/1, 2019 at 3:22 Comment(0)
M
0

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.

Middling answered 5/3, 2018 at 16:54 Comment(0)
U
0

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

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

[Vocabulary]

Unleavened answered 26/9, 2022 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.