The iOS project is the part that, in my opinion, is the hardest. There are plenty of mistakes that are very easy to accidentally make. Hopefully this post will help everyone else avoid the same mistakes that I made.
The iOS Library
1. Open Xcode and make a new project. In the New Project dialog, select iOS -> Framework & Library project option, then select “Cocoa Touch Static Library”.
2. You’ll see a .h file and a .m file named after your project. You don’t need the .h so you can delete that. After you delete the .h you should empty out the .m file since the default code isn’t needed anymore. You’ll also want to click on the Project in the bar on the left side and search for “prototype” in the Build Settings. Toggle the value of “Missing Function Prototypes” to no. This is just to suppress warnings.
3. Download the AIR sdk if you haven’t already. Navigate to “AIR_SDK_DIR/include”. There is a file named “FlashRuntimeExtensions.h”. Drag that file into Xcode and drop it onto your project in the left side bar. This includes the file in your project. (It doesn’t actually copy the file though, so if you delete/move the original Xcode won’t find it anymore.)
4. Open the main .m file and add #import "FlashRuntimeExtensions.h" to the top of the file. Now you can access the AIR runtime classes and get the architecture for the extension set up.
5. In the main .m file create a method called ContextInitializer. This method is called when the extension context is created in AIR. In this method you need to create a collection of functions that the main ActionScript library will call. Here’s an example:
void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{
*numFunctionsToTest = 2;
FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * 2);
func[0].name = (const uint8_t*) "init";
func[0].functionData = NULL;
func[0].function = &init;
func[1].name = (const uint8_t*) "setVolume";
func[1].functionData = NULL;
func[1].function = &setVolume;
*functionsToSet = func;
}
You can see that two functions are set up, init and setVolume. We invoked these methods earlier in the VolumeController class.
6. In the main .m file create a method called ContextFinalizer. This is called when the extension context is being garbage collected. Here you should clean up anything that is used by your native code.
void ContextFinalizer(FREContext ctx)
{
return;
}
7. Create two more methods. These methods should match the values in the initializer and finalizer nodes in the iPhone-ARM platform of the extension.xml file. The initializer method will set which methods to call when doing the actual initialization.
void VolumeExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet)
{
*extDataToSet = NULL;
*ctxInitializerToSet = &ContextInitializer;
*ctxFinalizerToSet = &ContextFinalizer;
}
void VolumeExtensionFinalizer(void* extData)
{
return;
}
8. Create the methods reference in the collection made in step #5. These methods are where you should add the native code to do whatever it is that your native extension does.
Each method will accept the same parameters. The main properties you’re worried about are the FREContext, which is extension context, and the collection of FREObject instances, which are the parameters passed to the native function. Each FREObject is a C representation of an ActionScript object. The FREObject class is a wrapper, so you have to pull the value out using methods on FREObject. See the below code for an example:
FREObject setVolume(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
double newVolume;
FREGetObjectAsDouble(argv[0], &newVolume);
[[MPMusicPlayerController applicationMusicPlayer] setVolume: newVolume];
return NULL;
}
9. Generating the compiled library.
Click on the project on the left hand side of the screen. Click on the “Build Settings” tab.
Do a search for “products path”. The directory specified in the “Per-configurations Build Projects Path” is where the compiled library will be put.
To change the directory just double click the current path and enter in the one you’d prefer.
After that’s done, press “command + b” which will make Xcode compile the library. You’ll find a .a file in the directory that was specified above.
A note about debugging:
Debugging in native extensions isn’t the easiest thing, but both IDEs for iOS and Android provide a way to log out to a console. You won’t be able to hit breakpoints but you can see log statements from the native code.
When using NSLog(NSString) statements the output will show up in the console available through the Xcode Organizer when the AIR application using the extension is running on the device and the device is plugged in via usb.
How to open the console: While Xcode is running, expand the “Window” menu option and select “Organizer”. Find your device in the left-hand list and hit the arrow to expand its contents. Select the “Console” option. The NSLog statements will show up here.
iOS Problems and Trap Doors
Making sure you support iOS 4.* devices.
By default Xcode sets the deployment target to match the SDK you are using. Xcode now only comes with the iOS 5.0 SDK which means that by default your library project targets 5.0.
Click on the project name in bar on the left hand side. In Build Setting do a search for “deployment” and change the “iOS Deployment Target” to be 4.0.
You’ll have to take this one step further by adding an xml file specifying the minimum required iOS version when packaging the ANE. See the item “Using External Frameworks and Libraries” for more information.
Always Debug on a Mac
I do my development on a Windows PC. So, naturally, after I built the native extension on the Mac (since Xcode is only available for OSX), I tried to compile a test application using an ANE on my PC. This would generally crash and burn. Sometimes compiling would fail. Oftentimes the application just didn’t work as expected, or would crash for no apparent reason. I had tried this prior to creating the Volume extension and the test extension was pretty basic, but it just didn’t work when compiling from a Windows PC.
I don’t know exactly why this happens, but it was something that multiple people in my office had trouble with. Especially coupled with the next few pitfalls, you’ll find it’s impossible not to compile the builds on a Mac.
Using More Than One Native Extension
Using more than one native extension in the same application can lead to some unnecessary headaches if you aren’t careful. If the ContextInitializer and ContextFinalizer functions that are specified in the extensions initializer function (the one specified in the extension.xml initializer node) share the same name as the ContextInitializer and ContextFinalizer of a different native extension, only first native extension loaded will work. I think this is because Objective C isn’t letting the new functions be created within the scope of the extension because they already exist from the previous extension. I’m assuming functions are stored in some sort of global scope; I’m not an expert on Objective C though, so that’s just my initial guess.
That being said, you should name the internal initializer and finalizer functions uniquely. I’m guilty of not doing that in this extension, which is why I’m writing this note right now. Here’s an example of what I mean.
void MySuperUniqueContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{
...
return;
}
void MySuperUniqueContextFinalizer(FREContext ctx)
{
return;
}
void VolumeExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet)
{
*extDataToSet = NULL;
*ctxInitializerToSet = &MySuperUniqueContextInitializer;
*ctxFinalizerToSet = &MySuperUniqueContextFinalizer;
}
Use the iOS 5.0 SDK when packaging the IPA
By default the AIR SDK compiles the IPA using the iOS 4.0 SDK. If you want to use iOS 5.0 SDK features, or if you want to link in external libraries (see next item) you’ll have to tell Flash Builder where the iOS 5.0 SDK is located. Also, as mentioned in the previous item, an application using an iOS native extension will just work better if you link in the SDK.
Go into the properties of your flex project. Navigate to “Flex Build Packaging -> Apple iOS”. Click the “Native Extensions” tab. In the “Apple iOS SDK” input type the location of the SDK. The default location is “/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk”.
If you are compiling via the command line, you can use the –platformsdk option to specify the location.
adt -package -target ipa-app-store -provisioning-profile ./myProfile.mobileprovision -storetype pkcs12 -keystore ./Certificates.p12 -storepass XXX myApp.ipa myApp-app.xml Main.swf -extdir ext/ -platformsdk /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/
See here for more information.
Using External Frameworks and Libraries
AIR doesn’t link in shared iOS frameworks or libraries, even some of the ones included with the iOS SDK. Here is a list of frameworks that AIR links in by default:
Inside of the iOS SDK directory, copy the library/framework files into either the System/Library/Frameworks folder or the usr/lib folder.
Create an XML file with the following structure.
This file does two important things. First it specifies the version of iOS that you support. Secondly, it tells the compiler which frameworks it should look for to compile into the ANE. If referencing a framework (you can tell it’s a framework because the folder will be Name.framework) use the option value –framework name. If it is a library (you can tell it’s a library because the file should be a *.dylib) then use the option value –name.
As of AIR 3.1 you can specify custom entitlements for your application by specifying them in the app descriptor file.
When using an attached device, such as a barcode scanner, you have to specify an entitlement in the app descriptor file.