[ffigen] Objective C interfaces (#287)

diff --git a/pkgs/ffigen/lib/src/code_generator.dart b/pkgs/ffigen/lib/src/code_generator.dart
index 0e175e6..6944957 100644
--- a/pkgs/ffigen/lib/src/code_generator.dart
+++ b/pkgs/ffigen/lib/src/code_generator.dart
@@ -13,6 +13,7 @@
 export 'code_generator/global.dart';
 export 'code_generator/imports.dart';
 export 'code_generator/library.dart';
+export 'code_generator/objc_interface.dart';
 export 'code_generator/struc.dart';
 export 'code_generator/type.dart';
 export 'code_generator/typealias.dart';
diff --git a/pkgs/ffigen/lib/src/code_generator/binding_string.dart b/pkgs/ffigen/lib/src/code_generator/binding_string.dart
index 2929ee8..031bf5a 100644
--- a/pkgs/ffigen/lib/src/code_generator/binding_string.dart
+++ b/pkgs/ffigen/lib/src/code_generator/binding_string.dart
@@ -23,4 +23,5 @@
   global,
   enumClass,
   typeDef,
+  objcInterface,
 }
diff --git a/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart b/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart
index 8e0de3c..93d5877 100644
--- a/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart
+++ b/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart
@@ -7,65 +7,66 @@
 /// Source: https://dart.dev/guides/language/language-tour#keywords.
 const keywords = {
   'abstract',
-  'else',
-  'import',
-  'super',
   'as',
-  'enum',
-  'in',
-  'switch',
   'assert',
-  'export',
-  'interface',
-  'sync',
   'async',
-  'extends',
-  'is',
-  'this',
   'await',
-  'extension',
-  'library',
-  'throw',
   'break',
-  'external',
-  'mixin',
-  'true',
   'case',
-  'factory',
-  'new',
-  'try',
   'catch',
-  'false',
-  'null',
-  'typedef',
   'class',
-  'final',
-  'on',
-  'var',
   'const',
-  'finally',
-  'operator',
-  'void',
   'continue',
-  'for',
-  'part',
-  'while',
   'covariant',
-  'Function',
-  'rethrow',
-  'with',
   'default',
-  'get',
-  'return',
-  'yield',
   'deferred',
-  'hide',
-  'set',
   'do',
-  'if',
-  'show',
   'dynamic',
+  'else',
+  'enum',
+  'export',
+  'extends',
+  'extension',
+  'external',
+  'factory',
+  'false',
+  'final',
+  'finally',
+  'for',
+  'Function',
+  'get',
+  'hide',
+  'if',
   'implements',
-  'static',
+  'import',
+  'in',
+  'interface',
+  'is',
   'late',
+  'library',
+  'mixin',
+  'new',
+  'null',
+  'on',
+  'operator',
+  'part',
+  'required',
+  'rethrow',
+  'return',
+  'set',
+  'show',
+  'static',
+  'super',
+  'switch',
+  'sync',
+  'this',
+  'throw',
+  'true',
+  'try',
+  'typedef',
+  'var',
+  'void',
+  'while',
+  'with',
+  'yield',
 };
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
new file mode 100644
index 0000000..de76111
--- /dev/null
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -0,0 +1,380 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:ffigen/src/code_generator.dart';
+
+import 'binding_string.dart';
+import 'utils.dart';
+import 'writer.dart';
+
+// Class methods defined on NSObject that we don't want to copy to child objects
+// by default.
+const _excludedNSObjectClassMethods = {
+  'allocWithZone:',
+  'copyWithZone:',
+  'mutableCopyWithZone:',
+  'instancesRespondToSelector:',
+  'conformsToProtocol:',
+  'instanceMethodForSelector:',
+  'instanceMethodSignatureForSelector:',
+  'isSubclassOfClass:',
+  'resolveClassMethod:',
+  'resolveInstanceMethod:',
+  'hash',
+  'superclass',
+  'class',
+  'description',
+  'debugDescription',
+};
+
+class _ObjCBuiltInFunctions {
+  late final _registerNameFunc = Func(
+    name: '_sel_registerName',
+    originalName: 'sel_registerName',
+    returnType: Type.pointer(Type.struct(objCSelType)),
+    parameters: [
+      Parameter(name: 'str', type: Type.pointer(Type.importedType(charType)))
+    ],
+  );
+  late final String registerName;
+
+  late final _getClassFunc = Func(
+    name: '_objc_getClass',
+    originalName: 'objc_getClass',
+    returnType: Type.pointer(Type.struct(objCObjectType)),
+    parameters: [
+      Parameter(name: 'str', type: Type.pointer(Type.importedType(charType)))
+    ],
+  );
+  late final String getClass;
+
+  // We need to load a separate instance of objc_msgSend for each signature.
+  final _msgSendFuncs = <String, Func>{};
+  Func getMsgSendFunc(Type returnType, List<ObjCMethodParam> params) {
+    // TODO(#279): These keys don't dedupe sufficiently.
+    var key = returnType.hashCode.toRadixString(36);
+    for (final p in params) {
+      key += ' ' + p.type.hashCode.toRadixString(36);
+    }
+    _msgSendFuncs[key] ??= Func(
+      name: '_objc_msgSend_${_msgSendFuncs.length}',
+      originalName: 'objc_msgSend',
+      returnType: returnType,
+      parameters: [
+        Parameter(name: 'obj', type: Type.pointer(Type.struct(objCObjectType))),
+        Parameter(name: 'sel', type: Type.pointer(Type.struct(objCSelType))),
+        for (final p in params) Parameter(name: p.name, type: p.type.type),
+      ],
+    );
+    return _msgSendFuncs[key]!;
+  }
+
+  bool utilsExist = false;
+  void ensureUtilsExist(Writer w, StringBuffer s) {
+    if (utilsExist) return;
+    utilsExist = true;
+
+    registerName = w.topLevelUniqueNamer.makeUnique('_registerName');
+    final selType = _registerNameFunc.functionType.returnType.getCType(w);
+    s.write('\n$selType $registerName(${w.className} _lib, String name) {\n');
+    s.write('  final cstr = name.toNativeUtf8();\n');
+    s.write('  final sel = _lib.${_registerNameFunc.name}(cstr.cast());\n');
+    s.write('  ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
+    s.write('  return sel;\n');
+    s.write('}\n');
+
+    getClass = w.topLevelUniqueNamer.makeUnique('_getClass');
+    final objType = _getClassFunc.functionType.returnType.getCType(w);
+    s.write('\n$objType $getClass(${w.className} _lib, String name) {\n');
+    s.write('  final cstr = name.toNativeUtf8();\n');
+    s.write('  final clazz = _lib.${_getClassFunc.name}(cstr.cast());\n');
+    s.write('  ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
+    s.write('  return clazz;\n');
+    s.write('}\n');
+
+    s.write('\nclass _ObjCWrapper {\n');
+    s.write('  final $objType _id;\n');
+    s.write('  final ${w.className} _lib;\n');
+    s.write('  _ObjCWrapper._(this._id, this._lib);\n');
+    s.write('}\n');
+  }
+
+  void addDependencies(Set<Binding> dependencies) {
+    _registerNameFunc.addDependencies(dependencies);
+    _getClassFunc.addDependencies(dependencies);
+    for (final func in _msgSendFuncs.values) {
+      func.addDependencies(dependencies);
+    }
+  }
+}
+
+final _builtInFunctions = _ObjCBuiltInFunctions();
+
+class ObjCInterface extends NoLookUpBinding {
+  ObjCInterface? superType;
+  final methods = <ObjCMethod>[];
+  bool filled = false;
+
+  // Objective C supports overriding class methods, but Dart doesn't support
+  // overriding static methods. So in our generated Dart code, child classes
+  // must explicitly implement all the class methods of their super type. To
+  // help with this, we store the class methods in this map, as well as in the
+  // methods list.
+  final classMethods = <String, ObjCMethod>{};
+
+  ObjCInterface({
+    String? usr,
+    required String originalName,
+    required String name,
+    String? dartDoc,
+  }) : super(
+          usr: usr,
+          originalName: originalName,
+          name: name,
+          dartDoc: dartDoc,
+        );
+
+  @override
+  BindingString toBindingString(Writer w) {
+    final s = StringBuffer();
+    if (dartDoc != null) {
+      s.write(makeDartDoc(dartDoc!));
+    }
+
+    final uniqueNamer = UniqueNamer({name});
+    final natLib = w.className;
+
+    _builtInFunctions.ensureUtilsExist(w, s);
+    final objType = Type.pointer(Type.struct(objCObjectType)).getCType(w);
+    final selType = Type.pointer(Type.struct(objCSelType)).getCType(w);
+
+    // Class declaration.
+    s.write('class $name ');
+    uniqueNamer.markUsed('_id');
+    s.write('extends ${superType?.name ?? '_ObjCWrapper'} {\n');
+    s.write('  $name._($objType id, $natLib lib) : super._(id, lib);\n\n');
+
+    // Class object, used to call static methods.
+    final classObject = uniqueNamer.makeUnique('_class');
+    s.write('  static $objType? $classObject;\n\n');
+
+    // Cast method.
+    s.write('  static $name castFrom<T extends _ObjCWrapper>(T other) {\n');
+    s.write('    return $name._(other._id, other._lib);\n');
+    s.write('  }\n\n');
+
+    // Methods.
+    for (final m in methods) {
+      final methodName = m._getDartMethodName(uniqueNamer);
+      final selName = uniqueNamer.makeUnique('_sel_$methodName');
+      final isStatic = m.kind == ObjCMethodKind.classMethod;
+
+      // SEL object for the method.
+      s.write('  static $selType? $selName;');
+
+      // The method declaration.
+      if (m.dartDoc != null) {
+        s.write(makeDartDoc(m.dartDoc!));
+      }
+      s.write('  ');
+      if (isStatic) s.write('static ');
+      s.write('${m.returnType!.getConvertedType(w, name)} ');
+      if (m.kind == ObjCMethodKind.propertyGetter) s.write('get ');
+      if (m.kind == ObjCMethodKind.propertySetter) s.write('set ');
+      s.write(methodName);
+      if (m.kind != ObjCMethodKind.propertyGetter) {
+        s.write('(');
+        var first = true;
+        if (isStatic) {
+          first = false;
+          s.write('$natLib _lib');
+        }
+        for (final p in m.params) {
+          if (first) {
+            first = false;
+          } else {
+            s.write(', ');
+          }
+          s.write('${p.type.getConvertedType(w, name)} ${p.name}');
+        }
+        s.write(')');
+      }
+      s.write(' {\n');
+
+      // Implementation.
+      if (isStatic) {
+        s.write('    $classObject ??= '
+            '${_builtInFunctions.getClass}(_lib, "$originalName");\n');
+      }
+      s.write('    $selName ??= '
+          '${_builtInFunctions.registerName}(_lib, "${m.originalName}");\n');
+      final convertReturn = m.returnType!.needsConverting;
+      s.write('    ${convertReturn ? 'final _ret = ' : 'return '}');
+      s.write('_lib.${m.msgSend!.name}(');
+      s.write(isStatic ? '_class!' : '_id');
+      s.write(', $selName!');
+      for (final p in m.params) {
+        s.write(', ${p.type.doArgConversion(p.name)}');
+      }
+      s.write(');\n');
+      if (convertReturn) {
+        final result = m.returnType!.doReturnConversion('_ret', name, '_lib');
+        s.write('    return $result;');
+      }
+
+      s.write('  }\n\n');
+    }
+
+    s.write('}\n\n');
+
+    return BindingString(
+        type: BindingStringType.objcInterface, string: s.toString());
+  }
+
+  @override
+  void addDependencies(Set<Binding> dependencies) {
+    if (dependencies.contains(this)) return;
+    dependencies.add(this);
+
+    if (superType != null) {
+      superType!.addDependencies(dependencies);
+      // Copy class methods from the super type, because Dart classes don't
+      // inherit static methods.
+      for (final m in superType!.classMethods.values) {
+        if (!_excludedNSObjectClassMethods.contains(m.originalName)) {
+          addMethod(m);
+        }
+      }
+    }
+
+    for (final m in methods) {
+      m.addDependencies(dependencies);
+    }
+
+    _builtInFunctions.addDependencies(dependencies);
+  }
+
+  void addMethod(ObjCMethod method) {
+    methods.add(method);
+    if (method.kind == ObjCMethodKind.classMethod) {
+      classMethods[method.originalName] ??= method;
+    }
+  }
+}
+
+enum ObjCMethodKind {
+  instanceMethod,
+  classMethod,
+  propertyGetter,
+  propertySetter,
+}
+
+class ObjCProperty {
+  final String originalName;
+  String? dartName;
+  ObjCProperty(this.originalName);
+}
+
+class ObjCMethod {
+  final String? dartDoc;
+  final String originalName;
+  final ObjCProperty? property;
+  ObjCMethodType? returnType;
+  final params = <ObjCMethodParam>[];
+  final ObjCMethodKind kind;
+  Func? msgSend;
+
+  ObjCMethod({
+    required this.originalName,
+    this.property,
+    this.dartDoc,
+    required this.kind,
+  });
+
+  void addDependencies(Set<Binding> dependencies) {
+    returnType!.type.addDependencies(dependencies);
+    for (final p in params) {
+      p.type.type.addDependencies(dependencies);
+    }
+    msgSend = _builtInFunctions.getMsgSendFunc(returnType!.type, params);
+  }
+
+  String _getDartMethodName(UniqueNamer uniqueNamer) {
+    if (property != null) {
+      // A getter and a setter are allowed to have the same name, so we can't
+      // just run the name through uniqueNamer. Instead they need to share
+      // the dartName, which is run through uniqueNamer.
+      if (property!.dartName == null) {
+        property!.dartName = uniqueNamer.makeUnique(property!.originalName);
+      }
+      return property!.dartName!;
+    }
+    // Objective C methods can look like:
+    // foo
+    // foo:
+    // foo:someArgName:
+    // If there is a trailing ':', omit it. Replace all other ':' with '_'.
+    var name = originalName;
+    final index = name.indexOf(':');
+    if (index != -1) name = name.substring(0, index);
+    return uniqueNamer.makeUnique(name.replaceAll(':', '_'));
+  }
+}
+
+class ObjCMethodParam {
+  final ObjCMethodType type;
+  final String name;
+  ObjCMethodParam(Type t, this.name) : type = ObjCMethodType(t);
+}
+
+// Wrapper around Type with helper methods for converting between the internal
+// types passed to native code, and the external types visible to the user. For
+// example, ObjCInterfaces are passed to native as Pointer<ObjCObject>, but the
+// user sees the Dart wrapper class.
+class ObjCMethodType {
+  final Type type;
+  ObjCMethodType(this.type);
+
+  bool get isObject {
+    if (type.broadType != BroadType.Pointer) return false;
+    final child = type.child!;
+    if (child.broadType != BroadType.Compound) return false;
+    return child.compound == objCObjectType;
+  }
+
+  bool get isInstanceType {
+    if (type.broadType != BroadType.Typealias) return false;
+    final alias = type.typealias!;
+    if (alias.name != 'instancetype') return false;
+    return ObjCMethodType(alias.type).isObject;
+  }
+
+  bool get isInterface => type.broadType == BroadType.ObjCInterface;
+  bool get isBool => type.broadType == BroadType.Boolean;
+  bool get needsConverting =>
+      isInterface || isBool || isObject || isInstanceType;
+
+  String getConvertedType(Writer w, String enclosingClass) {
+    if (isBool) return 'bool';
+    if (isInterface) return type.objCInterface!.name;
+    if (isObject) return 'NSObject';
+    if (isInstanceType) return enclosingClass;
+    return type.getDartType(w);
+  }
+
+  String doArgConversion(String value) {
+    if (isBool) return '$value ? 1 : 0';
+    if (isInterface || isObject || isInstanceType) return '$value._id';
+    return value;
+  }
+
+  String doReturnConversion(
+      String value, String enclosingClass, String library) {
+    if (isBool) return '$value != 0';
+    if (isInterface) return '${type.objCInterface!.name}._($value, $library)';
+    if (isObject) return 'NSObject._($value, $library)';
+    if (isInstanceType) return '$enclosingClass._($value, $library)';
+    return value;
+  }
+}
diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart
index b70b4ec..a708167 100644
--- a/pkgs/ffigen/lib/src/code_generator/type.dart
+++ b/pkgs/ffigen/lib/src/code_generator/type.dart
@@ -55,6 +55,9 @@
   ConstantArray,
   IncompleteArray,
 
+  /// Represents an Objective C interface.
+  ObjCInterface,
+
   /// Used as a marker, so that declarations having these can exclude them.
   Unimplemented,
 }
@@ -98,6 +101,9 @@
   /// Reference to the [ImportedType] this type refers to.
   ImportedType? importedType;
 
+  /// Reference to the [ObjCInterface] this type refers to.
+  ObjCInterface? objCInterface;
+
   /// For providing [SupportedNativeType] only.
   final SupportedNativeType? nativeType;
 
@@ -123,6 +129,7 @@
     this.typealias,
     this.functionType,
     this.importedType,
+    this.objCInterface,
     this.length,
     this.unimplementedReason,
   });
