Creating a Static Library in iOS
Why Use Static Libraries?
You might want to create a static library for different reasons. For example:
- You want to bundle a number of classes that you and/or your colleagues in your team use regularly and share those easily around.
- You want to be able to keep some common code centralized so you can easily add bugfixes or updates.
- You’d like to share a library with a number of people, but not allow them to see your code.
- You’d like to make a version snapshot of a library that develops over time.
Getting Started
Click Next. Finally, choose a location where you’d like to save your new project, and click Create.
Xcode has just created a ready to use static library project, and even added a class
ImageFilters for you. Image Filters
This library is designed for iOS and uses UIKit, so the first thing you need to do is import UIKit in the header file. Open ImageFilters.h and add the following import to the top of the file:
#import <UIKit/UIKit.h>
|
Next, paste the following declarations below the line
@interface ImageFilters : NSObject@property (nonatomic,readonly) UIImage *originalImage; - (id)initWithImage:(UIImage *)image; - (UIImage *)grayScaleImage; - (UIImage *)oldImageWithIntensity:(CGFloat)level; |
These header file declarations define the public interface of the class. When other developers (including yourself!) use this library, they’ll know the name of the class and the methods that are exported in the static library just by reading this header.
Now, it’s time to add the implementation. Open ImageFilters.m and paste the following code just below the line:
#import "ImageFilters.h":@interface ImageFilters() @property (nonatomic,strong) CIContext *context; @property (nonatomic,strong) CIImage *beginImage; @end |
The code above declares some properties used internally by the class. These properties are not public, so any app that uses this library won’t have access to them.
Finally, you need to implement the class methods. Paste the following code just below the line
@implementation ImageFilters:- (id)initWithImage:(UIImage *)image { self = [super init]; if (self) { _originalImage = image; _context = [CIContext contextWithOptions:nil]; _beginImage = [[CIImage alloc] initWithImage:_originalImage]; } return self; } - (UIImage*)imageWithCIImage:(CIImage *)ciImage { CGImageRef cgiImage = [self.context createCGImage:ciImage fromRect:ciImage.extent]; UIImage *image = [UIImage imageWithCGImage:cgiImage]; CGImageRelease(cgiImage); return image; } - (UIImage *)grayScaleImage { if( !self.originalImage) return nil; CIImage *grayScaleFilter = [CIFilter filterWithName:@"CIColorControls" keysAndValues:kCIInputImageKey, self.beginImage, @"inputBrightness", [NSNumber numberWithFloat:0.0], @"inputContrast", [NSNumber numberWithFloat:1.1], @"inputSaturation", [NSNumber numberWithFloat:0.0], nil].outputImage; CIImage *output = [CIFilter filterWithName:@"CIExposureAdjust" keysAndValues:kCIInputImageKey, grayScaleFilter, @"inputEV", [NSNumber numberWithFloat:0.7], nil].outputImage; UIImage *filteredImage = [self imageWithCIImage:output]; return filteredImage; } - (UIImage *)oldImageWithIntensity:(CGFloat)intensity { if( !self.originalImage ) return nil; CIFilter *sepia = [CIFilter filterWithName:@"CISepiaTone"]; [sepia setValue:self.beginImage forKey:kCIInputImageKey]; [sepia setValue:@(intensity) forKey:@"inputIntensity"]; CIFilter *random = [CIFilter filterWithName:@"CIRandomGenerator"]; CIFilter *lighten = [CIFilter filterWithName:@"CIColorControls"]; [lighten setValue:random.outputImage forKey:kCIInputImageKey]; [lighten setValue:@(1 - intensity) forKey:@"inputBrightness"]; [lighten setValue:@0.0 forKey:@"inputSaturation"]; CIImage *croppedImage = [lighten.outputImage imageByCroppingToRect:[self.beginImage extent]]; CIFilter *composite = [CIFilter filterWithName:@"CIHardLightBlendMode"]; [composite setValue:sepia.outputImage forKey:kCIInputImageKey]; [composite setValue:croppedImage forKey:kCIInputBackgroundImageKey]; CIFilter *vignette = [CIFilter filterWithName:@"CIVignette"]; [vignette setValue:composite.outputImage forKey:kCIInputImageKey]; [vignette setValue:@(intensity * 2) forKey:@"inputIntensity"]; [vignette setValue:@(intensity * 30) forKey:@"inputRadius"]; UIImage *filteredImage = [self imageWithCIImage:vignette.outputImage]; return filteredImage; } |
Now build and run your library. You will notice the usual Xcode “Run” button just performs a build
Universal Binaries
A universal binary is a special kind of binary file that contains object code for multiple architectures. You might be familiar with universal binaries from the transition from PowerPC (PPC) to Intel (i386) in the Mac computers line. During that transition, Mac apps usually shipped with universal binaries containingboth executables in a single binary file so that the application could work with both Intel and PowerPC Macs.
lipo is a command line tool that allows you to perform operations on universal files (operations like creating universal binaries, listing universal file contents and more). What you are going to use lipo for in this tutorial is combine different architecture binary files into a single output binary for multiple architectures. You could use lipo directly from the command line, but in this tutorial you will make Xcode do the work for you by running a command line script that creates a universal library.
An Aggregate Target in Xcode will build multiple targets at once, including command-line scripts. Navigate to File/New/Target in the Xcode menu. Choose iOS/Other and click on Aggregate, like so:
Call the target UniversalLib, and make sure ImageFilters is the selected project, as shown below:
Click on the ImageFilters project in the project navigator, then select the UniversalLib target. Switch to the Build Phases tab; this is where you will set up the actions that will run when the target is built.
Click on the Add Build Phase button, and select Add Run Script in the menu that pops up, as shown below:
Now you have a script item to set up. Expand the Run Script module, and paste in the following text under the
Shell line:
|
The code is not so complicated, this is how it goes line-by-line:
- UNIVERSAL_OUTPUTFOLDER contains the name of the folder where the universal binary will be copied: “Debug-universal”
- Step 1. On the second line you call xcodebuild and instruct it to build the ARM architecture binary (you will see on this line the parameter -sdk iphoneos)
- Next line calls xcodebuild again and builds in a separate folder the iPhone Simulator binary for an Intel architecure, the key params here are: -sdk iphonesimulator -arch i386. (If you’re interested, you can learn more about xcodebuild on its man page)
- Step 2. Now that you already have the two .a files for the two architectures you just invoke lipo -create and set it to create a universal binary out of them
- In the last line you copy the header files over to the universal build folder (using the cp shell command)
Now you’re ready to build the universal version of the static library. Select the aggregate targetUniversalLib in the Scheme Selection drop down, as so (unlike on the screenshot below instead of “iOS Device” you might see your device’s name):
Press the Play button to build the target for the aggregate scheme.
To see the result, use the Show in Finder option on the libImageFilters.a product again. Switch to Finder’s column view to see the folders in the hierarchy and you’ll see a new folder called Debug-Universal (or Release-Universal if you built the Release version), which contains the Universal version of the library, as shown below:
You’ll find the usual headers and static library files are present, except this one links with both the simulator and the device.
And that is all you needed to learn in order to create your very own universal static library!
To recap, a static library project is very similar to an app. You can have one or many classes, and the final build products are the header files and a .a file with the code. This .a file is the static library which can be linked into several applications.
Header and Library Binary Files
Create and Open the project, and build and run your app.
As expected, the app has no idea where to find the header file. In order to solve this, you need to add a new Header Search Path to the project that points to the folder where the header is located. Configuring the Header Search Path is always the first step when using code from a static library.
As demonstrated in the image below, click on the project root node in the project navigator (1), and select the CoreImageFun target (2). Select Build Settings (3), and locate the Header Search Pathssetting in the list. You can type “header search” in the search box to filter the big list of settings if necessary (4).
Double click on the Header Search Paths item, and a popover will appear. Click the + button, and enter the following:
$SOURCE_ROOT/include
|
The popover should look like the following image:
$SOURCE_ROOT is an Xcode environment variable, which points to the project’s root folder. Xcode will replace this variable with the actual folder which contains your project, which means stays up to date when you move the project to another folder or drive.
Click outside the popover to dismiss, and you’ll see that Xcode has automatically translated it to the actual location of the project, as shown below:
Build and run your app, and see what kind of result you get. Hmm — some linker errors appear:
That doesn’t look great, but it gives you one more piece of information that you need. If you look closely, you’ll see that all the compile error is gone, and has been replaced with the linker errors. This means that Xcode found the header file and used it to compile the app, but in the linking phase, it couldn’t find the object code for the ImageFilter class. Why?
That’s simple — you haven’t yet told Xcode where to find the library file which contains the class implementation. (See, it wasn’t anything too serious.)
As shown in the screenshot below, return to the build settings (1) for the CoreImageFun target (2). Select the Build Phases tab (3), and expand the Link Binary With Libraries section (4). Finally, click the+ button in that section (5).
In the window that appears, click on the Add Other… button and locate the libImageFilters.a library file in the lib subdirectory inside the project’s root folder, as so:
This is how the Build Phase of your project should look when you’re all done:
The final step is to add the -ObjC linker flag. The linker tries to be efficient about only including needed code, which can sometimes exclude static library code. With this flag, all the Objective-C classes and categories in the library are loaded properly. You can learn more about this in Apple’s Technical Q&A QA1490
Click on the Build Settings tab, and locate the Other linker Flags setting, as shown below:
In the popover, click on the + button and type -ObjC, as such:
Finally, build and run your app; you should not receive any build errors, and the app will open in all it’s glory














No comments:
Post a Comment