Skip to content

Commit

Permalink
[ffigen] Add selector conversion utils (#1616)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe authored Oct 1, 2024
1 parent 8490510 commit 4bc8c16
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class ObjCBuiltInFunctions {
generateForPackageObjectiveC ? null : builtInCompounds[name];
bool isBuiltInEnum(String name) =>
!generateForPackageObjectiveC && builtInEnums.contains(name);
bool isNSObject(String name) => name == 'NSObject';
static bool isNSObject(String name) => name == 'NSObject';

// We need to load a separate instance of objc_msgSend for each signature. If
// the return type is a struct, we need to use objc_msgSend_stret instead, and
Expand Down
16 changes: 14 additions & 2 deletions pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,11 @@ class ObjCInterface extends BindingType with ObjCMethods {
// - Methods that return instancetype, because the subclass's copy of the
// method needs to return the subclass, not the super class.
// Note: instancetype is only allowed as a return type, not an arg type.
final isNSObject = ObjCBuiltInFunctions.isNSObject(originalName);
for (final m in superType!.methods) {
if (m.isClassMethod &&
if (isNSObject) {
addMethod(m);
} else if (m.isClassMethod &&
!_excludedNSObjectMethods.contains(m.originalName)) {
addMethod(m);
} else if (ObjCBuiltInFunctions.isInstanceType(m.returnType)) {
Expand All @@ -290,8 +293,17 @@ class ObjCInterface extends BindingType with ObjCMethods {
}

void _copyMethodsFromProtocol(ObjCProtocol proto) {
final isNSObject = ObjCBuiltInFunctions.isNSObject(originalName);
for (final m in proto.methods) {
if (!_excludedNSObjectMethods.contains(m.originalName)) {
if (isNSObject) {
if (m.originalName == 'description' || m.originalName == 'hash') {
// TODO(https://github.com/dart-lang/native/issues/1220): Remove this
// special case. These methods only clash because they're sometimes
// declared as getters and sometimes as normal methods.
} else {
addMethod(m);
}
} else if (!_excludedNSObjectMethods.contains(m.originalName)) {
addMethod(m);
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ ${makeDartDoc(dartDoc ?? originalName)}abstract final class $name {
}

void _copyMethodsFromSuperType(ObjCProtocol superProtocol) {
if (builtInFunctions.isNSObject(superProtocol.originalName)) {
if (ObjCBuiltInFunctions.isNSObject(superProtocol.originalName)) {
// When writing a protocol that doesn't inherit from any other protocols,
// it's typical to have it inherit from NSObject instead. But NSObject has
// heaps of methods that users are very unlikely to want to implement, so
Expand Down
3 changes: 3 additions & 0 deletions pkgs/ffigen/test/large_integration_tests/large_objc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ void main() {
'attributedString',
'cachePolicy',
'candidateListTouchBarItem',
'delegate',
'hyphenationFactor',
'image',
'isProxy',
'objCType',
'tag',
'title',
};
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/test/native_objc_test/block_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ void main() {
final dummyObject = DummyObject.new1();
DartObjectListenerBlock? block =
ObjectListenerBlock.listener((DummyObject obj) {
expect(objectRetainCount(obj.ref.pointer), 1);
expect(objectRetainCount(obj.ref.pointer), greaterThan(0));
completer.complete();
expect(dummyObject, isNotNull);
});
Expand Down
1 change: 1 addition & 0 deletions pkgs/objective_c/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
bindings if an optional method is invoked, and the instance doesn't implement
the method.
- Dispatch all object/block releases to the main thread.
- Add utils for converting Dart `String`s to Objective-C selectors and back.
- Require Dart 3.4 or later (due to the use of `dart:ffi`
`Struct.create` by `package:ffigen`).
- __Breaking change__: Return structs from ObjC methods by value instead of
Expand Down
2 changes: 2 additions & 0 deletions pkgs/objective_c/ffigen_c.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ functions:
- 'objc_.*'
- 'object_getClass'
- 'sel_registerName'
- 'sel_getName'
- 'protocol_getMethodDescription'
- 'disposeObjCBlockWithClosure'
- 'isValidBlock'
Expand All @@ -37,6 +38,7 @@ functions:
- 'newFinalizableBool'
rename:
'sel_registerName': 'registerName'
'sel_getName': 'getName'
'objc_getClass': 'getClass'
'objc_retain': 'objectRetain'
'objc_retainBlock': 'blockRetain'
Expand Down
1 change: 1 addition & 0 deletions pkgs/objective_c/lib/objective_c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ export 'src/objective_c_bindings_generated.dart'
NSValue,
Protocol;
export 'src/protocol_builder.dart';
export 'src/selector.dart';
6 changes: 6 additions & 0 deletions pkgs/objective_c/lib/src/c_bindings_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ external ObjCMethodDesc getMethodDescription(
bool isInstanceMethod,
);

@ffi.Native<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ObjCSelector>)>(
symbol: "sel_getName", isLeaf: true)
external ffi.Pointer<ffi.Char> getName(
ffi.Pointer<ObjCSelector> sel,
);

@ffi.Native<ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>)>(
symbol: "object_getClass", isLeaf: true)
external ffi.Pointer<ObjCObject> getObjectClass(
Expand Down
26 changes: 18 additions & 8 deletions pkgs/objective_c/lib/src/objective_c_bindings_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6338,8 +6338,8 @@ class NSObject extends objc.ObjCObjectBase {
}

/// class
static objc.ObjCObjectBase class1() {
final _ret = _objc_msgSend_1unuoxw(_class_NSObject, _sel_class);
objc.ObjCObjectBase class1() {
final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_class);
return objc.ObjCObjectBase(_ret, retain: true, release: true);
}

Expand All @@ -6350,9 +6350,9 @@ class NSObject extends objc.ObjCObjectBase {
}

/// conformsToProtocol:
static bool conformsToProtocol_(Protocol protocol) {
bool conformsToProtocol_(Protocol aProtocol) {
return _objc_msgSend_l8lotg(
_class_NSObject, _sel_conformsToProtocol_, protocol.ref.pointer);
this.ref.pointer, _sel_conformsToProtocol_, aProtocol.ref.pointer);
}

/// copy
Expand All @@ -6374,8 +6374,12 @@ class NSObject extends objc.ObjCObjectBase {
}

/// debugDescription
static NSString debugDescription() {
final _ret = _objc_msgSend_1unuoxw(_class_NSObject, _sel_debugDescription);
NSString debugDescription() {
if (!objc.respondsToSelector(ref.pointer, _sel_debugDescription)) {
throw objc.UnimplementedOptionalMethodException(
'NSObject', 'debugDescription');
}
final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_debugDescription);
return NSString.castFromPointer(_ret, retain: true, release: true);
}

Expand Down Expand Up @@ -6639,6 +6643,12 @@ class NSObject extends objc.ObjCObjectBase {
_class_NSObject, _sel_resolveInstanceMethod_, sel);
}

/// respondsToSelector:
bool respondsToSelector_(ffi.Pointer<objc.ObjCSelector> aSelector) {
return _objc_msgSend_8d7dvc(
this.ref.pointer, _sel_respondsToSelector_, aSelector);
}

/// retain
NSObject retain() {
final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_retain);
Expand Down Expand Up @@ -6668,8 +6678,8 @@ class NSObject extends objc.ObjCObjectBase {
}

/// superclass
static objc.ObjCObjectBase superclass() {
final _ret = _objc_msgSend_1unuoxw(_class_NSObject, _sel_superclass);
objc.ObjCObjectBase superclass() {
final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_superclass);
return objc.ObjCObjectBase(_ret, retain: true, release: true);
}

Expand Down
25 changes: 25 additions & 0 deletions pkgs/objective_c/lib/src/selector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2024, 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:ffi/ffi.dart';

import 'c_bindings_generated.dart' as c;
import 'internal.dart';

extension StringToSelector on String {
/// Returns an Objective-C selector (aka `SEL`) for this [String].
///
/// This is equivalent to the Objective-C `@selector()` directive, or the
/// `NSSelectorFromString` function.
Pointer<c.ObjCSelector> toSelector() => registerName(this);
}

extension SelectorToString on Pointer<c.ObjCSelector> {
/// Returns the string that this Objective-C selector represents.
///
/// This is equivalent to the Objective-C `NSSelectorFromString` function.
String toDartString() => c.getName(this).cast<Utf8>().toDartString();
}
2 changes: 1 addition & 1 deletion pkgs/objective_c/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ topics:

environment:
sdk: '>=3.4.0 <4.0.0'
flutter: '>=3.3.0'
flutter: '>=3.22.0'

dependencies:
ffi: ^2.1.0
Expand Down
1 change: 1 addition & 0 deletions pkgs/objective_c/src/objective_c_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ typedef struct _ObjCObject ObjCObject;
typedef struct _ObjCProtocol ObjCProtocol;

ObjCSelector *sel_registerName(const char *name);
const char * sel_getName(ObjCSelector* sel);
ObjCObject *objc_getClass(const char *name);
ObjCObject *objc_retain(ObjCObject *object);
ObjCObject *objc_retainBlock(const ObjCObject *object);
Expand Down
37 changes: 37 additions & 0 deletions pkgs/objective_c/test/selector_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2024, 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')
library;

import 'dart:ffi';

import 'package:objective_c/objective_c.dart';
import 'package:test/test.dart';

void main() {
group('Selector', () {
setUpAll(() {
// TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
DynamicLibrary.open('test/objective_c.dylib');
});

test('from String and back', () {
expect('hello'.toSelector().toDartString(), 'hello');
expect(''.toSelector().toDartString(), '');
expect('foo:with:args:'.toSelector().toDartString(), 'foo:with:args:');
});

test('responds to selector', () {
final sel1 = 'addObserver:forKeyPath:options:context:'.toSelector();
expect(NSObject.new1().respondsToSelector_(sel1), isTrue);
expect(NSObject.new1().respondsToSelector_('foo'.toSelector()), isFalse);

final sel2 = 'canBeConvertedToEncoding:'.toSelector();
expect(NSString.new1().respondsToSelector_(sel2), isTrue);
expect(NSString.new1().respondsToSelector_('bar'.toSelector()), isFalse);
});
});
}

0 comments on commit 4bc8c16

Please sign in to comment.