@@ -184,6 +191,10 @@
   factory Type.handle() {
     return Type._(broadType: BroadType.Handle);
   }
+  factory Type.objCInterface(ObjCInterface objCInterface) {
+    return Type._(
+        broadType: BroadType.ObjCInterface, objCInterface: objCInterface);
+  }
 
   /// Get all dependencies of this type and save them in [dependencies].
   void addDependencies(Set<Binding> dependencies) {
@@ -198,6 +209,8 @@
         return typealias!.addDependencies(dependencies);
       case BroadType.Enum:
         return enumClass!.addDependencies(dependencies);
+      case BroadType.ObjCInterface:
+        return objCInterface!.addDependencies(dependencies);
       default:
         if (child != null) {
           return child!.addDependencies(dependencies);
@@ -269,13 +282,14 @@
         return '${w.ffiLibraryPrefix}.${_primitives[enumNativeType]!.c}';
       case BroadType.NativeFunction:
         return '${w.ffiLibraryPrefix}.NativeFunction<${nativeFunc!.type.getCType(w)}>';
-      case BroadType
-          .IncompleteArray: // Array parameters are treated as Pointers in C.
+      case BroadType.IncompleteArray:
+        // Array parameters are treated as Pointers in C.
         return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>';
-      case BroadType
-          .ConstantArray: // Array parameters are treated as Pointers in C.
+      case BroadType.ConstantArray:
+        // Array parameters are treated as Pointers in C.
         return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>';
-      case BroadType.Boolean: // Booleans are treated as uint8.
+      case BroadType.Boolean:
+        // Booleans are treated as uint8.
         return '${w.ffiLibraryPrefix}.${_primitives[SupportedNativeType.Uint8]!.c}';
       case BroadType.Handle:
         return '${w.ffiLibraryPrefix}.Handle';
@@ -283,6 +297,8 @@
         return functionType!.getCType(w);
       case BroadType.ImportedType:
         return '${importedType!.libraryImport.prefix}.${importedType!.cType}';
+      case BroadType.ObjCInterface:
+        return Type.pointer(Type.struct(objCObjectType)).getCType(w);
       case BroadType.Typealias:
         return typealias!.name;
       case BroadType.Unimplemented:
@@ -302,13 +318,14 @@
         return _primitives[enumNativeType]!.dart;
       case BroadType.NativeFunction:
         return '${w.ffiLibraryPrefix}.NativeFunction<${nativeFunc!.type.getDartType(w)}>';
-      case BroadType
-          .IncompleteArray: // Array parameters are treated as Pointers in C.
+      case BroadType.IncompleteArray:
+        // Array parameters are treated as Pointers in C.
         return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>';
-      case BroadType
-          .ConstantArray: // Array parameters are treated as Pointers in C.
+      case BroadType.ConstantArray:
+        // Array parameters are treated as Pointers in C.
         return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>';
-      case BroadType.Boolean: // Booleans are treated as uint8.
+      case BroadType.Boolean:
+        // Booleans are treated as uint8.
         return _primitives[SupportedNativeType.Uint8]!.dart;
       case BroadType.Handle:
         return 'Object';
@@ -320,6 +337,8 @@
         } else {
           return importedType!.dartType;
         }
+      case BroadType.ObjCInterface:
+        return getCType(w);
       case BroadType.Typealias:
         // Typealias cannot be used by name in Dart types unless both the C and
         // Dart type of the underlying types are same.
diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart
index 0fd7515..d3ff495 100644
--- a/pkgs/ffigen/lib/src/code_generator/writer.dart
+++ b/pkgs/ffigen/lib/src/code_generator/writer.dart
@@ -21,6 +21,8 @@
   final symbolAddressWriter = SymbolAddressWriter();
 
   late String _className;
+  String get className => _className;
+
   final String? classDocComment;
 
   String? _ffiLibraryPrefix;
@@ -31,6 +33,14 @@
     return _ffiLibraryPrefix!;
   }
 
