binding objective-c libraries, miguel de icaza
TRANSCRIPT
MonoTouch/iOS Native Interop
Xamarin Inc
Agenda• When to Bind• How to bind • Improving the binding
INTRODUCTION TO BINDINGS
Xamarin.iOS Native Interop
• Consume Objective-C or C code– Integrated existing code– Move performance sensitive code to C/assembly– Adopt third party libraries– Adopt third party controls/frameworks
• At the core of Xamarin.iOS itself
Integration with Native Libraries• iOS Native Libraries
– Core libraries written in C– High-level libraries written in Objective-C
• Consuming C Libraries: – Uses standard .NET Platform/Invoke support
• Consuming Objective-C Libraries:– Binding Projects– “Projects” Objective-C to C#– Provides full integration with native object hierarchy
Mono/Objective-C Bindings• Used by MonoTouch itself– Every Objective-C API is surfaced this way.
• Tool produces C# libraries that:– Map 1:1 to underlying Objective-C libraries– Allow instantiating Objective-C classes– Allow subclassing/overwriting of behavior– Can extend Objective-C with C# idioms
Configuring Objective Sharpie
Header Files + Namespace
Generating the IDL
MonoTouch Binding Project
Creating a Binding• Enumerations and Structures
– Contain core definitions used by the interface
• C# Interface Definition– Defines how to project Objective-C to C#– Name mapping, Overloads
• Curated Extensions:– Contains helper features to simplify development– Add strongly-typed features
Example: C# Interface DefinitionObjective-C@interface MagicBeans : NSObject {
// …}-(id) initWithString:(NSString*)msg;+(NSArray*) getAllBeans();-(void) grow;-(void) harvest:(NSDate *) time;@end
Example: C# Interface DefinitionObjective-C@interface MagicBeans : NSObject {
// …}-(id) initWithString:(NSString*)msg;+(NSArray*) getAllBeans();-(void) grow;-(void) harvest: (NSDate *) time;@end
C# Interface Definition[BaseType (typeof (NSObject))]Interface MagicBean { [Export (“initWithString:”)]
IntPtr Constructor (string msg);
[Static, Export (“getAllBeans”)] MagicBeans [] GetAllBeans ();
[Export (“grow”)] void Grow ();
[Export (“harvest:”)]void Harvest (NSDate time);
}
Binding “MagicBeans” libraryMagicBeans.dll
Enumerations, Structures
C# Interface Definition
Curated Extensions
libMagicBeans.a
C# Source Native Binary .NET Binary
Single deployment unit
Contains:• C# binding• Native Library• Assets (artwork,
audio)
OBJECTIVE SHARPIE
Compiler Driven Bindings• ObjectiveSharpie Tool– Uses LLVM’s Objective-C compiler to parse API– Applies standard Binding rules– Generates Baseline C# IDL for you– Then you modify
• Available Today– http://bit.ly/objective-sharpie
BASICS OF BINDINGS
Basics of Bindings• Binding Classes• Binding Protocols• Methods, Properties
– Type mappings– Arrays– Strings
• Delegate classes (and Events)• Exposing Weak and Strong Types• Binding Blocks
Class Declarations Objective-C C# Mapping Result
Class Declaration
@interface Foo : Bar [BaseType (typeof (Bar))]interfaceFoo
C# class
Class adopting protocol
@interface Foo : Bar <Pro>
[BaseType (typeof (Bar))]interfaceFoo : Pro
C# class, inlined protocol methods
Protocol @protocol Foo <Bar> [BaseType (typeof (Bar))][Model]interface Foo
C# class with methods to override
Category @interface Foo(Cute)
[BaseType (typeof (Foo))]interface Cute
C# extensions method class
Objective-C Method Selectors-(float) getNumber;
• Meaning:– “-” means it is an instance method– Float return type– Selector name is “getNumber”
• C# IDL:[Export (“getNumber”)] float GetNumber ();
Objective-C Method Selectors+(float) add:(int) first and:(int) second;
• Meaning:– “+” means it is a static method– Float return type– Takes two int arguments– Selector name is “add:and:”
• C# IDL:[Export (“add:and:”)]float Add (int first, int second)
Objective-C Property Selectors@property (readwrite) int foo;
• Meaning:– Property is read/write– int return type– Selector pair is: “foo” (get) and “setFoo:” (set)
• C# IDL:[Export (“foo”)]int Foo { get; set; }
Binding Correctness• [Export] definitions might have errors– Transcription errors– Accidental setters, or wrong getters
• Create a unit test– Subclass ApiCtorInitTest
Testing your APIs[TestFixture]public class BindingCtorTest : ApiCtorInitTest { protected override Assembly Assembly { get { return typeof (CCAccelAmplitude).Assembly; } } }}
Core Type MappingsObjective-C C#
BOOL, GLBoolean bool
NSString * C# string or NSString
char * [PlainString] string
NSInteger, NSUInteger int, uint
CGRect, CGPoint, CGSize RectangleF, PointF, SizeF
id NSObject
SEL ObjCRuntime.Selector
dispatch_queue_t CoreFoundation.DispatchQueue
CGFloat, GLFloat float
Arrays• NSArray represents arrays– Untyped, no code completion
• When binding, use strong types instead– “NSArray” becomes “UIView []”– Requires a trip to the documentation
LINKING LIBRARIES
Linking Libraries• Use assembly-level attribute LinkWith
– [assembly:LinkWith (…)
• Specify static and dynamic dependencies– Frameworks (for required dependencies)– WeakFrameworks (for optional dependencies)– Pass system linker flags
• Libraries referenced are bundled into DLL– Simplifies distribution (single DLL contains all resources)– Unpacked before the final build
Native Libraries• FAT libraries– One libFoo.a may contain x86, arm, thumb code– Not all libraries build have all targets– Make sure you build all the supported targets– See monotouch-bindings’ Makefile for samples
• IDE automatically examines fat libraries– And produces the proper LinkWith attribute
New: SmartLink• By default, all native code is linked-in
• SmartLink merges Mono and System linker– Only code that is directly referenced is included– Caveat: dynamic Objective-C code can fail– Big savings
SmartLink effect on Samples
ATMHud
BeebleS
DK
Couchbase
Datatra
ns
DropBoxS
ync -
DropBoxS
yncSa
mpleiOS
DropBoxS
ync -
DropBoxS
yncSa
mpleMTD
Flurry
Analytics
GCDiscree
tNotificati
on
GoogleAnaly
tics
GoogleMap
s
MBProgre
ssHud
MGSplitV
iewContro
ller
RedLas
er
SDSe
gmen
tedContro
l
TestF
light
TimesS
quare
WEP
opover
ZipArch
ive -
2,000
4,000
6,000
All codeSmart Linking
Size Savings
ATMHud
BeebleS
DK
Couchbase
Datatra
ns
DropBoxS
ync -
DropBoxS
yncSa
mpleiOS
DropBoxS
ync -
DropBoxS
yncSa
mpleMTD
Flurry
Analytics
GCDiscree
tNotificati
on
GoogleAnaly
tics
GoogleMap
s
MBProgre
ssHud
MGSplitV
iewContro
ller
RedLas
er
SDSe
gmen
tedContro
l
TestF
light
TimesS
quare
WEP
opover
ZipArch
ive0
100,000200,000300,000400,000500,000
Savings
ADVANCED BINDING FEATURES
Binding Public Variables• Mapped to properties in C#• Use the Field attribute.• Provide get or get/set
[Field (“FooServiceKey”, “__Internal”)]NSString ServiceKey { get; set; }
• Use “__Internal” for binding libraries• Supports NSArray, NSString, int, long, float, double, IntPtr and
System.Drawing.SizeF
Notifications• Basic API:– NSNotificationCenter takes a string + method– Invokes method when notification is posted– Contains an NSDictionary with notification data
• We want a strongly typed API:– Simplify discovery of available notifications– Simplify discovery of parameters , consumption
Observing APIUsage:
var obs = NSFileHandle.Notifications.ObserveReadCompletion ((sender, args) => { Console.WriteLine (args.AvailableData); Console.WriteLine (args.UnixError);});
Stop notifications:obs.Dispose ();
Binding Notification – Two StepsDefine Notification Payload• Optional• Interface without [Export]• Use EventArgs in type name
public interface NSFileHandleReadEventArgs { [Export ("NSFileHandleNotificationDataItem")] NSData AvailableData { get; }
[Export ("NSFileHandleError”)] int UnixErrorCode { get; }}
Annotate Fields• Annotate fields with
[Notification] attribute• Use type if you have a
payload
[Field ("NSFileHandleReadCompletionNotification")][Notification (typeof (NSFileHandleReadEventArgs))]NSString ReadCompletionNotification { get; }
Generated Notificationspublic class NSFileHandle { public class Notifications {
// Return value is a token to stop receiving notifications public static NSObject ObserveReadCompletion (EventHandler<MonoTouch.Foundation.NSFileHandleReadEventArgs> handler) }}
Usage:
NSFileHandle.Notifications.ObserveReadCompletion ((sender, args) => { Console.WriteLine (args.AvailableData); Console.WriteLine (args.UnixError);});
Async• Easy Async, turn any method of the form:
void Foo (arg1, arg2,.., argN, callback cb)• Just add [Async] to the contract and it does:
void Foo (arg1, arg2, .., argN, callback cb)Task FooAsync (arg1, arg2, .., argN)
• Callback must be a delegate type.
CURATED EXTENSIONS
• Add () methods, for initializing collections• Exposing high-level APIs• Strong typed APIs for Dictionaries• Implement ToString ()• Iterators and IEnumerable
Curated ExtensionsUsage• Improve the C# experience
• Expose some common methods (enumerators, LINQ, strong types)
• Re-surface internal methods
Examplepartial class MagicBeans {
// Enable code like this: // // foreach (var j in myBean){ // } //
public IEnumerator<Bean> GetEnumerator() { foreach (var bean in GetAllBeans())
yield return bean; }}
NSDictionary As Options • Many APIs in iOS take NSDictionary “options”– Annoying to find acceptable keys and values– Requires a trip to the documentation– Often docs are bad, see StackOverflow or Google– Invalid values are ignored or– Generate an error with no useful information
Strong typed wrappers• Discovery of properties with IDE’s Code
Completion.• Let the IDE guide you• Only valid key/values allowed• Skip a trip to the documentation• Avoid ping-pong in docs• Avoid error guesswork for your users
NSDictionary vs Strong TypesWith NSDictionaryvar options = new NSDictionary (
AVAudioSettings.AVLinearPCMIsFloatKey,new NSNumber (1),
AVAudioSettings.AVEncoderBitRateKey,new NSNumber (44000));
new AVAssetReaderAudioMixOutput (tr, options);
With Strong Typesvar options = new AudioSettings () {
AVLinearPCMFloat = true,EncoderBitRate = 44000
};
new AVAssetReaderAudioMixOutput (tr, options)
Using DictionaryContainer• DictionaryContainer provides bridge– Subclass DictionaryContainer– Provide a constructor that takes NSDictionary– Provide constructor with fresh
NSMutableDictionary• Use GetXxxValue and SetXxxValue
DictionaryContainer Samplepublic class AudioSettings : DictionaryContainer{ public AudioSettings () : base (new NSMutableDictionary ()) {} public AudioSettings (NSDictionary dictionary) : base (dictionary) {}
public AudioFormatType? Format { set { SetNumberValue (AVAudioSettings.AVFormatIDKey, (int?) value); } get { return (AudioFormatType?)GetInt32Value (AVAudioSettings.AVFormatIDKey); }}
Expose Iterators• Useful for LINQ• Lets your objects be consumed by LINQ• Implement GetEnumerator• Use yield to invoke underlying APIs