Requirements
Prerequisite knowledge
This series of tutorials is designed for developers with an intermediate to advanced understanding of ActionScript 3 and building Adobe AIR applications. Familiarity with Flash Builder, Java, Objective-C, and Xcode will also be helpful. If you have not already done so, read Part 1 and Part 2 of this tutorial series before proceeding.
Required products
Flash Builder (Download trial)
Adobe AIR SDK
Additional required other products
Volume Native Extension
User Level
Intermediate

In the previous tutorial—Building a native extension for iOS and Android – Part 2: Developing the ActionScript library—you created the main and default libraries in ActionScript. If you are going to develop an AIR native extension to use with iOS, you also have to create an iOS library.
 
The iOS project is the part that, in my opinion, is the hardest. There are plenty of mistakes that are easy to accidentally make. This tutorial can help you avoid these common mistakes.
 

 
Creating the iOS library

Follow these steps to create the iOS library for your native extension:
 
  1. Open Xcode and make a new project. In the New Project dialog box, select iOS > Framework & Library on the left, and then click Cocoa Touch Static Library.
  2. After you create the project. You'll see a .h file and a .m file named after your project. You don't need the .h file so you can delete that. Next, empty the .m file since the default code isn't needed anymore.
  3. 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.
  4. Download the Adobe AIR SDK if you haven't already. Navigate to AIR_SDK_DIR/include and locate the file named FlashRuntimeExtensions.h. To include this file in your project, drag it into Xcode and drop it onto your project in the left side bar. (This doesn't actually copy the file though, so if you delete or move the original, then Xcode won't find it anymore.)
  5. Open the main .m file and add the following line to the top of the file:
#import "FlashRuntimeExtensions.h"
Now you can access the AIR runtime classes and set up the architecture for the extension.
 
  1. In the main .m file create a method named VolExtContextInitializer() . 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. Because you haven't defined these methods in the native code yet you'll need to revisit this later. Here's an example:
void VolExtContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet){ *numFunctionsToTest = 0; FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * *numFunctionsToTest; *functionsToSet = func; }
Note: When naming the ContextInitializer function you should choose a unique name. If multiple native extensions are used in the same application, functions with the same name will conflict and the native extensions will not work properly. It's a good idea to incorporate the name of the extension into the name of the function.
 
  1. Create another method to serve as the initialize method. The name of this method must match the initializer node in the iPhone-ARM platform of the extension.xml file. This method will set which methods to call when doing the actual initialization.
void VolumeExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet){ *extDataToSet = NULL; *ctxInitializerToSet = &VolExtContextInitializer; }
  1. Now you need to build out the concrete implementations of the methods that the ActionScript code is going to call. For this native extension, you need one named init() and one named setVolume() .
Each method will accept the same parameters. The main properties you're worried about are the FREContext , which is the 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. Objects cannot be passed directly between ActionScript and native code, so any time you deal with an object it will be of type FREObject .
 
The FREObject class is a wrapper, so you have to pull the value out using methods on FREObject . See the following 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; }
The code above won't compile as is. You'll need to add the following import statements:
 
#import <MediaPlayer/MediaPlayer.h> #import <AudioToolbox/AudioToolbox.h>
  1. Once these methods are created you'll need to go back to the initializer method and add in some code so that AIR can call them:
void VolExtContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet){ *numFunctionsToTest = 2; FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * *numFunctionsToTest; 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; }
Compare this code to the initial code for VolExtContextInitializer above to see the changes.
 
See the completed code in the IOSVolumeLib folder in the sample files for this article for init() and other functions.
 
 
Dispatching an event to ActionScript
There will be times when you'll want the AIR project to know that something has happened in the native code. You've already set up listeners on the extensionContext when you created the ActionScript librarybut now you need to dispatch the event.
 
The FREDispatchStatusEventAsync() method is used to send an event back to ActionScript. Dispatching the event is straightforward; you give the method the extensionContext ( FREContext instance), a code string, and a level string. See the Native C API Reference for FREDispatchStatusEventAsync() for details on these parameters.
 
