Header Analysis for .NET Bindings
Systematically analyze Objective-C and Swift framework headers to extract the full API surface needed for .NET binding projects.
When to Use This Skill
- Before creating a new binding project — to understand what APIs exist
- When binding a multi-framework SDK — to determine dependency order
- When updating bindings for a new SDK version — to find added/removed/changed APIs
- When deciding what to bind vs skip — to assess API complexity
Quick Start
1. Extract Framework Metadata
Use the analyze_xcframework.py script (from macios-binding-project skill) or manually inspect:
# List all headers in the framework
FRAMEWORK="MyFramework.xcframework"
SLICE=$(ls "$FRAMEWORK" | grep "ios-arm64" | head -1)
HEADERS="$FRAMEWORK/$SLICE/MyFramework.framework/Headers"
ls "$HEADERS"
Key things to note:
*-Swift.h— Auto-generated ObjC header for Swift code. Contains all@objc-exposed Swift APIs- Umbrella header (e.g.,
MyFramework.h) — Imports all public headers - Individual
.hfiles — One per class/protocol typically
2. Classify the Framework
| Pattern | Meaning | Binding Approach |
|---|---|---|
Only *-Swift.h |
Pure Swift framework | Bind from the generated ObjC header only |
Many .h files, no -Swift.h |
Pure ObjC framework | Bind from individual headers |
Both .h files and -Swift.h |
Mixed ObjC + Swift | Bind ObjC headers + Swift-generated header |
| Very few headers | Minimal public API | Quick binding, check for protocols |
3. Read All Public Headers
Read every header file systematically. This is the most time-consuming but most important step.
# Read all headers
for h in "$HEADERS"/*.h; do
echo "=== $(basename $h) ==="
cat "$h"
done
4. Extract API Inventory
As you read, build a structured inventory (see detailed patterns below).
5. Determine Dependencies
Check #import statements and framework references to build the dependency graph.
API Extraction Patterns
Classes
Look for @interface declarations:
// Standard class
@interface FBSDKAccessToken : NSObject
// → [BaseType (typeof (NSObject))]
// interface AccessToken { }
// Class with a specific ObjC name (Swift)
SWIFT_CLASS_NAMED("AccessToken")
@interface FBSDKAccessToken : NSObject
// → [BaseType (typeof (NSObject), Name = "FBSDKAccessToken")]
// interface AccessToken { }
// Class inheriting from another bound class
@interface FBSDKLoginButton : FBButton
// → [BaseType (typeof (FBButton))]
// interface LoginButton { }
Record for each class:
- ObjC class name (e.g.,
FBSDKAccessToken) - C# name to use (e.g.,
AccessToken— strip prefix) - Base class
- Adopted protocols (e.g.,
<NSCopying, NSSecureCoding>) - Whether it's Swift-generated (
SWIFT_CLASS/SWIFT_CLASS_NAMED)
Protocols
Look for @protocol declarations:
// Protocol with required + optional methods
@protocol FBSDKSharingDelegate <NSObject>
@required
- (void)sharer:(id<FBSDKSharing>)sharer didCompleteWithResults:(NSDictionary<NSString *, id> *)results;
@optional
- (void)sharerDidCancel:(id<FBSDKSharing>)sharer;
@end
Record for each protocol:
- Protocol name
- Whether it has
@requiredmethods (→[Abstract]) - Whether it has
@optionalmethods - Whether it's used as a delegate (→
[Protocol, Model]) - Whether it's used as a type constraint only (→
[Protocol]without[Model]) - Parent protocols
Delegate vs Type-constraint heuristic:
- If a class has a
delegateproperty typed as this protocol → it's a delegate, use[Model] - If it's used as a generic constraint or return type → it's a type constraint, no
[Model] - If unsure → use
[Model](safer, generates both interface and abstract class)
Properties
// Read-only property
@property (nonatomic, readonly, copy) NSString *tokenString;
// → [Export ("tokenString")] string TokenString { get; }
// Read-write property
@property (nonatomic, strong) NSString *name;
// → [Export ("name", ArgumentSemantic.Strong)] string Name { get; set; }
// Nullable property
@property (nonatomic, copy, nullable) NSString *email;
// → [NullAllowed] [Export ("email", ArgumentSemantic.Copy)] string Email { get; set; }
// Class property (static)
@property (class, nonatomic, readonly) FBSDKAccessToken *currentAccessToken;
// → [Static] [NullAllowed] [Export ("currentAccessToken")] AccessToken CurrentAccessToken { get; }
Property attribute mapping:
| ObjC Attribute | C# Equivalent |
|---|---|
readonly |
Only get; accessor |
readwrite (default) |
Both get; set; |
copy |
ArgumentSemantic.Copy |
strong / retain |
ArgumentSemantic.Strong |
weak |
ArgumentSemantic.Weak (+ [NullAllowed]) |
assign |
ArgumentSemantic.Assign (value types) |
nullable |
[NullAllowed] |
nonnull |
No attribute needed (default) |
class |
[Static] |
nonatomic |
No C# equivalent needed |
Methods
// Instance method, no params
- (void)logOut;
// → [Export ("logOut")] void LogOut ();
// Instance method with params
- (void)logInWithPermissions:(NSArray<NSString *> *)permissions
fromViewController:(UIViewController *)viewController
handler:(FBSDKLoginManagerLoginResultBlock)handler;
// → [Export ("logInWithPermissions:fromViewController:handler:")]
// void LogIn (string[] permissions, [NullAllowed] UIViewController viewController,
// Action<LoginManagerLoginResult, NSError> handler);
// Class method (static)
+ (instancetype)sharedInstance;
// → [Static] [Export ("sharedInstance")] MyClass SharedInstance { get; }
// Factory method — CAUTION: may not exist at runtime for Swift classes
+ (instancetype)tokenWithString:(NSString *)string;
// → Prefer binding as constructor: [Export ("initWithString:")] NativeHandle Constructor (string str);
// Failable initializer
- (nullable instancetype)initWithURL:(NSURL *)url;
// → [Export ("initWithURL:")] NativeHandle Constructor (NSUrl url);
// (add [return: NullAllowed] if failable)
Method selector construction:
- No params: method name only →
"logOut" - One param: method name +
:→"initWithString:" - Multiple params: first part +
:+ subsequent labels +:→"logInWithPermissions:fromViewController:handler:"
Enums
// NS_ENUM — standard enum
typedef NS_ENUM(NSInteger, FBSDKLoginTracking) {
FBSDKLoginTrackingEnabled,
FBSDKLoginTrackingLimited,
};
// → [Native]
// public enum LoginTracking : long {
// Enabled = 0,
// Limited = 1,
// }
// NS_OPTIONS — flags enum
typedef NS_OPTIONS(NSUInteger, FBSDKGameRequestFilter) {
FBSDKGameRequestFilterNone = 0,
FBSDKGameRequestFilterAppUsers = 1 << 0,
FBSDKGameRequestFilterAppNonUsers = 1 << 1,
};
// → [Native] [Flags]
// public enum GameRequestFilter : ulong {
// None = 0, AppUsers = 1, AppNonUsers = 2,
// }
// NS_CLOSED_ENUM — exhaustive enum
typedef NS_CLOSED_ENUM(NSInteger, FBSDKAppEventName) { ... };
// → Same as NS_ENUM but guarantees no new cases in future
Enum naming convention: Strip the common prefix. FBSDKLoginTrackingEnabled → Enabled.
Constants and Fields
// String constant
FOUNDATION_EXPORT NSString *const FBSDKErrorDomain;
// → [Field ("FBSDKErrorDomain", "FrameworkName")]
// NSString ErrorDomain { get; }
// Notification name
FOUNDATION_EXPORT NSNotificationName const FBSDKAccessTokenDidChangeNotification;
// → [Notification]
// [Field ("FBSDKAccessTokenDidChangeNotification", "FrameworkName")]
// NSString DidChangeNotification { get; }
// Numeric constant
FOUNDATION_EXPORT const NSInteger FBSDKErrorCodeNetwork;
// → [Field ("FBSDKErrorCodeNetwork", "FrameworkName")]
// nint ErrorCodeNetwork { get; }
Critical: The library parameter must be the framework name (e.g., "FBSDKCoreKit"), not "__Internal".
Blocks (Callbacks)
typedef void (^FBSDKLoginManagerLoginResultBlock)(FBSDKLoginManagerLoginResult *_Nullable result,
NSError *_Nullable error);
// → delegate void LoginManagerLoginResultHandler (
// [NullAllowed] LoginManagerLoginResult result,
// [NullAllowed] NSError error);
// Or inline as Action/Func:
// → Action<LoginManagerLoginResult, NSError> handler
Block mapping rules:
void (^)(void)→Actionvoid (^)(Type1, Type2)→Action<Type1, Type2>or named delegateReturnType (^)(Type1)→Func<Type1, ReturnType>or named delegate- Complex blocks with nullable params → use named delegate for clarity
Categories (Extensions)
@interface NSString (MyFrameworkAdditions)
- (NSString *)myFramework_urlEncode;
@end
// → [Category]
// [BaseType (typeof (NSString))]
// interface NSStringMyFrameworkAdditions {
// [Export ("myFramework_urlEncode")]
// string UrlEncode ();
// }
Dependency Analysis
Finding Framework Dependencies
Check these sources for import relationships:
# 1. Check umbrella header imports
cat "$HEADERS/MyFramework.h"
# 2. Search all headers for imports of other frameworks
grep -r "#import <" "$HEADERS/" | grep -v "MyFramework/" | sort -u
# 3. Check module map for dependencies
cat "$FRAMEWORK/$SLICE/MyFramework.framework/Modules/module.modulemap"
# 4. Check linked frameworks in binary (if needed)
otool -L "$FRAMEWORK/$SLICE/MyFramework.framework/MyFramework" 2>/dev/null | grep -v "/usr/lib"
Building the Dependency Graph
For a multi-framework SDK:
- List all frameworks in the SDK
- For each framework, find which other SDK frameworks it imports
- Build a DAG (directed acyclic graph) of dependencies
- Determine build order — bind leaf frameworks first (those with no SDK dependencies)
Example (Facebook SDK):
FBSDKCoreKit_Basics (no SDK deps) ─┐
FBAEMKit (no SDK deps) ────────────┤
├─→ FBSDKCoreKit ─┬─→ FBSDKLoginKit
├─→ FBSDKShareKit ──→ FBSDKGamingServicesKit
└───────────────────┘
Binding order: Basics, AEMKit → CoreKit → LoginKit, ShareKit → GamingServicesKit
Cross-Framework Type References
When Framework B depends on Framework A:
- Types from A used in B's API must be bound in A's project first
- B's binding project needs a
<ProjectReference>to A's binding project - Do not re-declare types from A in B's binding — use
usingimports
SDK Version Diff Workflow
When updating bindings for a new SDK version:
1. Identify Changes
# Diff headers between old and new versions
diff -r OldFramework.xcframework/ios-arm64/OldFramework.framework/Headers \
NewFramework.xcframework/ios-arm64/NewFramework.framework/Headers
2. Categorize Changes
| Change Type | Action |
|---|---|
| New class | Add new [BaseType] interface |
| New method on existing class | Add [Export] to existing interface |
| New property | Add property to existing interface |
| New enum value | Add to existing enum |
| Removed API | Add [Obsolete] or remove (breaking change) |
| Changed parameter type | Update binding signature |
| New protocol | Add [Protocol, Model] interface |
| Renamed API | Keep old with [Obsolete], add new |
3. Handle Deprecations
// API deprecated in header with __deprecated_msg("Use newMethod instead")
[Deprecated (PlatformName.iOS, 16, 0, message: "Use 'NewMethod' instead.")]
[Export ("oldMethod")]
void OldMethod ();
API Inventory Template
Use this structure to document extracted APIs before writing bindings:
## Framework: MyFramework
Type: Mixed ObjC + Swift
Headers: 15 total (14 ObjC + 1 Swift-generated)
Dependencies: Foundation, UIKit, CoreGraphics, OtherSDKFramework
### Classes (8)
1. MyManager — NSObject, singleton, main entry point
- Properties: sharedInstance (static), isConfigured (readonly), delegate (weak)
- Methods: configure(options:), reset(), logEvent(name:params:)
- Constructors: none (use sharedInstance)
- Notes: [DisableDefaultCtor]
2. MyConfiguration — NSObject, data class
- Properties: appId, appSecret, environment
- Constructors: initWithAppId:appSecret:
- Protocols: NSCopying, NSSecureCoding
### Protocols (3)
1. MyManagerDelegate — delegate pattern
- Required: managerDidFinish(manager:result:)
- Optional: managerDidFail(manager:error:)
2. MyConfigurable — type constraint (used as <MyConfigurable> param type)
- Required: configure(with:)
### Enums (4)
1. MyEnvironment — NS_ENUM(NSInteger) — Production=0, Sandbox=1, Debug=2
2. MyLogLevel — NS_ENUM(NSInteger) — None=0, Error=1, Warning=2, Info=3, Debug=4
3. MyFeatureFlags — NS_OPTIONS(NSUInteger) — None=0, FeatureA=1, FeatureB=2, FeatureC=4
4. MyErrorCode — NS_ENUM(NSInteger) — Unknown=0, Network=1, Auth=2, NotFound=3
### Constants (6)
1. MyErrorDomain — NSString constant
2. MyDidConfigureNotification — NSNotificationName
3. MyVersionString — NSString constant
4. MyVersionNumber — double constant
### Blocks (2)
1. MyCompletionHandler — void(^)(MyResult *_Nullable, NSError *_Nullable)
2. MyProgressHandler — void(^)(double progress)
What to Skip
Not everything in headers needs binding. Skip:
- Internal/private prefixed APIs — Methods starting with
_or containinginternal/private - Deprecated-only APIs — If already deprecated and there's a replacement, consider skipping
- C functions — Unless essential; they need
[DllImport]P/Invoke instead of binding attributes - Inline functions / macros — Cannot be bound; reimplement in C# if needed
- Unavailable APIs — Marked with
NS_UNAVAILABLEorUNAVAILABLE_ATTRIBUTE - Swift-only APIs — Not marked
@objc, won't appear in-Swift.h - Test/debug-only classes — Internal testing utilities
References
- references/objc-declaration-patterns.md — Complete reference of ObjC declaration syntax patterns and their C# binding equivalents
Converted and distributed by TomeVault — claim your Tome and manage your conversions.