+  String? _ffiPkgLibraryPrefix;
+  String get ffiPkgLibraryPrefix {
+    _ffiPkgLibraryPrefix ??= libraryImports
+        .firstWhere((element) => element.name == ffiPkgImport.name)
+        .prefix;
+    return _ffiPkgLibraryPrefix!;
+  }
+
   final Set<LibraryImport> libraryImports;
 
   late String _lookupFuncIdentifier;
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart
index 1f0c7a6..6ba4f4e 100644
--- a/pkgs/ffigen/lib/src/config_provider/config_types.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -29,6 +29,7 @@
 }
 
 enum CommentStyle { doxygen, any }
+
 enum CommentLength { none, brief, full }
 
 enum CompoundDependencies { full, opaque }
diff --git a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
index 2c0c33f..0b76fcb 100644
--- a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -759,6 +759,24 @@
   late final _clang_getArgType =
       _clang_getArgTypePtr.asFunction<CXType Function(CXType, int)>();
 
+  /// Retrieves the base type of the ObjCObjectType.
+  ///
+  /// If the type is not an ObjC object, an invalid type is returned.
+  CXType clang_Type_getObjCObjectBaseType(
+    CXType T,
+  ) {
+    return _clang_Type_getObjCObjectBaseType(
+      T,
+    );
+  }
+
+  late final _clang_Type_getObjCObjectBaseTypePtr =
+      _lookup<ffi.NativeFunction<CXType Function(CXType)>>(
+          'clang_Type_getObjCObjectBaseType');
+  late final _clang_Type_getObjCObjectBaseType =
+      _clang_Type_getObjCObjectBaseTypePtr
+          .asFunction<CXType Function(CXType)>();
+
   /// Return the number of elements of an array or vector type.
   ///
   /// If a type is passed in that is not an array or vector type,