The tricky part is that the native iOS code is likely using the iOS versions of the objects, but AIR deals with the native C objects. For example, iOS code uses objects of type NSString to represent Strings, while the AIR extensions use uint8_t* (an array of characters). You'll have to do some conversion, but it isn't terribly hard. Here's an example:
 
void dispatchVolumeEvent(float volume, FREContext ctx) { if (ctx == NULL) { return; } NSNumber *numVolume = [NSNumber numberWithFloat:volume]; NSString *strVolume = [numVolume stringValue]; NSString *eventName = @”volumeChanged”; const uint8_t* volumeCode = (const uint8_t*) [strVolume UTF8String]; const uint8_t* eventCode = (const uint8_t*) [eventName UTF8String]; FREDispatchStatusEventAsync(ctx, eventCode, volumeCode); }
Note: See the completed code to understand how this method is used in the volume extension.
 
 
Generating the compiled library
When you've finished coding the library, follow these steps to build it:
 
  1. Click the project on the left hand side of the screen and then click the Build Settings tab.
  2. Search for "products path". The compiled library will be placed in the directory specified in the Per-configuration Build Projects Path setting.
To change the directory just double-click the current path and type a new path.
 
Note: Later, I'll cover the files required when building the native extension. At that point you'll create a directory to hold the file that Xcode is going to generate, and you should set the Per-configuration Build Projects Path value to be that directory.
 
  1. Next, search for "Architectures". Be sure that the value for Architectures is "armv7".
  2. Search for "deployment" and set the "iOS Deployment Target" to the minimum version of iOS that you are supporting. Unless you have particular version requirements, iOS 4.0 should be fine.
  3. In the top bar click Product and then click Edit Scheme. While debugging you should leave Build Configuration set to Debug, but when you are ready to build the final native extension, set the value to Release.
  4. To build the library press Cmd+b (You may want to use completed code from the sample files). When the library is built, you'll find a .a file in the directory that you specified .
 
A note about debugging
Debugging native extensions isn't the easiest thing to do, but both IDEs for iOS and Android provide a way to log messages to a console. You won't be able to set 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 a USB port.
 
To open the console:
 
  1. While Xcode is running, choose Window > Organizer.
  2. Find your device in the left-hand list and click the arrow to expand its contents.
  3. Select the Console option. Output from your NSLog statements will show up here.

 
iOS problems and pitfalls

This section outlines several potential problems that you may encounter when building your iOS library.
 
 
Ensuring support for 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, so by default your library project targets 5.0.
 
To remedy this, click the project name in the bar on the left side. In Build Settings search for "deployment" and set iOS Deployment Target to 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 native extension. See Using External Frameworks And Libraries below for more information.
 
 
Compiling on a Mac
I do my development on a Windows PC. So, naturally, after I built my first native extension on the Mac (since Xcode is only available for OS X), I tried to compile a test application using the native extension on my PC. This would generally crash with AIR 3.2. Sometimes compiling would fail. Often 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. Especially coupled with the next few pitfalls, you may find it difficult or impossible to compile the builds on anything but a Mac.
 
Note: AIR 3.3 has been released since this tutorial was first written. The new version of AIR is compiling against iOS 5 which should ease development on Windows to an extent. However, I still recommend doing any final release IPA builds from an OS X machine with the iOS SDK (Xcode) installed.
 
 
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() function specified in the extension's initializer function (the one specified in the extension.xml file initializer node) shares the same name as the ContextInitializer of a different native extension only the first native extension loaded will work.
 
That being said, you should name the internal initializer functions in a unique way. Appending the name of your extension is an easy way to accomplish this. Here's an example:
 
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; }
 
Using an external iOS SDK when packaging the IPA
The AIR SDK has a specific version of the iOS SDK bundled with it. For AIR 3.2 it is iOS 4.0 and for AIR 3.3 it is iOS 5.1. If you are using features that are included in the iOS SDK that the AIR SDK is bundled with you'll be OK.
 
However, if you want to use features from a new iOS SDK or if you want to use features that are not included in the default libraries that the AIR SDK includes, you'll need to specify an external iOS SDK to use for compiling.
 
