Objective-C → C# Binding Definition Skill
This skill encodes the complete knowledge needed to translate Objective-C header declarations into
Xamarin.iOS / .NET for iOS C# binding API definitions (the ApiDefinition.cs contract format).
The binding contract uses C# interfaces (not classes) decorated with attributes. The binding generator (btouch / generator) turns these interfaces into actual classes at build time.
File Structure
A binding project has two key files:
| File | Contents |
|---|---|
ApiDefinition.cs | C# interfaces with [BaseType], [Export], [Protocol] attributes — the API contract |
StructsAndEnums.cs | Enums, structs, delegates, and supporting type definitions |
Rule: ApiDefinition.cs contains ONLY interfaces and delegate declarations. All enums, structs, and
other value types go in StructsAndEnums.cs.
Class Binding (@interface)
Basic Class
@interface MyClass : NSObject @end
[BaseType (typeof (NSObject))]
interface MyClass
{
}
Rules:
- •Every interface MUST have
[BaseType]specifying the ObjC superclass - •The C# interface name matches the ObjC class name
- •If the ObjC name doesn't match .NET conventions, use
[BaseType (typeof (NSObject), Name = "ObjCName")]
Class with Protocol Conformance
@interface MyClass : UIView <UITextInput, NSCoding> @end
[BaseType (typeof (UIView))]
interface MyClass : IUITextInput, INSCoding
{
}
Rule: Protocol conformance uses I-prefixed interface names in the inheritance list.
Disabling Default Constructor
When a class marks init as NS_UNAVAILABLE:
[DisableDefaultCtor]
[BaseType (typeof (NSObject))]
interface MyClass
{
[DesignatedInitializer]
[Export ("initWithName:")]
NativeHandle Constructor (string name);
}
Property Binding (@property)
Basic Property
@property (nonatomic, copy) NSString *title;
[Export ("title")]
string Title { get; set; }
Rules:
- •Property name is PascalCase in C# (the
[Export]carries the ObjC name) - •The
[Export]selector is the ObjC property name (the getter selector) - •The runtime auto-derives the setter as
setTitle:from the export name
ArgumentSemantic
| ObjC Attribute | C# ArgumentSemantic | When to Use |
|---|---|---|
copy | ArgumentSemantic.Copy | Always specify |
retain / strong | ArgumentSemantic.Strong | When explicitly declared |
assign | ArgumentSemantic.Assign | For value types or explicit |
weak | ArgumentSemantic.Weak | Plus [NullAllowed] |
| (none, object pointer) | (omit) | Don't infer a default |
| (none, primitive) | (omit) | Don't specify ArgumentSemantic for primitives |
| (none, enum/value type) | ArgumentSemantic.Assign | Inferred for non-primitive value types |
@property (nonatomic, copy) NSString *name; @property (nonatomic, strong) UIView *contentView; @property (nonatomic, weak) id<MyDelegate> delegate; @property (nonatomic, assign) NSInteger count; @property (nonatomic) UIColor *tintColor; // no explicit semantic
[Export ("name", ArgumentSemantic.Copy)]
string Name { get; set; }
[Export ("contentView", ArgumentSemantic.Strong)]
UIView ContentView { get; set; }
[NullAllowed, Export ("delegate", ArgumentSemantic.Weak)]
IMyDelegate Delegate { get; set; }
[Export ("count")]
nint Count { get; set; }
[Export ("tintColor")] // no default semantic for object pointers
UIColor TintColor { get; set; }
Readonly Properties
@property (nonatomic, readonly) NSInteger count;
[Export ("count")]
nint Count { get; }
Rule: Omit the setter for readonly properties.
Class Properties (Static)
@property (class, nonatomic, readonly) MyClass *sharedInstance;
[Static]
[Export ("sharedInstance")]
MyClass SharedInstance { get; }
Custom Getter Name
@property (nonatomic, getter=isEnabled) BOOL enabled;
[Export ("enabled")]
bool Enabled { [Bind ("isEnabled")] get; set; }
Block-Type Properties
@property (nonatomic, copy) void (^completionHandler)(BOOL success);
[Export ("completionHandler", ArgumentSemantic.Copy)]
Action<bool> CompletionHandler { get; set; }
Rule: Block properties use Action<> for void-returning blocks, Func<> for value-returning blocks.
Method Binding
Instance Methods
- (void)performAction:(NSString *)action withCompletion:(void (^)(BOOL))handler;
[Export ("performAction:withCompletion:")]
void PerformAction (string action, Action<bool> handler);
Rules:
- •The
[Export]selector includes ALL colons:"performAction:withCompletion:" - •Method name uses only the first selector part (PascalCased), with trailing parameter context stripped
- •
addAnnotation:options:animated:→AddAnnotation - •
cancelSearchAnimated:→CancelSearch(strips "Animated") - •For protocol/delegate methods, strip the sender class name prefix:
myController:didSelectItem:→DidSelectItem - •For single-part protocol selectors with embedded sender prefix + verb:
myViewControllerDidCancel:→DidCancel - •For non-void methods with parameters that don't start with a verb, add
Getprefix:annotationForIndexPath:→GetAnnotation - •Normalize acronyms: URL→Url, PDF→Pdf, HUD→Hud, HTML→Html, JSON→Json
- •
[Async]is only added to class methods with completion handlers, never protocol methods - •Constructor detection handles
nonnull instancetypeandnullable instancetype, not just bareinstancetype - •Parameter names come from the ObjC parameter names
Class Methods (Static)
+ (instancetype)documentWithURL:(NSURL *)url;
[Static]
[Export ("documentWithURL:")]
MyClass FromUrl (NSUrl url);
Rule: Add [Static] before [Export] for class methods (+).
NullAllowed on Parameters
- (void)setText:(nullable NSString *)text;
[Export ("setText:")]
void SetText ([NullAllowed] string text);
NullAllowed on Return Values
- (nullable UIView *)viewForKey:(NSString *)key;
[return: NullAllowed]
[Export ("viewForKey:")]
UIView ViewForKey (string key);
Constructor Binding
Basic Constructor
- (instancetype)initWithFrame:(CGRect)frame;
[Export ("initWithFrame:")]
NativeHandle Constructor (CGRect frame);
Rules:
- •Constructors return
NativeHandle(wasIntPtrin older Xamarin) - •Method name is always
Constructor - •The
[Export]carries the full ObjC selector
Designated Initializer
- (instancetype)initWithTitle:(NSString *)title NS_DESIGNATED_INITIALIZER;
[DesignatedInitializer]
[Export ("initWithTitle:")]
NativeHandle Constructor (string title);
Auto-Generated Constructors
The binding generator automatically creates these constructors for every bound class:
- •
Foo ()— default, maps to ObjCinit - •
Foo (NSCoder)— for NIB deserialization (initWithCoder:) - •
Foo (NativeHandle)— handle-based creation (runtime use) - •
Foo (NSEmptyFlag)— for derived classes (prevents double init)
Use [DisableDefaultCtor] to suppress the parameterless constructor.
Protocol Binding (@protocol)
Protocol with Required and Optional Methods
@protocol MyDelegate <NSObject> @required - (void)didFinish:(MyClass *)sender; @optional - (BOOL)shouldStart:(MyClass *)sender; @end
// 1. Empty stub interface (REQUIRED for typed protocol references)
interface IMyDelegate {}
// 2. Protocol definition
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate
{
// @required → [Abstract]
[Abstract]
[Export ("didFinish:")]
void DidFinish (MyClass sender);
// @optional → no [Abstract]
[Export ("shouldStart:")]
bool ShouldStart (MyClass sender);
}
Critical Rules:
- •Every
@protocolneeds an emptyinterface IProtocolName {}stub BEFORE the definition - •The stub enables typed protocol references (
IMyDelegateas parameter/property type) - •
@requiredmethods get[Abstract] - •
@optionalmethods do NOT get[Abstract] - •Protocols ending in
DelegateorDataSourceget[Protocol, Model]+[BaseType (typeof (NSObject))] - •Other protocols get
[Protocol]only (no[Model], no[BaseType])
Protocol Properties — Required vs Optional
Protocol properties are handled differently based on @required vs @optional:
@protocol MyProtocol <NSObject> @required @property (nonatomic, readonly, getter=isActive) BOOL active; @property (nonatomic) NSString *name; @optional @property (nonatomic, getter=isSelected) BOOL selected; @property (nonatomic) NSObject *value; @end
[Protocol]
[BaseType (typeof (NSObject))]
interface MyProtocol
{
// @required → [Abstract] + C# property (with [Bind] for custom getter)
[Abstract]
[Export ("active")]
bool Active { [Bind ("isActive")] get; }
[Abstract]
[Export ("name")]
string Name { get; set; }
// @optional → decomposed into getter/setter method pairs
[Export ("isSelected")]
bool GetSelected ();
[Export ("setSelected:")]
void SetSelected (bool selected);
[Export ("value")]
NSObject GetValue ();
[Export ("setValue:")]
void SetValue ([NullAllowed] NSObject value);
}
Critical Rules:
- •
@requiredproperties get[Abstract]and stay as C# properties - •
@requiredproperties with custom getters use[Bind("isX")]syntax - •
@optionalproperties are decomposed intoGetXxx()/SetXxx()method pairs - •
@optionalproperties with custom getters use the custom selector as the getter export (e.g.,isSelected) - •Regular class (
@interface) properties are NEVER decomposed — they always use{ get; set; }
Protocol Used as Parameter Type
- (void)setDelegate:(id<MyDelegate>)delegate;
[Export ("setDelegate:")]
void SetDelegate ([NullAllowed] IMyDelegate delegate);
Rule: Use I-prefixed stub interface for protocol-typed parameters.
Delegate/Event Pattern
To generate C# events from an ObjC delegate protocol:
[BaseType (typeof (NSObject),
Delegates = new string [] { "WeakDelegate" },
Events = new Type [] { typeof (MyDelegate) })]
interface MyClass
{
[Wrap ("WeakDelegate")]
[NullAllowed]
IMyDelegate Delegate { get; set; }
[Export ("delegate", ArgumentSemantic.Assign)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate
{
[Export ("myClass:didSelectItem:"), EventArgs ("ItemSelected")]
void DidSelectItem (MyClass sender, NSObject item);
[Export ("myClassShouldClose:"), DelegateName ("Predicate<MyClass>"), DefaultValue (true)]
bool ShouldClose (MyClass sender);
}
Rules:
- •
EventsandDelegateson[BaseType]enable C# event generation - •
[EventArgs ("Name")]generates aNameEventArgsclass for multi-parameter events - •
[DelegateName]+[DefaultValue]for methods that return values - •Provide both
Delegate(strongly typed,[Wrap]) andWeakDelegate(weakly typed,[Export])
Nullability Rules
NS_ASSUME_NONNULL Scope
Most modern ObjC headers use:
NS_ASSUME_NONNULL_BEGIN // Everything here is nonnull by default NS_ASSUME_NONNULL_END
Inside NS_ASSUME_NONNULL scope:
- •All object pointers are assumed nonnull → do NOT add
[NullAllowed] - •Only explicitly
nullabletypes get[NullAllowed]
Outside NS_ASSUME_NONNULL scope:
- •All object pointer types should get
[NullAllowed]
Nullability Annotation Mapping
| ObjC Annotation | C# Attribute |
|---|---|
nullable / _Nullable / __nullable | [NullAllowed] |
nonnull / _Nonnull / __nonnull | (nothing — this is the default inside NONNULL scope) |
null_unspecified | [NullAllowed] (safer default) |
weak property | Always [NullAllowed] (weak refs are inherently nullable) |
Where NullAllowed Goes
| Context | Placement |
|---|---|
| Property | [NullAllowed, Export ("name")] (before Export) |
| Method parameter | void Foo ([NullAllowed] string arg) (inline on parameter) |
| Return value | [return: NullAllowed] (separate attribute line) |
| Setter only | string Name { get; [NullAllowed] set; } |
Enum Binding
NS_ENUM (Native Integer Enums)
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
// In StructsAndEnums.cs
[Native]
public enum UITableViewCellStyle : long
{
Default,
Value1,
Value2,
Subtitle,
}
Rules:
- •
NS_ENUM(NSInteger, ...)→[Native]+: long - •
NS_ENUM(NSUInteger, ...)→[Native]+: ulong - •Strip the enum name prefix from member names:
UITableViewCellStyleDefault→Default - •Goes in
StructsAndEnums.cs, NOT inApiDefinition.cs
NS_OPTIONS (Flags Enums)
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
};
[Native]
[Flags]
public enum UIViewAutoresizing : ulong
{
None = 0,
FlexibleLeftMargin = 1 << 0,
FlexibleWidth = 1 << 1,
}
Enum Backing Type Mapping
| ObjC | C# |
|---|---|
NSInteger | long (with [Native]) |
NSUInteger | ulong (with [Native]) |
int / int32_t | int |
unsigned int / uint32_t | uint |
uint8_t | byte |
String-Backed Enums
enum NSRunLoopMode
{
[DefaultEnumValue]
[Field ("NSDefaultRunLoopMode")]
Default,
[Field ("NSRunLoopCommonModes")]
Common,
}
Field Binding (Extern Constants)
String Constants / Notification Names
extern NSString * const MyClassDidFinishNotification;
[Field ("MyClassDidFinishNotification")]
NSString DidFinishNotification { get; }
Rules:
- •Fields go inside an interface decorated with
[Static] - •For statically linked libraries, use
[Field ("name", "__Internal")] - •For framework constants, use
[Field ("name", "FrameworkName")]
Notification Binding
extern NSNotificationName const MyClassDidChangeNotification;
[Notification]
[Field ("MyClassDidChangeNotification")]
NSString DidChangeNotification { get; }
This generates a Notifications nested class with ObserveDidChange() methods.
For notifications with payload:
[Notification (typeof (MyChangedEventArgs))]
[Field ("MyClassDidChangeNotification")]
NSString DidChangeNotification { get; }
// Separate interface for EventArgs
interface MyChangedEventArgs
{
[Export ("ChangedItemKey")]
NSObject ChangedItem { get; set; }
}
Category Binding
Categories That Extend Known Types (Merge)
When the parent class is in your binding, merge the category members into the parent:
// PSPDFPageView+AnnotationMenu.h @interface PSPDFPageView (AnnotationMenu) - (void)showMenu; @end
// Add directly into the PSPDFPageView interface definition:
[BaseType (typeof (UIView))]
interface PSPDFPageView
{
// ... existing members ...
// From AnnotationMenu category
[Export ("showMenu")]
void ShowMenu ();
}
Rule: Modern binding convention is to merge categories into their parent class.
Categories That Extend System Types (Extension Methods)
When extending a system type like UIView or NSString:
@interface UIView (MyExtensions) - (void)makeBackgroundRed; @end
[Category]
[BaseType (typeof (UIView))]
interface UIView_MyExtensions
{
[Export ("makeBackgroundRed")]
void MakeBackgroundRed ();
}
Rules:
- •Use
[Category]+[BaseType (typeof (ExtendedType))] - •Interface name convention:
ExtendedType_CategoryName - •These become C# extension methods
- •Avoid
[Static]members in categories (generates broken code — use[Category (allowStaticMembers: true)]if needed)
Type Mapping Reference
Primitive Types
| Objective-C | C# |
|---|---|
void | void |
BOOL / bool | bool |
char / signed char | sbyte |
unsigned char / uint8_t | byte |
short / int16_t | short |
unsigned short / uint16_t | ushort |
int / int32_t | int |
unsigned int / uint32_t | uint |
long | nint |
unsigned long | nuint |
long long / int64_t | long |
unsigned long long / uint64_t | ulong |
float | float |
double | double |
NSInteger | nint |
NSUInteger | nuint |
CGFloat | nfloat |
NSTimeInterval | double |
size_t | nuint |
Object Types
| Objective-C | C# |
|---|---|
NSString * | string |
NSArray * | NSObject [] |
NSArray<Type *> | Type [] (typed arrays) |
NSDictionary * | NSDictionary |
NSData * | NSData |
NSDate * | NSDate |
NSURL * | NSUrl |
NSError * | NSError |
NSSet * | NSSet |
NSNumber * | NSNumber |
NSObject * | NSObject |
id | NSObject |
id<Protocol> | IProtocol |
UIView<Protocol> | IProtocol (protocol interface) |
instancetype | The class type itself (for factory methods) or NativeHandle (for constructors) |
IBAction | void |
Class | Class |
SEL | Selector |
*Block typedef | *Handler (.NET convention) |
Pointer / Handle Types
| Objective-C | C# |
|---|---|
NSError ** | out NSError |
Type ** | out Type |
void * | IntPtr |
CGColorRef | CGColor |
CGPathRef | CGPath |
CGImageRef | CGImage |
CGContextRef | CGContext |
SecIdentityRef | SecIdentity |
SecTrustRef | SecTrust |
dispatch_queue_t | DispatchQueue |
Block Types
| Objective-C Block | C# |
|---|---|
void (^)(void) | Action |
void (^)(BOOL) | Action<bool> |
void (^)(NSString *, NSError *) | Action<string, NSError> |
BOOL (^)(NSString *) | Func<string, bool> |
id (^)(void) | Func<NSObject> |
const char * (C Strings)
| Objective-C | C# |
|---|---|
const char * | string (marshaled automatically) |
char * (mutable) | IntPtr (manual marshaling) |
Variadic Parameters
- (void)appendItems:(NSObject *)firstItem, ... NS_REQUIRES_NIL_TERMINATION;
[Export ("appendItems:"), Internal]
void AppendItems (NSObject firstItem, IntPtr varArgs);
// Then provide a public wrapper using params:
// public void AppendItems (params NSObject[] items) { ... }
Struct Binding
typedef struct {
CGFloat x;
CGFloat y;
CGFloat width;
} MyRect;
// In StructsAndEnums.cs
[StructLayout (LayoutKind.Sequential)]
public struct MyRect
{
public nfloat X;
public nfloat Y;
public nfloat Width;
}
Advanced Attributes
[Wrap] — Strongly Typed Convenience
[Export ("delegate", ArgumentSemantic.Assign)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
[Wrap ("WeakDelegate")]
[NullAllowed]
IMyDelegate Delegate { get; set; }
[Async] — Async Wrapper Generation
[Export ("fetchDataWithCompletion:")]
[Async]
void FetchData (Action<NSData, NSError> completion);
Generates: Task<NSData> FetchDataAsync ()
[Sealed] — Prevent Overriding
[Sealed]
[Export ("uniqueIdentifier")]
string UniqueIdentifier { get; }
[New] — Hide Inherited Member
[New]
[Export ("init")]
NativeHandle Constructor ();
[Internal] — Hide from Public API
[Internal]
[Export ("_privateMethod")]
void PrivateMethod ();
[Verify] — Mark for Manual Review
[Verify (MethodToProperty)]
[Export ("count")]
nint Count { get; }
Used by Objective Sharpie to flag bindings that need human review.
[BindAs] — Better Type Conversion
[return: BindAs (typeof (bool?))]
[Export ("shouldDraw:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);
// Generates: bool? ShouldDraw (CGRect rect)
Common Patterns and Pitfalls
1. Missing I-Prefix Protocol Stub
Wrong: Using protocol directly without stub
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate { ... }
Right: Always add the empty stub interface
interface IMyDelegate {}
[Protocol, Model]
[BaseType (typeof (NSObject))]
interface MyDelegate { ... }
2. Forgetting ArgumentSemantic for Object Properties
Wrong: Missing semantic for object pointer property
[Export ("view")]
UIView View { get; set; }
Right: Only include ArgumentSemantic when explicitly declared in ObjC property attributes
[Export ("view")]
UIView View { get; set; } // no default semantic needed
3. Wrong Selector for Multi-Part Methods
Wrong: Missing colons in selector
[Export ("setValueForKey")]
void SetValue (NSObject value, string key);
Right: Include ALL colons
[Export ("setValue:forKey:")]
void SetValue (NSObject value, string key);
4. Using int Instead of nint for NSInteger
Wrong:
[Export ("count")]
int Count { get; }
Right: NSInteger maps to nint (pointer-sized)
[Export ("count")]
nint Count { get; }
5. Enums in Wrong File
Wrong: Putting enums in ApiDefinition.cs
Right: All enums, structs, and delegates go in StructsAndEnums.cs
6. Constructor Returning Wrong Type
Wrong:
[Export ("initWithName:")]
IntPtr Constructor (string name); // Old Xamarin style
Right (modern .NET for iOS):
[Export ("initWithName:")]
NativeHandle Constructor (string name);
Vendor Macro Handling
When reading ObjC headers from third-party frameworks, ignore these common macros:
| Macro Pattern | Meaning | Action |
|---|---|---|
NS_ASSUME_NONNULL_BEGIN/END | Nullability scope | Affects [NullAllowed] placement |
NS_DESIGNATED_INITIALIZER | Designated init | Add [DesignatedInitializer] |
NS_REQUIRES_SUPER | Must call super | Ignore (no C# equivalent) |
NS_SWIFT_NAME(...) | Swift name override | Ignore |
API_AVAILABLE(...) | Availability | Map to [Introduced] if needed |
API_DEPRECATED(...) | Deprecation | Map to [Deprecated] if needed |
NS_UNAVAILABLE | Unavailable init | Use [DisableDefaultCtor] |
NS_REFINED_FOR_SWIFT | Swift refinement | Ignore |
OBJC_EXPORT / vendor export macros | Visibility | Treat as extern |
| Other UPPER_SNAKE_CASE macros | Vendor annotations | Skip entirely |
__attribute__((...)) | Compiler attributes | Skip entirely |
Checklist for Binding an ObjC Header
- •Identify the scope: Is the header inside
NS_ASSUME_NONNULL_BEGIN/END? - •Map the class:
@interface→[BaseType] interface - •Map the superclass:
: NSObject→typeof (NSObject) - •Map protocol conformance:
<P1, P2>→: IP1, IP2 - •Map each property: type mapping, ArgumentSemantic, readonly, nullable
- •Map each method: selector with colons, parameter types, return type, nullable
- •Map constructors:
init*methods →NativeHandle Constructor, add[DesignatedInitializer]if marked - •Map protocols:
@protocol→interface IFoo {}stub +[Protocol]definition,[Abstract]for@required - •Map enums:
NS_ENUM→[Native] enum,NS_OPTIONS→[Native] [Flags] enum - •Map extern constants:
extern NSString *→[Field]in[Static] interface Constants - •Map categories: Merge into parent class or use
[Category]for system types - •Verify nullability: Check every object pointer parameter, property, and return value
- •Strip enum prefixes:
UITableViewCellStyleDefault→Default - •Put types in correct files: Interfaces in ApiDefinition.cs, enums/structs in StructsAndEnums.cs
References
- •Binding Types Reference Guide
- •Binding Objective-C Libraries
- •type-mapping.md — Complete ObjC→C# type mapping table
- •attributes.md — Complete attribute reference