@@ -983,6 +1001,40 @@
   late final _clang_getCursorDefinition =
       _clang_getCursorDefinitionPtr.asFunction<CXCursor Function(CXCursor)>();
 
+  /// Given a cursor that represents a property declaration, return the
+  /// name of the method that implements the getter.
+  CXString clang_Cursor_getObjCPropertyGetterName(
+    CXCursor C,
+  ) {
+    return _clang_Cursor_getObjCPropertyGetterName(
+      C,
+    );
+  }
+
+  late final _clang_Cursor_getObjCPropertyGetterNamePtr =
+      _lookup<ffi.NativeFunction<CXString Function(CXCursor)>>(
+          'clang_Cursor_getObjCPropertyGetterName');
+  late final _clang_Cursor_getObjCPropertyGetterName =
+      _clang_Cursor_getObjCPropertyGetterNamePtr
+          .asFunction<CXString Function(CXCursor)>();
+
+  /// Given a cursor that represents a property declaration, return the
+  /// name of the method that implements the setter, if any.
+  CXString clang_Cursor_getObjCPropertySetterName(
+    CXCursor C,
+  ) {
+    return _clang_Cursor_getObjCPropertySetterName(
+      C,
+    );
+  }
+
+  late final _clang_Cursor_getObjCPropertySetterNamePtr =
+      _lookup<ffi.NativeFunction<CXString Function(CXCursor)>>(
+          'clang_Cursor_getObjCPropertySetterName');
+  late final _clang_Cursor_getObjCPropertySetterName =
+      _clang_Cursor_getObjCPropertySetterNamePtr
+          .asFunction<CXString Function(CXCursor)>();
+
   /// Given a cursor that represents a declaration, return the associated
   /// comment's source range.  The range may include multiple consecutive comments
   /// with whitespace in between.
diff --git a/pkgs/ffigen/lib/src/header_parser/includer.dart b/pkgs/ffigen/lib/src/header_parser/includer.dart
index 4f3bbbf..b2791f8 100644
--- a/pkgs/ffigen/lib/src/header_parser/includer.dart
+++ b/pkgs/ffigen/lib/src/header_parser/includer.dart
@@ -35,6 +35,11 @@
       usr, name, bindingsIndex.isSeenType, config.functionDecl.shouldInclude);
 }
 
+bool shouldIncludeInterface(String usr, String name) {
+  // TODO(#279): Check config YAML.
+  return true;
+}
+
 bool shouldIncludeEnumClass(String usr, String name) {
   return _shouldIncludeDecl(
       usr, name, bindingsIndex.isSeenType, config.enumClassDecl.shouldInclude);
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
new file mode 100644
index 0000000..1905c53
--- /dev/null
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
@@ -0,0 +1,190 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/header_parser/data.dart';
+import 'package:logging/logging.dart';
+
+import '../clang_bindings/clang_bindings.dart' as clang_types;
+import '../includer.dart';
+import '../utils.dart';
+
+final _logger = Logger('ffigen.header_parser.objcinterfacedecl_parser');
+
+class _ParsedObjCInterface {
+  ObjCInterface interface;
+  _ParsedObjCInterface(this.interface);
+}
+
+class _ParsedObjCMethod {
+  ObjCMethod method;
+  bool hasError = false;
+  _ParsedObjCMethod(this.method);
+}
+
+final _interfaceStack = Stack<_ParsedObjCInterface>();
+final _methodStack = Stack<_ParsedObjCMethod>();
+
+Type? parseObjCInterfaceDeclaration(clang_types.CXCursor cursor) {
+  final itfUsr = cursor.usr();
+  final itfName = cursor.spelling();
+  if (!shouldIncludeInterface(itfUsr, itfName)) {
+    return null;
+  }
+
+  final t = cursor.type();
+  final name = t.spelling();
+
+  _logger.fine('++++ Adding ObjC interface: '
+      'Name: $name, ${cursor.completeStringRepr()}');
+
+  return Type.objCInterface(ObjCInterface(
+    usr: itfUsr, originalName: name,
+    name: name, // TODO(#279): config.interfaceDecl.renameUsingConfig(name),
+    dartDoc: getCursorDocComment(cursor),
+  ));
+}
+
+void fillObjCInterfaceMethodsIfNeeded(
+    ObjCInterface itf, clang_types.CXCursor cursor) {
+  if (itf.filled) return;
+  itf.filled = true; // Break cycles.
+
+  _logger.fine('++++ Filling ObjC interface: '
+      'Name: ${itf.originalName}, ${cursor.completeStringRepr()}');
+
+  _interfaceStack.push(_ParsedObjCInterface(itf));
+  clang.clang_visitChildren(
+      cursor,
+      Pointer.fromFunction(_parseInterfaceVisitor, exceptional_visitor_return),
+      nullptr);
+  _interfaceStack.pop();
+
+  _logger.fine('++++ Finished ObjC interface: '
+      'Name: ${itf.originalName}, ${cursor.completeStringRepr()}');
+}
+
+int _parseInterfaceVisitor(clang_types.CXCursor cursor,
+    clang_types.CXCursor parent, Pointer<Void> clientData) {
+  switch (cursor.kind) {
+    case clang_types.CXCursorKind.CXCursor_ObjCSuperClassRef:
+      _parseSuperType(cursor);
+      break;
+    case clang_types.CXCursorKind.CXCursor_ObjCPropertyDecl:
+      _parseProperty(cursor);
+      break;
+    case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl:
+    case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl:
+      _parseMethod(cursor);
+      break;
+  }
+  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
+}
+
+void _parseSuperType(clang_types.CXCursor cursor) {
+  final superType = cursor.type().toCodeGenType();
+  _logger.fine('       > Super type: '
+      '$superType ${cursor.completeStringRepr()}');
+  _interfaceStack.top.interface.superType = superType.objCInterface;
+}
+
+void _parseProperty(clang_types.CXCursor cursor) {
+  final itf = _interfaceStack.top.interface;
+  final fieldName = cursor.spelling();
+  final fieldType = cursor.type().toCodeGenType();
+  final dartDoc = getCursorDocComment(cursor);
+  final property = ObjCProperty(fieldName);
+  _logger.fine('       > Property: '
+      '$fieldType $fieldName ${cursor.completeStringRepr()}');
+
+  final getter = ObjCMethod(
+    originalName: clang
+        .clang_Cursor_getObjCPropertyGetterName(cursor)
+        .toStringAndDispose(),
+    property: property,
+    dartDoc: dartDoc,
+    kind: ObjCMethodKind.propertyGetter,
+  );
+  getter.returnType = ObjCMethodType(fieldType);
+  itf.addMethod(getter);
+
+  final setter = ObjCMethod(
+    originalName: clang
+        .clang_Cursor_getObjCPropertySetterName(cursor)
+        .toStringAndDispose(),
+    property: property,
+    dartDoc: dartDoc,
+    kind: ObjCMethodKind.propertySetter,
+  );
+  setter.returnType = ObjCMethodType(Type.nativeType(SupportedNativeType.Void));
+  setter.params.add(ObjCMethodParam(fieldType, 'value'));
+  itf.addMethod(setter);
+}
+
+void _parseMethod(clang_types.CXCursor cursor) {
+  final isClassMethod =
+      cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
+  final method = ObjCMethod(
+    originalName: cursor.spelling(),
+    dartDoc: getCursorDocComment(cursor),
+    kind: isClassMethod
+        ? ObjCMethodKind.classMethod
+        : ObjCMethodKind.instanceMethod,
+  );
+  final parsed = _ParsedObjCMethod(method);
+  _logger.fine('       > ${isClassMethod ? 'Class' : 'Instance'} method: '
+      '${method.originalName} ${cursor.completeStringRepr()}');
+  _methodStack.push(parsed);
+  clang.clang_visitChildren(
+      cursor,
+      Pointer.fromFunction(_parseMethodVisitor, exceptional_visitor_return),
+      nullptr);
+  _methodStack.pop();
+  if (parsed.hasError || method.returnType == null) {
+    // Discard it.
+    return;
+  }
+  _interfaceStack.top.interface.addMethod(method);
+}
+
+int _parseMethodVisitor(clang_types.CXCursor cursor,
+    clang_types.CXCursor parent, Pointer<Void> clientData) {
+  switch (cursor.kind) {
+    case clang_types.CXCursorKind.CXCursor_TypeRef:
+    case clang_types.CXCursorKind.CXCursor_ObjCClassRef:
+      _parseMethodReturnType(cursor);
+      break;
+    case clang_types.CXCursorKind.CXCursor_ParmDecl:
+      _parseMethodParam(cursor);
+      break;
+    default:
+  }
+  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
+}
+
+void _parseMethodReturnType(clang_types.CXCursor cursor) {
+  final parsed = _methodStack.top;
+  if (parsed.method.returnType != null) {
+    parsed.hasError = true;
+    _logger.fine(
+        '           >> Extra return type: ${cursor.completeStringRepr()}');
+    _logger.warning('Method "${parsed.method.originalName}" in instance '
+        '"${_interfaceStack.top.interface.originalName}" has multiple return '
+        'types.');
+  } else {
+    parsed.method.returnType = ObjCMethodType(cursor.type().toCodeGenType());
+    _logger.fine('           >> Return type: '
+        '${parsed.method.returnType} ${cursor.completeStringRepr()}');
+  }
+}
+
+void _parseMethodParam(clang_types.CXCursor cursor) {
+  final name = cursor.spelling();
+  final type = cursor.type().toCodeGenType();
+  _logger.fine(
+      '           >> Parameter: $type $name ${cursor.completeStringRepr()}');
+  _methodStack.top.method.params.add(ObjCMethodParam(type, name));
+}
diff --git a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
index 60c623e..7778ddf 100644
--- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
@@ -57,6 +57,9 @@
         case clang_types.CXCursorKind.CXCursor_VarDecl:
           addToBindings(parseVarDeclaration(cursor));
           break;
+        case clang_types.CXCursorKind.CXCursor_ObjCInterfaceDecl:
+          addToBindings(_getCodeGenTypeFromCursor(cursor)?.objCInterface);
+          break;
         default:
           _logger.finer('rootCursorVisitor: CursorKind not implemented');
       }
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
index 3c2f2dc..0f487a9 100644
--- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
+++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -13,6 +13,7 @@
 import '../data.dart';
 import '../sub_parsers/compounddecl_parser.dart';
 import '../sub_parsers/enumdecl_parser.dart';
