You’re almost done with your Xamarin application, and then the client does it… they request a whiz-bang feature that you don’t have time to add. So to Github you go, trying to find that perfect open source feature that does what you need. Oh no, another problem! You found the perfect component, but it’s written in Objective-C and there’s no Xamarin binding available for it. Ugh, you look at the source code – all of those colons, carets & what the heck is up with those function names? It looks like a foreign language – like ancient Latin, or a dialect only spoken in northern Wisconsin! You look around and you feel like a stranger, in a strange land that you just don’t understand (like if you were in northern Wisconsin)! What to do?
Do not despair! We’re going to take a tour through the Land of the Bindings, figuring out how to map an Objective-C library to C#, and by the end you’re going to feel like a local returning home.
PreparingFor Our Journey
Before we head out, we’re going to need to figure out how to get to our destination… and our vehicle for this trip will be a Binding Project. An iOS Binding project produces a DLL, and it’s that DLL we’ll add to our app’s project to provide the functionality written and compiled in Objective-C, but consumed in C#!
Fire up Xamarin Studio, create a new solution, and you’ll find the iOS Binding Project under the C# -> iOS -> Unified API node. Note that Xamarin Studio on the Mac must be used to create these bindings.
Give the solution a name, create it, and you’ll find that it comes pre-loaded with 2 files:
- APIDefinition.cs
- StructsAndEnums.cs
These files are the fuel for the iOS Binding project vehicle. The APIDefinition.cs is used to define the mapping between the Objective-C library we’re binding and the C# code that we’ll eventually consume in our iPhone/iPad application. If there are any structs or enums defined in the Objective-C library, we need to place them in the StructsAndEnums.cs file.
And maybe most importantly of all, we’ll need to add the static library we’re binding against to this project as well. (I’m assuming you already have an Objective-C static library to bind, if not we’ll cover how to create one in a later post). Either add it as an existing file, or just drag it into your solution. You’ll see that Xamarin Studio not only adds the library, but creates a “code-behind” file with the extension “.linkwith.cs”.
This linkwith.cs file is a way to tell Xamarin studio to package the static library into the resulting DLL, specifies which architectures the DLL should support (ArmV7, Simulator64, Arm64, etc.), and links against other frameworks (such as ExternalAccessory), if needed. It is composed of several “using” statements as well as an assembly level attribute named, of all things, LinkWith. Further documentation on this attribute can be found here(search for LinkWithAttribute on the page). But generally, the only thing you’ll have to edit in this file are the supported architectures, and possibly specify the other frameworks needed.
using System;
using ObjCRuntime;
[assembly: LinkWith ("libCNPGridMenuLibraryUniversal.a", LinkTarget.ArmV7 | LinkTarget.Simulator | LinkTarget.Simulator64 | LinkTarget.Arm64, SmartLink = true, ForceLoad = true)]
Getting To Know The Culture
Of course, to become a local means that we’ll need to learn the ways and customs of the land we’re traveling to. That means we should review some common Objective-C idioms so we’ll know what we’re seeing when we encounter them. You’ll find these signatures in the header (*.h) file that you’re using. (If you’re already familiar with Objective-C, feel free to skip to the next part, “Reading The Map”).
Classes
Classes are defined with the keyword “@interface” and the class which they inherit from is followed by a colon then the parent’s class name.
@interface CNPGridMenu : UICollectionViewController
Inside the class definition you’ll find the normal stuff, properties, methods, constructors, etc. The class definition will close with the “@end” keyword.
Methods
Method definitions are found within the class definition itself. They will start with a “-” or “+” character. The minus represents an instance method, while the + indicates a static method. The return type of the method is indicated next, surrounded by parentheses. Finally the confusing part. In Objective-C the entire method name is separated by colons, followed by an input parameter’s type surrounded by parentheses, and then the parameter’s name. It’s confusing, I know. A lot like listening to a person from Northern Wisconsin.
// instance method - full name is "gridMenuDidTapOnBackground:"
- (void)gridMenuDidTapOnBackground:(CNPGridMenu *)menu;
// static method - the full method name is "logEventForSource:message:iPadUDID:"
+ (void) logEventForSource:(NSString*) source message:(NSString*) message iPadUDID:(NSString *) iPadUDID;
Properties
Properties start with “@property” and are followed by keywords which will give you hints on how to bind the property (we’ll cover that in the section below).
@property (nonatomic, assign) CNPBlurEffectStyle blurEffectStyle;
@property (nonatomic, weak) id <CNPGridMenuDelegate> delegate;
@property (nonatomic, readonly) NSArray *menuItems;
Constructors
Constructors within Objective-C start with a “-“, so they look like a method, but they’ll generally return an “instancetype” or “id”. Convention has it that the constructor’s name begin with “init”.
- (instancetype)initWithMenuItems:(NSArray *)items;
Protocols
Protocols in Objective-C are very similar to the concept of events in C#, but instead of a event being raised, a method is called in another class. Protocols can have both mandatory and optional methods. And those mean exactly as you’d expect, a mandatory method must be defined in the implementing class, while an optional method is optional.
@protocol CNPGridMenuDelegate <NSObject>
@optional
- (void)gridMenuDidTapOnBackground:(CNPGridMenu *)menu;
- (void)gridMenu:(CNPGridMenu *)menu didTapOnItem:(CNPGridMenuItem *)item;
@end
Class Extensions
Now we start to get into the interesting stuff. Class extensions in Objective-C are more or less equivalent to C# extension methods. The definition looks just like class definition, except the name of the class is a preexisting one, followed by parentheses. Within the extension definition are method & property definitions. A full example follows.
@interface UIViewController (CNPGridMenu)
@property (nonatomic, strong) CNPGridMenu *gridMenu;
- (void)presentGridMenu:(CNPGridMenu *)menu animated:(BOOL)flag completion:(void (^)(void))completion;
- (void)dismissGridMenuAnimated:(BOOL)flag completion:(void (^)(void))completion;
@end
Blocks
Finally we’ll take a look at blocks. Blocks are a lot like anonymous methods in C#, and can be thought of as a delegate. Their definition however is super confusing. You’ll know when you’re dealing with a block when you encounter the ^ symbol. The block definition will have a first set of parentheses with a caret symbol in it, this is the return type. Then you’ll see one or more input parameter types (and sometimes names) following. And example of both ways follows:
// Definition along the lines of a C# delegate
typedef void (^SelectionHandler)(CNPGridMenuItem *item);
// Definiton along the lines of a C# anonymous method
- (void)dismissGridMenuAnimated:(BOOL)flag completion:(void (^)(void))completion;
Reading The Map
Now that we know the customs of the land we’re going to – now we have to figure out exactly how to get there. In other words – let’s learn about how to map the Objective-C idioms from above into C# code.
All of the examples below are from binding the CNPGridMenu iOS component developed by Carson Perrotti and can be found on GitHub.
All of the mappings will occur in the APIDefinitions.cs file that the iOS Bindings project created for us. Each class and protocol defined in the header files from our static library will have a mapping in that file. In other words, it could get very long. Every class from Objective-C is mapped into an interface definition in C# and decorated with one or more attributes. By making everything an interface, we don’t have to worry about providing an implementation and let the Mono framework take care of all the messy details. But first, let’s start by mapping an Objective-C enumeration…
Enums and Structs
When you come upon a struct or enum declaration in one of the Objective-C header files, there’s no need to place that into the APIDefinition.cs. Rather, you should put them into the StructsAndEnums.cs file. There’s no need for any attributes on the C# declaration, just declare them. For example:
typedef NS_ENUM(NSInteger, CNPBlurEffectStyle) {
CNPBlurEffectStyleExtraLight,
CNPBlurEffectStyleLight,
CNPBlurEffectStyleDark
};
Becomes
public enum CNPBlurEffectStyle
{
CNPBlurEffectStyleExtraLight,
CNPBlurEffectStyleLight,
CNPBlurEffectStyleDark
}
Classes
Next up are classes. The [BaseType]
attribute will decorate the interface which maps the Objective-C class to C#. This attribute takes a typeof
operator in its constructor. Here we’ll specify the type of iOS class the class we’re mapping derives from. In this case, the UICollectionViewController
.
[BaseType (typeof (UICollectionViewController))]
interface CNPGridMenu {
// More implementation below
Methods
Binding methods is relatively straight forward (the most difficult part is getting the Objective-C method name correct). All methods are decorated with an [Export]
attribute. The return value of the C# declaration will be the same as the Objective-C one. What follows is an example of binding a single parameter method & a multiple parameter one:
[Export ("gridMenuDidTapOnBackground:")]
void DidTapOnBackground(CNPGridMenu menu);
[Export ("gridMenu:didTapOnItem:")]
void DidTapOnItem(CNPGridMenu menu, CNPGridMenuItem item);
Note that the input parameters match the header file.
- (void)gridMenuDidTapOnBackground:(CNPGridMenu *)menu;
- (void)gridMenu:(CNPGridMenu *)menu didTapOnItem:(CNPGridMenuItem *)item;
Other notes regarding binding methods:
- Use the
[Static]
attribute when binding to a static Objective-C method - To hide the Objective-C method from the C# implementation use
[Internal]
attribute - Finally to specify that null is allowed for an input parameter, use the
[NullAllowed]
attribute.
Properties
For properties we must use the [Export]
attribute. However, when specifying the Objective-C name to export, be sure to NOT include any colons, unlike methods which need them. You can also use the [Internal]
and [Static]
attributes just as in methods. The [NullAllowed]
attribute can be specified on the property itself or on the setter.
The Objective-C convention is to retrieve the property’s value by just specifying the property’s name, and to set the value by putting “set” in front of the property’s name. If the library deviates from this convention and instead uses a different name to set the property … we can use the [Bind]
attribute on the setter to specify what it should bind to. An example of binding a property:
[Export("title")]
string Title { get; set; }
[Export ("icon")]
UIImage Icon { get; set; }
And remember from the Objective-C property section above where those weird keywords within parentheses? Things like the following:
@property (nonatomic, assign) CNPBlurEffectStyle blurEffectStyle;
@property (nonatomic, weak) id <CNPGridMenuDelegate> delegate;
@end
The assign, weak, and also copy & retain can be mapped using the ArgumentSemantic
enum of the [Export]
attribute. Our bound C# code then looks like:
[Export("blurEffectStyle", ArgumentSemantic.Assign)]
CNPBlurEffectStyle BlurEffectStyle { get; set;}
[Export("delegate", ArgumentSemantic.Weak)][NullAllowed]
NSObject WeakDelegate { get; set; }
Constructors
By default, the btouch-native tool (which actually produces the bound DLL), will create 4 constructors. The default constructor maps to the no-parameter init constructor.
To bind to any other Objective-C constructor the following rules must be followed:
- The
[Export]
attribute with the custom constructor’s name is still used. - The method must return an
InPtr
object - The method name must be
Constructor
A bound constructor:
[Export("initWithMenuItems:")]
IntPtr Constructor(CNPGridMenuItem[] items);
Protocols
A protocol is bound very much like a class is. The only difference is that the interface is also decorated with the [Model]
and [Protocol]
attributes. For example:
[BaseType (typeof (NSObject))]
[Model][Protocol]
interface CNPGridMenuDelegate {
[Export ("gridMenuDidTapOnBackground:")]
void DidTapOnBackground(CNPGridMenu menu);
[Export ("gridMenu:didTapOnItem:")]
void DidTapOnItem(CNPGridMenu menu, CNPGridMenuItem item);
}
Blocks
An Objective-C block maps to a C# delegate pretty easily. The hard part is just separating all of the carets and parentheses out to see what the return value and parameters are!
This Objective-C:
typedef void (^SelectionHandler)(CNPGridMenuItem *item);
Produces this C# binding:
delegate void ItemSelected(CNPGridMenuItem item);
The C# way is a bit easier to understand, no?
Class Extensions
Finally, let’s map out the class extensions. Again this is very similar to how we map classes over. In addition to the [BaseType]
attribute, we also add a [Category]
attribute to signify this is akin to a C# extension method.
So this Objective-C class extension:
@interface UIViewController (CNPGridMenu)
@property (nonatomic, strong) CNPGridMenu *gridMenu;
- (void)presentGridMenu:(CNPGridMenu *)menu animated:(BOOL)flag completion:(void (^)(void))completion;
- (void)dismissGridMenuAnimated:(BOOL)flag completion:(void (^)(void))completion;
@end
Becomes
[BaseType (typeof (UIViewController))]
[Category]
interface CNPGridViewControllerExtension {
[Export("gridMenu")]
[Static]
CNPGridMenu Menu { get; set;}
[Export("presentGridMenu:animated:completion:")]
void PresentGridMenu(CNPGridMenu menu, bool animated, [NullAllowed]CompletionHandler completion);
[Export("dismissGridMenuAnimated:completion:")]
void DismissGridMenu(bool animated, [NullAllowed]CompletionHandler completion);
}
Becoming A Local
Now that we’re feeling comfortable in this strange land, let’s make it a home by providing some comfortable amenities. And by that, I mean, let’s put some helper functionality into a new file, we’ll call it Extras.cs, that will make life easier when using the binding.
In the Extras.cs file we define C# partial classes based off the interfaces we created in the APIDefinitions.cs file. And within those partial classes we can create anything we would in any other class… methods, constructors, properties… all to make dealing with the underlying bound library easier. For example, we could create a ToString() function or create a new constructor which takes some initialization parameters. Take a look at the following:
public partial class CNPGridMenuItem {
public CNPGridMenuItem (string title, UIImage img)
{
this.Icon = img;
this.Title = title;
}
}
public partial class CNPGridMenu {
public CNPGridMenu (List<CNPGridMenuItem> items)
{
this.Items = items.ToArray ();
}
}
In this very simple example, all we did was add a new constructor to each class taking in some initialization parameters. This example may have been simple, but the possibilities are endless.
Wrapping Up
I’m sure you like to see all of this in action! You can view a complete sample up on GitHub here. Again many thanks to Carson Perrotti for developing the original library.
Well… we did it! We managed to learn the ways and meanings of various Objective-C idioms and we followed the map(pings) to bring us a DLL which binds to an Objective-C library. I hope you enjoyed the ride!
It will take a bit of practice to be able to speak like a local (in other words, don’t be surprised if you get the wrong name into the [Export]
attributes, but keep at it, and in no time, you’ll no longer be a stranger!
Comments