[ffigen] Add a static isInstance method to ObjC classes (#383)
* Add an isInstance method for checking the type of an object.
* Tighten up the test
* Simplify a bit
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index 95143f2..4c77067 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -44,6 +44,8 @@
final ObjCBuiltInFunctions builtInFunctions;
final bool isBuiltIn;
late final ObjCInternalGlobal _classObject;
+ late final ObjCInternalGlobal _isKindOfClass;
+ late final Func _isKindOfClassMsgSend;
ObjCInterface({
String? usr,
@@ -95,14 +97,23 @@
{bool retain = false, bool release = false}) :
super._(id, lib, retain: retain, release: release);
+ /// Returns a [$name] that points to the same underlying object as [other].
static $name castFrom<T extends _ObjCWrapper>(T other) {
return $name._(other._id, other._lib, retain: true, release: true);
}
+ /// Returns a [$name] that wraps the given raw object pointer.
static $name castFromPointer($natLib lib, ffi.Pointer<ObjCObject> other) {
return $name._(other, lib, retain: true, release: true);
}
+ /// Returns whether [obj] is an instance of [$name].
+ static bool isInstance(_ObjCWrapper obj) {
+ return obj._lib.${_isKindOfClassMsgSend.name}(
+ obj._id, obj._lib.${_isKindOfClass.name},
+ obj._lib.${_classObject.name});
+ }
+
''');
if (isNSString) {
@@ -219,6 +230,9 @@
(Writer w) => '${builtInFunctions.getClass.name}("$originalName")',
builtInFunctions.getClass)
..addDependencies(dependencies);
+ _isKindOfClass = builtInFunctions.getSelObject('isKindOfClass:');
+ _isKindOfClassMsgSend = builtInFunctions.getMsgSendFunc(
+ BooleanType(), [ObjCMethodParam(PointerType(objCObjectType), 'clazz')]);
if (isNSString) {
_addNSStringMethods();
diff --git a/pkgs/ffigen/test/native_objc_test/is_instance_config.yaml b/pkgs/ffigen/test/native_objc_test/is_instance_config.yaml
new file mode 100644
index 0000000..90ca44b
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/is_instance_config.yaml
@@ -0,0 +1,12 @@
+name: IsInstanceTestObjCLibrary
+description: 'Tests isInstance'
+language: objc
+output: 'test/native_objc_test/is_instance_bindings.dart'
+functions:
+ exclude:
+ - '.*'
+headers:
+ entry-points:
+ - 'test/native_objc_test/is_instance_test.m'
+preamble: |
+ // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/pkgs/ffigen/test/native_objc_test/is_instance_test.dart b/pkgs/ffigen/test/native_objc_test/is_instance_test.dart
new file mode 100644
index 0000000..bbbfee9
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/is_instance_test.dart
@@ -0,0 +1,47 @@
+// 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 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'is_instance_bindings.dart';
+import 'util.dart';
+
+void main() {
+ late IsInstanceTestObjCLibrary lib;
+
+ group('isInstance', () {
+ setUpAll(() {
+ logWarnings();
+ final dylib = File('test/native_objc_test/is_instance_test.dylib');
+ verifySetupFile(dylib);
+ lib = IsInstanceTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
+ generateBindingsForCoverage('is_instance');
+ });
+
+ test('Unrelated classes', () {
+ final base = NSObject.castFrom(BaseClass.new1(lib));
+ final unrelated = NSObject.castFrom(UnrelatedClass.new1(lib));
+ expect(BaseClass.isInstance(base), isTrue);
+ expect(BaseClass.isInstance(unrelated), isFalse);
+ expect(UnrelatedClass.isInstance(base), isFalse);
+ expect(UnrelatedClass.isInstance(unrelated), isTrue);
+ });
+
+ test('Base class vs child class', () {
+ final base = NSObject.castFrom(BaseClass.new1(lib));
+ final child = NSObject.castFrom(ChildClass.new1(lib));
+ expect(BaseClass.isInstance(base), isTrue);
+ expect(BaseClass.isInstance(child), isTrue);
+ expect(ChildClass.isInstance(base), isFalse);
+ expect(ChildClass.isInstance(child), isTrue);
+ });
+ });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/is_instance_test.m b/pkgs/ffigen/test/native_objc_test/is_instance_test.m
new file mode 100644
index 0000000..96b2314
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/is_instance_test.m
@@ -0,0 +1,23 @@
+// 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 <Foundation/NSObject.h>
+
+@interface BaseClass : NSObject {}
+@end
+
+@interface ChildClass : BaseClass {}
+@end
+
+@interface UnrelatedClass : NSObject {}
+@end
+
+@implementation BaseClass
+@end
+
+@implementation ChildClass
+@end
+
+@implementation UnrelatedClass
+@end
diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart
index 3e9b5e0..22d99c9 100644
--- a/pkgs/ffigen/test/native_objc_test/setup.dart
+++ b/pkgs/ffigen/test/native_objc_test/setup.dart
@@ -51,6 +51,7 @@
'cast',
'category',
'forward_decl',
+ 'is_instance',
'method',
'native_objc',
'nullable',