+import '../sub_parsers/objcinterfacedecl_parser.dart';
 import '../type_extractor/cxtypekindmap.dart';
 import '../utils.dart';
 
@@ -38,17 +39,18 @@
         ignoreFilter: ignoreFilter, pointerReference: pointerReference);
   }
 
-  // Objective C types skip the cache, and are conditional on the language flag.
+  // These basic Objective C types skip the cache, and are conditional on the
+  // language flag.
   if (config.language == Language.objc) {
     switch (cxtype.kind) {
       case clang_types.CXTypeKind.CXType_ObjCObjectPointer:
       case clang_types.CXTypeKind.CXType_BlockPointer:
       case clang_types.CXTypeKind.CXType_ObjCId:
+      case clang_types.CXTypeKind.CXType_ObjCClass:
+      case clang_types.CXTypeKind.CXType_ObjCTypeParam:
         return Type.pointer(Type.struct(objCObjectType));
       case clang_types.CXTypeKind.CXType_ObjCSel:
         return Type.pointer(Type.struct(objCSelType));
-      case clang_types.CXTypeKind.CXType_ObjCClass:
-        return Type.struct(objCObjectType);
     }
   }
 
@@ -194,9 +196,10 @@
       } else {
         return _CreateTypeFromCursorResult(Type.enumClass(enumClass));
       }
+    case clang_types.CXTypeKind.CXType_ObjCInterface:
+      return _CreateTypeFromCursorResult(parseObjCInterfaceDeclaration(cursor));
     default:
-      throw UnimplementedError(
-          'Unknown cursor kind: ${cursor.completeStringRepr()}');
+      throw UnimplementedError('Unknown type: ${cxtype.completeStringRepr()}');
   }
 }
 
