Faking an iOS Framework

Ever wanted to share your iOS code with others without having them copy your code and headers and hope it compiles properly? Well you can easily do that with frameworks. I had found several guides on how to do this but they were either out-of-date or incomplete. While the iOS App Store doesn't allow you include dynamic frameworks, you can create a relocatable static framework which functions very simliar with one key difference (more on this later). One more thing we can do to simplify distribution is to create a universal framework so that they same file works on the iOS device (armv7) and in the simulator (i386).

Let's get started by creating a new project in Xcode.

We're going to create a Mac OS X Bundle (and change it into an iOS bundle).

First we're going to replace the contents of the prefix header (Xcode puts #import <Cocoa/Cocoa.h> in it regardless of what framework you chose to base the project on).

#import <UIKit/UIKit.h>

Now we are going to change some Build Settings so we build an iOS Framework.

Group Setting Value
Architectures Base SDK Latest iOS
Architectures Standard (armv7)
Build Active Architecture Only NO
Linking Dead Code Striping NO
Link With Standard Libraries NO
Mach-O Type Relocatable Object File
Other Linker Flags -ObjC
Packaging Wrapper Extension framework
User-Defined GCC_GENERATE_DEBUGGING_SYMBOLS NO

Now for the Build Phases. Start with deleting the CoreFoundation.framework entry from Link Binary with Framework. Now click on Add Build Phase.

Now click Add Copy Headers and then click Add Build Phase again and click Add Run Script.

Now rename the run script phase to Build Universal Binary. And this is the script we'll be using.

[[ -n "$BUILD_FOR_UNIVERSAL" ]] && exit

if [[ "$PLATFORM_NAME" == "iphoneos" ]]; then
  platform="iphonesimulator"
else
  platform="iphoneos"
fi

xcodebuild -configuration "$CONFIGURATION" -target "$TARGETNAME" -sdk "$platform" BUILD_FOR_UNIVERSAL="yes" OBJROOT="$OBJROOT" SYMROOT="$SYMROOT"

other_platform_build_dir="$SYMROOT/$CONFIGURATION-$platform"
universal_build_dir="$SYMROOT/$CONFIGURATION-iphoneuniversal"

mkdir -p "$universal_build_dir"
cp -rf "$TARGET_BUILD_DIR/$FULL_PRODUCT_NAME" "$universal_build_dir"

rm -f "$universal_build_dir/$EXECUTABLE_PATH"
lipo -create "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" "$other_platform_build_dir/$EXECUTABLE_PATH" -output "$universal_build_dir/$EXECUTABLE_PATH"

Now before trying to build we need to add some code (otherwise the script will fail since no binary will be produced). Let's create an Objective C Class.



And in following with Apple standards, we'll create a header that includes everything from our framework.


And we'll insert our imports into this header.

#import <Saozap/SaozapWidget.h>

Now back to the Build Phases and under Copy Headers move the two headers from Project into Public.

Now we're ready to build. Once the build has completed you should now have a framework sitting in the build products directory under Debug-iphoneuniversal. Use this from the terminal if you want to copy the framework to your desktop (or anywhere else).

cp -r ~/Library/Developer/Xcode/DerivedData/Saozap-*/Build/Products/Debug-iphoneuniversal/*.framework ~/Desktop

In a iOS application project, add the framework and then wherever you need it import the header we created and start using our class.

#import <Saozap/Saozap.h>

SaozapWidget * w = [[SaozapWidget alloc] init];
NSLog(@"%@", w);

Now you may have noticed we removed the framework(s) from Link Binary with Frameworks and might be wondering how we can still use NSObject. That's because by building a relocatable object file we are no longer linking with anything and instead defer the entire process to the consumer project. This means if you use any frameworks you must get the developer consuming your framework to add the framework(s) to their Link Binary with Frameworks step otherwise their build will fail with missing symbols.

One last piece of advice: if you are including code from other libraries in your code (like SBJson or CocoaAsyncSocket) I highly recommend prefixing all of the library's code so that anyone that uses your framework won't have conflicts if they use the same library.

Leave a comment

Your email address will not be published. Required fields are marked *