Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/objective_c.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e
with:
channel: 'stable'
channel: 'beta'
- name: Install dependencies
run: flutter pub get
- name: Build test dylib
Expand Down
21 changes: 7 additions & 14 deletions pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,24 +98,17 @@ class ObjCInterface extends BindingType with ObjCMethods, HasLocalScope {

final rawObjType = PointerType(objCObjectType).getCType(context);
final wrapObjType = ObjCBuiltInFunctions.objectBase.gen(context);
final protos = protocols.map((p) => p.getDartType(context)).join(', ');
final protoImpl = protocols.isEmpty ? '' : 'implements $protos ';
final protos = [
wrapObjType,
...[superType, ...protocols].nonNulls.map((p) => p.getDartType(context)),
];

final superTypeDartType = superType?.getDartType(context) ?? wrapObjType;
final superCtor = superType == null ? 'super' : 'super.castFromPointer';
s.write('''
class $name extends $superTypeDartType $protoImpl{
$name._($rawObjType pointer, {bool retain = false, bool release = false}) :
$superCtor(pointer, retain: retain, release: release)$ctorBody

/// Constructs a [$name] that points to the same underlying object as [other].
$name.castFrom($wrapObjType other) :
this._(other.ref.pointer, retain: true, release: true);

extension type $name.castFrom($wrapObjType _\$) implements ${protos.join(',')} {
/// Constructs a [$name] that wraps the given raw object pointer.
$name.castFromPointer($rawObjType other,
{bool retain = false, bool release = false}) :
this._(other, retain: retain, release: release);
_\$ = $wrapObjType(other, retain: retain, release: release)$ctorBody

${generateAsStub ? '' : _generateStaticMethods(w)}
}
Expand Down Expand Up @@ -163,7 +156,7 @@ ${generateInstanceMethodBindings(w, this)}
if (newMethod != null && originalName != 'NSString') {
s.write('''
/// Returns a new instance of $name constructed with the default `new` method.
factory $name() => ${newMethod.name}();
$name() : this.castFrom(${newMethod.name}()._\$);
''');
}

Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/lib/src/code_generator/objc_methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ class ObjCMethod extends AstNode with HasLocalScope {
} else {
targetStr = target.convertDartTypeToFfiDartType(
context,
'this',
'_\$',
objCRetain: consumesSelf,
objCAutorelease: false,
);
Expand Down
17 changes: 6 additions & 11 deletions pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,16 @@ class ObjCProtocol extends BindingType with ObjCMethods, HasLocalScope {
}
s.write(makeDartDoc(dartDoc ?? originalName));

final sp = superProtocols.map((p) => p.getDartType(context));
final impls = superProtocols.isEmpty ? '' : 'implements ${sp.join(', ')}';
final sp = [
protocolBase,
...superProtocols.map((p) => p.getDartType(context)),
];
s.write('''
interface class $name extends $protocolBase $impls{
$name._($rawObjType pointer, {bool retain = false, bool release = false}) :
super(pointer, retain: retain, release: release);

/// Constructs a [$name] that points to the same underlying object as [other].
$name.castFrom($objectBase other) :
this._(other.ref.pointer, retain: true, release: true);

extension type $name.castFrom($protocolBase _\$) implements ${sp.join(', ')} {
/// Constructs a [$name] that wraps the given raw object pointer.
$name.castFromPointer($rawObjType other,
{bool retain = false, bool release = false}) :
this._(other, retain: retain, release: release);
this.castFrom($protocolBase(other, retain: retain, release: release));
''');

if (!generateAsStub) {
Expand Down
4 changes: 2 additions & 2 deletions pkgs/ffigen/test/native_objc_test/method_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ void main() {
'objc_test.dylib',
),
);
verifySetupFile(dylib);
// verifySetupFile(dylib);
DynamicLibrary.open(dylib.absolute.path);
testInstance = MethodInterface();
generateBindingsForCoverage('method');
// generateBindingsForCoverage('method');
});

group('Instance methods', () {
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/test/native_objc_test/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ int objectRetainCount(Pointer<ObjCObject> object) {
// isValidObject broke due to a runtime update.
// These constants are the ISA_MASK macro defined in runtime/objc-private.h.
const maskX64 = 0x00007ffffffffff8;
const maskArm = 0x00000001fffffff8;
const maskArm = 0x0000000ffffffff8;
final mask = Abi.current() == Abi.macosX64 ? maskX64 : maskArm;
final clazz = Pointer<ObjCObject>.fromAddress(header & mask);

Expand Down
1 change: 0 additions & 1 deletion pkgs/objective_c/ffigen_objc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,3 @@ preamble: |
// ignore_for_file: unused_element
// ignore_for_file: unused_field
// coverage:ignore-file
import 'dart:collection';
4 changes: 4 additions & 0 deletions pkgs/objective_c/lib/objective_c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ export 'src/cf_string.dart';
export 'src/converter.dart';
export 'src/internal.dart'
hide blockHasRegisteredClosure, isValidBlock, isValidClass, isValidObject;
export 'src/ns_array.dart';
export 'src/ns_data.dart';
export 'src/ns_date.dart';
export 'src/ns_dictionary.dart';
export 'src/ns_enumerator.dart';
export 'src/ns_input_stream.dart';
export 'src/ns_mutable_data.dart';
export 'src/ns_number.dart';
export 'src/ns_set.dart';
export 'src/ns_string.dart';
export 'src/objective_c_bindings_exported.dart';
export 'src/observer.dart';
Expand Down
26 changes: 23 additions & 3 deletions pkgs/objective_c/lib/src/converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.

import 'internal.dart';
import 'ns_array.dart';
import 'ns_date.dart';
import 'ns_dictionary.dart';
import 'ns_number.dart';
import 'ns_set.dart';
import 'ns_string.dart';
import 'objective_c_bindings_generated.dart';

Expand Down Expand Up @@ -115,22 +118,39 @@ Object? toNullableDartObject(
}

extension NSArrayToDartList on NSArray {
/// Deep converts this [NSArray] to a Dart [List].
///
/// This creates a new [List], converts all the [NSArray] elements, and adds
/// them to the [List]. If you only need iteration and element access,
/// [toDart] is much more efficient.
List<Object> toDartList({
Object Function(ObjCObjectBase) convertOther = _defaultDartConverter,
}) => map((o) => toDartObject(o, convertOther: convertOther)).toList();
}) =>
toDart().map((o) => toDartObject(o, convertOther: convertOther)).toList();
}

extension NSSetToDartSet on NSSet {
/// Deep converts this [NSSet] to a Dart [Set].
///
/// This creates a new [Set], converts all the [NSSet] elements, and adds
/// them to the [Set]. If you only need iteration and element access,
/// [toDart] is much more efficient.
Set<Object> toDartSet({
Object Function(ObjCObjectBase) convertOther = _defaultDartConverter,
}) => map((o) => toDartObject(o, convertOther: convertOther)).toSet();
}) =>
toDart().map((o) => toDartObject(o, convertOther: convertOther)).toSet();
}

extension NSDictionaryToDartMap on NSDictionary {
/// Deep converts this [NSDictionary] to a Dart [Map].
///
/// This creates a new [Map], converts all the [NSDictionary] elements, and
/// adds them to the [Map]. If you only need iteration and element access,
/// [toDart] is much more efficient.
Map<Object, Object> toDartMap({
Object Function(ObjCObjectBase) convertOther = _defaultDartConverter,
}) => Map.fromEntries(
entries.map(
toDart().entries.map(
(kv) => MapEntry(
toDartObject(kv.key, convertOther: convertOther),
toDartObject(kv.value, convertOther: convertOther),
Expand Down
6 changes: 3 additions & 3 deletions pkgs/objective_c/lib/src/internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,9 @@ bool _isValidClass(ObjectPtr clazz, {bool forceReloadClasses = false}) {
}

/// Only for use by FFIgen bindings.
class ObjCProtocolBase extends ObjCObjectBase {
ObjCProtocolBase(super.ptr, {required super.retain, required super.release});
}
// This exists so that interface_lists_test.dart can tell the difference between
// a protocol and an interface.
typedef ObjCProtocolBase = ObjCObjectBase;

@pragma('vm:deeply-immutable')
final class ObjCBlockRef extends _ObjCReference<c.ObjCBlockImpl> {
Expand Down
119 changes: 119 additions & 0 deletions pkgs/objective_c/lib/src/ns_array.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) 2025, 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:collection';

import 'converter.dart';
import 'internal.dart';
import 'objective_c_bindings_generated.dart';

class _NSArrayAdapter with ListBase<ObjCObjectBase> {
final NSArray _array;

_NSArrayAdapter(this._array);

@override
int get length => _array.count;

@override
ObjCObjectBase elementAt(int index) => _array.objectAtIndex(index);

@override
Iterator<ObjCObjectBase> get iterator => _NSArrayIterator(this);

@override
ObjCObjectBase operator [](int index) => _array.objectAtIndex(index);

@override
set length(int newLength) => throw UnsupportedError('Cannot modify NSArray');

@override
void operator []=(int index, ObjCObjectBase value) =>
throw UnsupportedError('Cannot modify NSArray');

@override
void add(ObjCObjectBase value) =>
throw UnsupportedError('Cannot modify NSArray');
}

extension NSArrayToAdapter on NSArray {
/// Wraps this [NSArray] in an adapter that implements an immutable [List].
///
/// This is not a conversion, doesn't create a new list, or change the
/// elements. For deep conversion, use [toDartList].
List<ObjCObjectBase> toDart() => _NSArrayAdapter(this);
}

class _NSMutableArrayAdapter with ListBase<ObjCObjectBase> {
final NSMutableArray _array;

_NSMutableArrayAdapter(this._array);

@override
int get length => _array.count;

@override
set length(int newLength) {
var len = length;
RangeError.checkValueInInterval(newLength, 0, len);
for (; len > newLength; --len) {
_array.removeLastObject();
}
}

@override
ObjCObjectBase elementAt(int index) => _array.objectAtIndex(index);

@override
Iterator<ObjCObjectBase> get iterator => _NSArrayIterator(this);

@override
ObjCObjectBase operator [](int index) => _array.objectAtIndex(index);

@override
void operator []=(int index, ObjCObjectBase value) =>
_array.replaceObjectAtIndex(index, withObject: value);

@override
void add(ObjCObjectBase value) => _array.addObject(value);
}

extension NSMutableArrayToAdapter on NSMutableArray {
/// Wraps this [NSMutableArray] in an adapter that implements [List].
///
/// This is not a conversion, doesn't create a new list, or change the
/// elements. For deep conversion, use [toDartList].
List<ObjCObjectBase> toDart() => _NSMutableArrayAdapter(this);
}

class _NSArrayIterator implements Iterator<ObjCObjectBase> {
final Iterable<ObjCObjectBase> _iterable;
final int _length;
int _index;
ObjCObjectBase? _current;

_NSArrayIterator(Iterable<ObjCObjectBase> iterable)
: _iterable = iterable,
_length = iterable.length,
_index = 0;

@override
ObjCObjectBase get current => _current!;

@override
@pragma('vm:prefer-inline')
bool moveNext() {
final length = _iterable.length;
if (_length != length) {
throw ConcurrentModificationError(_iterable);
}
if (_index >= length) {
_current = null;
return false;
}
_current = _iterable.elementAt(_index);
_index++;
return true;
}
}
Loading
Loading