@@ -206,6 +209,8 @@
   if (type.compound != null) {
     fillCompoundMembersIfNeeded(type.compound!, cursor,
         ignoreFilter: ignoreFilter, pointerReference: pointerReference);
+  } else if (type.objCInterface != null) {
+    fillObjCInterfaceMethodsIfNeeded(type.objCInterface!, cursor);
   }
 }
 
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_interface_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_interface_bindings.dart
new file mode 100644
index 0000000..e584704
--- /dev/null
+++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_interface_bindings.dart
@@ -0,0 +1,852 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+import 'package:ffi/ffi.dart' as pkg_ffi;
+
+/// ObjC Interface Test
+class NativeLibrary {
+  /// Holds the symbol lookup function.
+  final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+      _lookup;
+
+  /// The symbols are looked up in [dynamicLibrary].
+  NativeLibrary(ffi.DynamicLibrary dynamicLibrary)
+      : _lookup = dynamicLibrary.lookup;
+
+  /// The symbols are looked up with [lookup].
+  NativeLibrary.fromLookup(
+      ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+          lookup)
+      : _lookup = lookup;
+
+  ffi.Pointer<ObjCSel> _sel_registerName(
+    ffi.Pointer<pkg_ffi.Char> str,
+  ) {
+    return __sel_registerName(
+      str,
+    );
+  }
+
+  late final __sel_registerNamePtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCSel> Function(
+              ffi.Pointer<pkg_ffi.Char>)>>('sel_registerName');
+  late final __sel_registerName = __sel_registerNamePtr
+      .asFunction<ffi.Pointer<ObjCSel> Function(ffi.Pointer<pkg_ffi.Char>)>();
+
+  ffi.Pointer<ObjCObject> _objc_getClass(
+    ffi.Pointer<pkg_ffi.Char> str,
+  ) {
+    return __objc_getClass(
+      str,
+    );
+  }
+
+  late final __objc_getClassPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(
+              ffi.Pointer<pkg_ffi.Char>)>>('objc_getClass');
+  late final __objc_getClass = __objc_getClassPtr.asFunction<
+      ffi.Pointer<ObjCObject> Function(ffi.Pointer<pkg_ffi.Char>)>();
+
+  instancetype _objc_msgSend_0(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_0(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_0Ptr = _lookup<
+      ffi.NativeFunction<
+          instancetype Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_0 = __objc_msgSend_0Ptr.asFunction<
+      instancetype Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  instancetype _objc_msgSend_1(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<_NSZone> zone,
+  ) {
+    return __objc_msgSend_1(
+      obj,
+      sel,
+      zone,
+    );
+  }
+
+  late final __objc_msgSend_1Ptr = _lookup<
+      ffi.NativeFunction<
+          instancetype Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<_NSZone>)>>('objc_msgSend');
+  late final __objc_msgSend_1 = __objc_msgSend_1Ptr.asFunction<
+      instancetype Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<_NSZone>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_2(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_2(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_2Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_2 = __objc_msgSend_2Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_3(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_3(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_3Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_3 = __objc_msgSend_3Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_4(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<_NSZone> zone,
+  ) {
+    return __objc_msgSend_4(
+      obj,
+      sel,
+      zone,
+    );
+  }
+
+  late final __objc_msgSend_4Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>, ffi.Pointer<_NSZone>)>>('objc_msgSend');
+  late final __objc_msgSend_4 = __objc_msgSend_4Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+          ffi.Pointer<ObjCSel>, ffi.Pointer<_NSZone>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_5(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<_NSZone> zone,
+  ) {
+    return __objc_msgSend_5(
+      obj,
+      sel,
+      zone,
+    );
+  }
+
+  late final __objc_msgSend_5Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>, ffi.Pointer<_NSZone>)>>('objc_msgSend');
+  late final __objc_msgSend_5 = __objc_msgSend_5Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+          ffi.Pointer<ObjCSel>, ffi.Pointer<_NSZone>)>();
+
+  int _objc_msgSend_6(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> aSelector,
+  ) {
+    return __objc_msgSend_6(
+      obj,
+      sel,
+      aSelector,
+    );
+  }
+
+  late final __objc_msgSend_6Ptr = _lookup<
+      ffi.NativeFunction<
+          BOOL Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_6 = __objc_msgSend_6Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCSel>)>();
+
+  int _objc_msgSend_7(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCObject> protocol,
+  ) {
+    return __objc_msgSend_7(
+      obj,
+      sel,
+      protocol,
+    );
+  }
+
+  late final __objc_msgSend_7Ptr = _lookup<
+      ffi.NativeFunction<
+          BOOL Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCObject>)>>('objc_msgSend');
+  late final __objc_msgSend_7 = __objc_msgSend_7Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCObject>)>();
+
+  IMP _objc_msgSend_8(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> aSelector,
+  ) {
+    return __objc_msgSend_8(
+      obj,
+      sel,
+      aSelector,
+    );
+  }
+
+  late final __objc_msgSend_8Ptr = _lookup<
+      ffi.NativeFunction<
+          IMP Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_8 = __objc_msgSend_8Ptr.asFunction<
+      IMP Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCSel>)>();
+
+  IMP _objc_msgSend_9(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> aSelector,
+  ) {
+    return __objc_msgSend_9(
+      obj,
+      sel,
+      aSelector,
+    );
+  }
+
+  late final __objc_msgSend_9Ptr = _lookup<
+      ffi.NativeFunction<
+          IMP Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_9 = __objc_msgSend_9Ptr.asFunction<
+      IMP Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_10(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> aSelector,
+  ) {
+    return __objc_msgSend_10(
+      obj,
+      sel,
+      aSelector,
+    );
+  }
+
+  late final __objc_msgSend_10Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_10 = __objc_msgSend_10Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+          ffi.Pointer<ObjCSel>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_11(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> aSelector,
+  ) {
+    return __objc_msgSend_11(
+      obj,
+      sel,
+      aSelector,
+    );
+  }
+
+  late final __objc_msgSend_11Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_11 = __objc_msgSend_11Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+          ffi.Pointer<ObjCSel>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_12(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> aSelector,
+  ) {
+    return __objc_msgSend_12(
+      obj,
+      sel,
+      aSelector,
+    );
+  }
+
+  late final __objc_msgSend_12Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_12 = __objc_msgSend_12Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+          ffi.Pointer<ObjCSel>, ffi.Pointer<ObjCSel>)>();
+
+  int _objc_msgSend_13(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_13(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_13Ptr = _lookup<
+      ffi.NativeFunction<
+          BOOL Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_13 = __objc_msgSend_13Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  int _objc_msgSend_14(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCObject> aClass,
+  ) {
+    return __objc_msgSend_14(
+      obj,
+      sel,
+      aClass,
+    );
+  }
+
+  late final __objc_msgSend_14Ptr = _lookup<
+      ffi.NativeFunction<
+          BOOL Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCObject>)>>('objc_msgSend');
+  late final __objc_msgSend_14 = __objc_msgSend_14Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCObject>)>();
+
+  int _objc_msgSend_15(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> sel1,
+  ) {
+    return __objc_msgSend_15(
+      obj,
+      sel,
+      sel1,
+    );
+  }
+
+  late final __objc_msgSend_15Ptr = _lookup<
+      ffi.NativeFunction<
+          BOOL Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_15 = __objc_msgSend_15Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCSel>)>();
+
+  int _objc_msgSend_16(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCSel> sel1,
+  ) {
+    return __objc_msgSend_16(
+      obj,
+      sel,
+      sel1,
+    );
+  }
+
+  late final __objc_msgSend_16Ptr = _lookup<
+      ffi.NativeFunction<
+          BOOL Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_16 = __objc_msgSend_16Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCSel>)>();
+
+  int _objc_msgSend_17(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_17(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_17Ptr = _lookup<
+      ffi.NativeFunction<
+          NSUInteger Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_17 = __objc_msgSend_17Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_18(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_18(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_18Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_18 = __objc_msgSend_18Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_19(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_19(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_19Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_19 = __objc_msgSend_19Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_20(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_20(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_20Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_20 = __objc_msgSend_20Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  int _objc_msgSend_21(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+  ) {
+    return __objc_msgSend_21(
+      obj,
+      sel,
+    );
+  }
+
+  late final __objc_msgSend_21Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Int32 Function(
+              ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>>('objc_msgSend');
+  late final __objc_msgSend_21 = __objc_msgSend_21Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>)>();
+
+  void _objc_msgSend_22(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    int value,
+  ) {
+    return __objc_msgSend_22(
+      obj,
+      sel,
+      value,
+    );
+  }
+
+  late final __objc_msgSend_22Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+              ffi.Int32)>>('objc_msgSend');
+  late final __objc_msgSend_22 = __objc_msgSend_22Ptr.asFunction<
+      void Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>, int)>();
+
+  ffi.Pointer<ObjCObject> _objc_msgSend_23(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    double someArg,
+  ) {
+    return __objc_msgSend_23(
+      obj,
+      sel,
+      someArg,
+    );
+  }
+
+  late final __objc_msgSend_23Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>, ffi.Double)>>('objc_msgSend');
+  late final __objc_msgSend_23 = __objc_msgSend_23Ptr.asFunction<
+      ffi.Pointer<ObjCObject> Function(
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>, double)>();
+
+  int _objc_msgSend_24(
+    ffi.Pointer<ObjCObject> obj,
+    ffi.Pointer<ObjCSel> sel,
+    ffi.Pointer<ObjCObject> someArg,
+    ffi.Pointer<ObjCObject> otherArg,
+  ) {
+    return __objc_msgSend_24(
+      obj,
+      sel,
+      someArg,
+      otherArg,
+    );
+  }
+
+  late final __objc_msgSend_24Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Int32 Function(
+              ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCSel>,
+              ffi.Pointer<ObjCObject>,
+              ffi.Pointer<ObjCObject>)>>('objc_msgSend');
+  late final __objc_msgSend_24 = __objc_msgSend_24Ptr.asFunction<
+      int Function(ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCSel>,
+          ffi.Pointer<ObjCObject>, ffi.Pointer<ObjCObject>)>();
+}
+
+ffi.Pointer<ObjCSel> _registerName(NativeLibrary _lib, String name) {
+  final cstr = name.toNativeUtf8();
+  final sel = _lib._sel_registerName(cstr.cast());
+  pkg_ffi.calloc.free(cstr);
+  return sel;
+}
+
+ffi.Pointer<ObjCObject> _getClass(NativeLibrary _lib, String name) {
+  final cstr = name.toNativeUtf8();
+  final clazz = _lib._objc_getClass(cstr.cast());
+  pkg_ffi.calloc.free(cstr);
+  return clazz;
+}
+
+class _ObjCWrapper {
+  final ffi.Pointer<ObjCObject> _id;
+  final NativeLibrary _lib;
+  _ObjCWrapper._(this._id, this._lib);
+}
+
+class Foo extends NSObject {
+  Foo._(ffi.Pointer<ObjCObject> id, NativeLibrary lib) : super._(id, lib);
+
+  static ffi.Pointer<ObjCObject>? _class;
+
+  static Foo castFrom<T extends _ObjCWrapper>(T other) {
+    return Foo._(other._id, other._lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_someProperty;
+  int get someProperty {
+    _sel_someProperty ??= _registerName(_lib, "someProperty");
+    return _lib._objc_msgSend_21(_id, _sel_someProperty!);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_someProperty1;
+  void set someProperty(int value) {
+    _sel_someProperty1 ??= _registerName(_lib, "setSomeProperty:");
+    return _lib._objc_msgSend_22(_id, _sel_someProperty1!, value);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_aClassMethod;
+  static Foo aClassMethod(NativeLibrary _lib, double someArg) {
+    _class ??= _getClass(_lib, "Foo");
+    _sel_aClassMethod ??= _registerName(_lib, "aClassMethod:");
+    final _ret = _lib._objc_msgSend_23(_class!, _sel_aClassMethod!, someArg);
+    return Foo._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_anInstanceMethod;
+  int anInstanceMethod(NSObject someArg, NSObject otherArg) {
+    _sel_anInstanceMethod ??=
+        _registerName(_lib, "anInstanceMethod:withOtherArg:");
+    return _lib._objc_msgSend_24(
+        _id, _sel_anInstanceMethod!, someArg._id, otherArg._id);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_new1;
+  static Foo new1(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "Foo");
+    _sel_new1 ??= _registerName(_lib, "new");
+    final _ret = _lib._objc_msgSend_0(_class!, _sel_new1!);
+    return Foo._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_alloc;
+  static Foo alloc(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "Foo");
+    _sel_alloc ??= _registerName(_lib, "alloc");
+    final _ret = _lib._objc_msgSend_0(_class!, _sel_alloc!);
+    return Foo._(_ret, _lib);
+  }
+}
+
+class NSObject extends _ObjCWrapper {
+  NSObject._(ffi.Pointer<ObjCObject> id, NativeLibrary lib) : super._(id, lib);
+
+  static ffi.Pointer<ObjCObject>? _class;
+
+  static NSObject castFrom<T extends _ObjCWrapper>(T other) {
+    return NSObject._(other._id, other._lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_init;
+  NSObject init() {
+    _sel_init ??= _registerName(_lib, "init");
+    final _ret = _lib._objc_msgSend_0(_id, _sel_init!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_new1;
+  static NSObject new1(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_new1 ??= _registerName(_lib, "new");
+    final _ret = _lib._objc_msgSend_0(_class!, _sel_new1!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_allocWithZone;
+  static NSObject allocWithZone(NativeLibrary _lib, ffi.Pointer<_NSZone> zone) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_allocWithZone ??= _registerName(_lib, "allocWithZone:");
+    final _ret = _lib._objc_msgSend_1(_class!, _sel_allocWithZone!, zone);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_alloc;
+  static NSObject alloc(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_alloc ??= _registerName(_lib, "alloc");
+    final _ret = _lib._objc_msgSend_0(_class!, _sel_alloc!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_copy;
+  NSObject copy() {
+    _sel_copy ??= _registerName(_lib, "copy");
+    final _ret = _lib._objc_msgSend_2(_id, _sel_copy!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_mutableCopy;
+  NSObject mutableCopy() {
+    _sel_mutableCopy ??= _registerName(_lib, "mutableCopy");
+    final _ret = _lib._objc_msgSend_3(_id, _sel_mutableCopy!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_copyWithZone;
+  static NSObject copyWithZone(NativeLibrary _lib, ffi.Pointer<_NSZone> zone) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_copyWithZone ??= _registerName(_lib, "copyWithZone:");
+    final _ret = _lib._objc_msgSend_4(_class!, _sel_copyWithZone!, zone);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_mutableCopyWithZone;
+  static NSObject mutableCopyWithZone(
+      NativeLibrary _lib, ffi.Pointer<_NSZone> zone) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_mutableCopyWithZone ??= _registerName(_lib, "mutableCopyWithZone:");
+    final _ret = _lib._objc_msgSend_5(_class!, _sel_mutableCopyWithZone!, zone);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_instancesRespondToSelector;
+  static int instancesRespondToSelector(
+      NativeLibrary _lib, ffi.Pointer<ObjCSel> aSelector) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_instancesRespondToSelector ??=
+        _registerName(_lib, "instancesRespondToSelector:");
+    return _lib._objc_msgSend_6(
+        _class!, _sel_instancesRespondToSelector!, aSelector);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_conformsToProtocol;
+  static int conformsToProtocol(NativeLibrary _lib, NSObject protocol) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_conformsToProtocol ??= _registerName(_lib, "conformsToProtocol:");
+    return _lib._objc_msgSend_7(
+        _class!, _sel_conformsToProtocol!, protocol._id);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_methodForSelector;
+  IMP methodForSelector(ffi.Pointer<ObjCSel> aSelector) {
+    _sel_methodForSelector ??= _registerName(_lib, "methodForSelector:");
+    return _lib._objc_msgSend_8(_id, _sel_methodForSelector!, aSelector);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_instanceMethodForSelector;
+  static IMP instanceMethodForSelector(
+      NativeLibrary _lib, ffi.Pointer<ObjCSel> aSelector) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_instanceMethodForSelector ??=
+        _registerName(_lib, "instanceMethodForSelector:");
+    return _lib._objc_msgSend_9(
+        _class!, _sel_instanceMethodForSelector!, aSelector);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_forwardingTargetForSelector;
+  NSObject forwardingTargetForSelector(ffi.Pointer<ObjCSel> aSelector) {
+    _sel_forwardingTargetForSelector ??=
+        _registerName(_lib, "forwardingTargetForSelector:");
+    final _ret = _lib._objc_msgSend_10(
+        _id, _sel_forwardingTargetForSelector!, aSelector);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_methodSignatureForSelector;
+  NSMethodSignature methodSignatureForSelector(ffi.Pointer<ObjCSel> aSelector) {
+    _sel_methodSignatureForSelector ??=
+        _registerName(_lib, "methodSignatureForSelector:");
+    final _ret =
+        _lib._objc_msgSend_11(_id, _sel_methodSignatureForSelector!, aSelector);
+    return NSMethodSignature._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_instanceMethodSignatureForSelector;
+  static NSMethodSignature instanceMethodSignatureForSelector(
+      NativeLibrary _lib, ffi.Pointer<ObjCSel> aSelector) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_instanceMethodSignatureForSelector ??=
+        _registerName(_lib, "instanceMethodSignatureForSelector:");
+    final _ret = _lib._objc_msgSend_12(
+        _class!, _sel_instanceMethodSignatureForSelector!, aSelector);
+    return NSMethodSignature._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_allowsWeakReference;
+  int allowsWeakReference() {
+    _sel_allowsWeakReference ??= _registerName(_lib, "allowsWeakReference");
+    return _lib._objc_msgSend_13(_id, _sel_allowsWeakReference!);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_retainWeakReference;
+  int retainWeakReference() {
+    _sel_retainWeakReference ??= _registerName(_lib, "retainWeakReference");
+    return _lib._objc_msgSend_13(_id, _sel_retainWeakReference!);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_isSubclassOfClass;
+  static int isSubclassOfClass(NativeLibrary _lib, NSObject aClass) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_isSubclassOfClass ??= _registerName(_lib, "isSubclassOfClass:");
+    return _lib._objc_msgSend_14(_class!, _sel_isSubclassOfClass!, aClass._id);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_resolveClassMethod;
+  static int resolveClassMethod(NativeLibrary _lib, ffi.Pointer<ObjCSel> sel) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_resolveClassMethod ??= _registerName(_lib, "resolveClassMethod:");
+    return _lib._objc_msgSend_15(_class!, _sel_resolveClassMethod!, sel);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_resolveInstanceMethod;
+  static int resolveInstanceMethod(
+      NativeLibrary _lib, ffi.Pointer<ObjCSel> sel) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_resolveInstanceMethod ??=
+        _registerName(_lib, "resolveInstanceMethod:");
+    return _lib._objc_msgSend_16(_class!, _sel_resolveInstanceMethod!, sel);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_hash;
+  static int hash(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_hash ??= _registerName(_lib, "hash");
+    return _lib._objc_msgSend_17(_class!, _sel_hash!);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_superclass;
+  static NSObject superclass(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_superclass ??= _registerName(_lib, "superclass");
+    final _ret = _lib._objc_msgSend_18(_class!, _sel_superclass!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_class1;
+  static NSObject class1(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_class1 ??= _registerName(_lib, "class");
+    final _ret = _lib._objc_msgSend_19(_class!, _sel_class1!);
+    return NSObject._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_description;
+  static NSString description(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_description ??= _registerName(_lib, "description");
+    final _ret = _lib._objc_msgSend_20(_class!, _sel_description!);
+    return NSString._(_ret, _lib);
+  }
+
+  static ffi.Pointer<ObjCSel>? _sel_debugDescription;
+  static NSString debugDescription(NativeLibrary _lib) {
+    _class ??= _getClass(_lib, "NSObject");
+    _sel_debugDescription ??= _registerName(_lib, "debugDescription");
+    final _ret = _lib._objc_msgSend_20(_class!, _sel_debugDescription!);
+    return NSString._(_ret, _lib);
+  }
+}
+
+typedef instancetype = ffi.Pointer<ObjCObject>;
+
+class ObjCObject extends ffi.Opaque {}
+
+class _NSZone extends ffi.Opaque {}
+
+typedef BOOL = pkg_ffi.SignedChar;
+
+class ObjCSel extends ffi.Opaque {}
+
+typedef IMP = ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>;
+
+class NSMethodSignature extends _ObjCWrapper {
+  NSMethodSignature._(ffi.Pointer<ObjCObject> id, NativeLibrary lib)
+      : super._(id, lib);
+
+  static ffi.Pointer<ObjCObject>? _class;
+
+  static NSMethodSignature castFrom<T extends _ObjCWrapper>(T other) {
+    return NSMethodSignature._(other._id, other._lib);
+  }
+}
+
+typedef NSUInteger = pkg_ffi.UnsignedLong;
+
+class NSString extends _ObjCWrapper {
+  NSString._(ffi.Pointer<ObjCObject> id, NativeLibrary lib) : super._(id, lib);
+
+  static ffi.Pointer<ObjCObject>? _class;
+
+  static NSString castFrom<T extends _ObjCWrapper>(T other) {
+    return NSString._(other._id, other._lib);
+  }
+}
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h b/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h
index a2f7fc9..c8d4f31 100644
--- a/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h
+++ b/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h
@@ -3,6 +3,6 @@
   id anId;
   SEL selector;
   NSObject* object;
-  Class* clazz;
+  Class clazz;
   int32_t (^blockThatReturnsAnInt)(void);
 };
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart b/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart
index 8ee93f4..252683e 100644
--- a/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart
+++ b/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart
@@ -23,7 +23,7 @@
       actual = parser.parse(
         Config.fromYaml(yaml.loadYaml('''
 ${strings.name}: 'NativeLibrary'
-${strings.description}: 'Opaque Dependencies Test'
+${strings.description}: 'ObjC Basic Types Test'
 ${strings.output}: 'unused'
 ${strings.language}: '${strings.langObjC}'
 ${strings.headers}:
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_interface.h b/pkgs/ffigen/test/header_parser_tests/objc_interface.h
new file mode 100644
index 0000000..ee299bf
--- /dev/null
+++ b/pkgs/ffigen/test/header_parser_tests/objc_interface.h
@@ -0,0 +1,16 @@
+// This is the Foo interface.
+@interface Foo : NSObject {
+  // This is an instance variable. They are private, so are ignored.
+  double instVar;
+}
+
+// This is a property. We generate getters and setters for them.
+@property int32_t someProperty;
+
+// This is a class method, so becomes a static function.
++ (Foo*)aClassMethod:(double)someArg;
+
+// This is an instance method, so becomes a regular method.
+- (int32_t)anInstanceMethod:(NSString*)someArg withOtherArg:(Foo*)otherArg;
+
+@end
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_interface_test.dart b/pkgs/ffigen/test/header_parser_tests/objc_interface_test.dart
new file mode 100644
index 0000000..b614f3d
--- /dev/null
+++ b/pkgs/ffigen/test/header_parser_tests/objc_interface_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/config_provider.dart';
+import 'package:ffigen/src/header_parser.dart' as parser;
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+import '../test_utils.dart';
+
+late Library actual;
+void main() {
+  group('objc_interface_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+      actual = parser.parse(
+        Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'ObjC Interface Test'
+${strings.output}: 'unused'
+${strings.language}: '${strings.langObjC}'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/objc_interface.h'
+''') as yaml.YamlMap),
+      );
+    });
+    test('Expected bindings', () {
+      matchLibraryWithExpected(actual, [
+        'test',
+        'debug_generated',
+        'header_parser_objc_interface_test_output.dart'
+      ], [
+        'test',
+        'header_parser_tests',
+        'expected_bindings',
+        '_expected_objc_interface_bindings.dart'
+      ]);
+    });
+  });
+}
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml
index c6940ac..37d96f9 100644
--- a/pkgs/ffigen/tool/libclang_config.yaml
+++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -109,3 +109,6 @@
     - clang_getCursorDefinition
     - clang_Cursor_isNull
     - clang_Cursor_hasAttrs
+    - clang_Type_getObjCObjectBaseType
+    - clang_Cursor_getObjCPropertyGetterName
+    - clang_Cursor_getObjCPropertySetterName