Note: Even within the iOS SDK that the AIR SDK includes, not all libraries are supported. This is covered in more detail below in Using External Frameworks And Libraries.
 
Follow these steps to set up an external iOS SDK on OS X.
 
  1. In Flash Builder, right-click your Flex project and select properties.
  2. Select Flex Build Packaging > Apple iOS.
  3. Click the Native Extensions tab.
  4. >For the Apple iOS SDK, type the location of the SDK. The default location (on my Snow Leopard machine) 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; for example:
 
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/
 
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:
 
  • CoreFoundation
  • UIKit
  • MobileCoreServices
  • CoreGraphics
  • SystemConfiguration
  • AudioToolbox
  • CFNetwork
  • QuartzCore
  • CoreLocation
  • CoreMedia
  • CoreVideo
  • AVFoundation
  • Foundation
  • OpenGLES
  • Security
If you want to use any other library or framework you have to list them in an XML file, which gets compiled with the native extension. One reason this is required is because Xcode links in external libraries at compile time of the main application. So when you build a library project in Xcode the resulting .a file doesn't contain any code from referenced libraries or frameworks.
 
If the frameworks are not included with the iOS SDK by default, you must copy them into the iOS SDK folder. (You can also specify custom directories to search for frameworks in the XML file; however you have to specify the path as relative to a path known by the packager, so I find it easier to just copy the libraries and frameworks into the iOS SDK directory.)
 
Inside of the iOS SDK directory, copy the library or framework files into either the System/Library/Frameworks folder or the usr/lib folder.
 
Create an XML file with the following structure:
 
<platform xmlns="http://ns.adobe.com/air/extension/3.1"> <sdkVersion>4.0</sdkVersion> <linkerOptions> <option>-ios_version_min 4.0</option> <option>-framework Twitter</option> <option>-liconv</option> </linkerOptions> </platform>
This file does two important things. First it specifies the version of iOS that you support (in this case, it is 4.0). Secondly, it tells the compiler which frameworks it should look for to compile into the native extension. 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 will be a *.dylib) then use the option value –lname .
 
When compiling the native extension, use the –platformoptions option to specify the location of the XML file; for example:
 
adt -package -target ane myextension.ane extension.xml -swc mySwc.swc -platform iPhone-ARM library.swf libmylib.a -platformoptions myplatformoptions.xml
See Rajorshi Ghosh's post on this topic for more information.
 
 
Using entitlements
As of AIR 3.1 you can specify custom entitlements for your application by listing them in the app descriptor file; for example:
 
<iPhone> <!-- A list of plist key/value pairs to be added to the application Info.plist --> <InfoAdditions> <![CDATA[ ... ]]> </InfoAdditions> <!-- A list of plist key/value pairs to be added to the application entitlements --> <Entitlements> <![CDATA[ <key>keychain-access-groups</key> <array> <string>...</string> ... </array> ... ]]> </Entitlements> </iPhone>
Rajorshi's post also includes an example of this.
 
 
Using attached devices
When using an attached device, such as a barcode scanner, you have to specify an entitlement in the app descriptor file.
 
Here's the information from the iOS documentation:
 
To declare the protocols your app supports, you must include the UISupportedExternalAccessoryProtocols key in your app's Info.plist file. This key contains an array of strings that identify the communications protocols that your app supports. Your app can include any number of protocols in this list and the protocols can be in any order. The system does not use this list to determine which protocol your app should choose; it uses it only to determine if your app is capable of communicating with the accessory. It is up to your code to choose an appropriate communications protocol when it begins talking to the accessory.
 
As explained in the last section, you can add the UISupportedExternalAccessoryProtocols entitlement to your application descriptor file.
 

 
Where to go from here

The iOS library is now complete. If you are going to use the native extension on Android devices, read the next tutorial in this series—Building a native extension for iOS and Android – Part 4: Building the Android library. Otherwise continue to the final tutorial in the series— Building a native extension for iOS and Android – Part 5: Building the ANE file.