diff --git a/.github/workflows/objective_c.yaml b/.github/workflows/objective_c.yaml index ee5b93bf7c..6ddeb731c6 100644 --- a/.github/workflows/objective_c.yaml +++ b/.github/workflows/objective_c.yaml @@ -75,3 +75,20 @@ jobs: carryforward: "ffigen,jni,jnigen,native_assets_builder_macos,native_assets_builder_ubuntu,native_assets_builder_windows,native_assets_cli_macos,native_assets_cli_ubuntu,native_assets_cli_windows,native_toolchain_c_macos,native_toolchain_c_ubuntu,native_toolchain_c_windows,objective_c,swift2objc" github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true + + build-example: + needs: analyze + runs-on: 'macos-latest' + defaults: + run: + working-directory: pkgs/objective_c/example/ + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 + with: + flutter-version: 3.19.0 + channel: 'stable' + - name: Install dependencies + run: flutter pub get + - name: Build the example app + run: flutter build macos diff --git a/.gitignore b/.gitignore index 9943ed042a..c870722884 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,7 @@ coverage/ *.jar *.class *.exe +*.o # Visual Studio user-specific files. *.suo diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index a8e6da982d..8e96bbf37e 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -13,7 +13,11 @@ `ObjCBlock`, instead of the codegenned `ObjCBlock_...` wrapper. The wrapper is now a non-constructible set of util methods for constructing `ObjCBlock`. - +- __Breaking change__: Generated ObjC code has been migrated to ARC (Automatic + Reference Counting), and must now be compiled with ARC enabled. For example, + if you had a line like `s.requires_arc = []` in your podspec, this should + either be removed, or you should add the ffigen generated ObjC code to the + list. If you're compiling directly with clang, add the `-fobjc-arc` flag. ## 13.0.0 diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index c67ce1b45d..6a5a8ae553 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -193,6 +193,8 @@ abstract final class $name { objCRetain: true); final listenerConvFn = '($paramsFfiDartType) => $listenerConvFnInvocation'; + final wrapFn = _wrapListenerBlock?.name; + final releaseFn = ObjCBuiltInFunctions.objectRelease.gen(w); s.write(''' @@ -205,12 +207,22 @@ abstract final class $name { /// /// Note that unlike the default behavior of NativeCallable.listener, listener /// blocks do not keep the isolate alive. - static $blockType listener($funcDartType fn) => - $blockType(${_wrapListenerBlock?.name ?? ''}($newClosureBlock( - (_dartFuncListenerTrampoline ??= $nativeCallableType.listener( - $closureTrampoline $exceptionalReturn)..keepIsolateAlive = - false).nativeFunction.cast(), $listenerConvFn)), - retain: false, release: true); + static $blockType listener($funcDartType fn) { + final raw = $newClosureBlock( + (_dartFuncListenerTrampoline ??= $nativeCallableType.listener( + $closureTrampoline $exceptionalReturn)..keepIsolateAlive = + false).nativeFunction.cast(), $listenerConvFn);'''); + if (wrapFn != null) { + s.write(''' + final wrapper = $wrapFn(raw); + $releaseFn(raw.cast()); + return $blockType(wrapper, retain: false, release: true);'''); + } else { + s.write(''' + return $blockType(raw, retain: false, release: true);'''); + } + s.write(''' + } static $nativeCallableType? _dartFuncListenerTrampoline; '''); } @@ -256,12 +268,10 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( s.write(''' typedef ${getNativeType(varName: blockTypedef)}; -$blockTypedef $fnName($blockTypedef block) { - $blockTypedef wrapper = [^void(${argsReceived.join(', ')}) { +$blockTypedef $fnName($blockTypedef block) NS_RETURNS_RETAINED { + return ^void(${argsReceived.join(', ')}) { block(${retains.join(', ')}); - } copy]; - [block release]; - return wrapper; + }; } '''); return BindingString( @@ -337,7 +347,7 @@ $blockTypedef $fnName($blockTypedef block) { ObjCInterface.generateConstructor(name, value, objCRetain); @override - String? generateRetain(String value) => '[$value copy]'; + String? generateRetain(String value) => 'objc_retainBlock($value)'; @override String toString() => '($returnType (^)(${argTypes.join(', ')}))'; diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart index 55e76c4595..e2944b61cd 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart @@ -25,6 +25,7 @@ class ObjCBuiltInFunctions { static const getProtocolMethodSignature = ObjCImport('getProtocolMethodSignature'); static const getProtocol = ObjCImport('getProtocol'); + static const objectRelease = ObjCImport('objectRelease'); static const objectBase = ObjCImport('ObjCObjectBase'); static const blockType = ObjCImport('ObjCBlock'); static const protocolMethod = ObjCImport('ObjCProtocolMethod'); diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index 9537f866fc..3a75381a2f 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -341,7 +341,7 @@ class ObjCInterface extends BindingType with ObjCMethods { } @override - String? generateRetain(String value) => '[$value retain]'; + String? generateRetain(String value) => 'objc_retain($value)'; String _getConvertedType(Type type, Writer w, String enclosingClass) { if (type is ObjCInstanceType) return enclosingClass; diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index 9c2e550eb3..66f1bd136f 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -135,5 +135,5 @@ class ObjCObjectPointer extends PointerType { '${getDartType(w)}($value, retain: $objCRetain, release: true)'; @override - String? generateRetain(String value) => '[$value retain]'; + String? generateRetain(String value) => 'objc_retain($value)'; } diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 2098032218..8b6dc6ec29 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -418,12 +418,20 @@ class Writer { final s = StringBuffer(); s.write(''' #include - '''); for (final entryPoint in nativeEntryPoints) { s.write(_objcImport(entryPoint, outDir)); } + s.write(''' + +#if !__has_feature(objc_arc) +#error "This file must be compiled with ARC enabled" +#endif + +id objc_retain(id); +id objc_retainBlock(id); +'''); var empty = true; for (final binding in _allBindings) { diff --git a/pkgs/ffigen/test/native_objc_test/arc_config.yaml b/pkgs/ffigen/test/native_objc_test/arc_config.yaml new file mode 100644 index 0000000000..175315d51e --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/arc_config.yaml @@ -0,0 +1,17 @@ +name: ArcTestObjCLibrary +description: 'Tests ARC' +language: objc +output: 'arc_bindings.dart' +exclude-all-by-default: true +functions: + include: + - objc_autoreleasePoolPop + - objc_autoreleasePoolPush +objc-interfaces: + include: + - ArcTestObject +headers: + entry-points: + - 'arc_test.m' +preamble: | + // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field diff --git a/pkgs/ffigen/test/native_objc_test/arc_test.dart b/pkgs/ffigen/test/native_objc_test/arc_test.dart new file mode 100644 index 0000000000..d0933b9f08 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/arc_test.dart @@ -0,0 +1,408 @@ +// 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. + +// ignore_for_file: unused_local_variable + +// Objective C support is only available on mac. +@TestOn('mac-os') + +// This test is slightly flaky. Some of the ref counts are occasionally lower +// than expected, presumably due to the Dart wrapper objects being GC'd before +// the expect call. +@Retry(3) + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:objective_c/objective_c.dart'; +import 'package:test/test.dart'; +import '../test_utils.dart'; +import 'arc_bindings.dart'; +import 'util.dart'; + +void main() { + late ArcTestObjCLibrary lib; + + group('Reference counting', () { + setUpAll(() { + // TODO(https://github.com/dart-lang/native/issues/1068): Remove this. + DynamicLibrary.open('../objective_c/test/objective_c.dylib'); + final dylib = File('test/native_objc_test/arc_test.dylib'); + verifySetupFile(dylib); + lib = ArcTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path)); + + generateBindingsForCoverage('arc'); + }); + + test('objectRetainCount edge cases', () { + expect(objectRetainCount(nullptr), 0); + expect(objectRetainCount(Pointer.fromAddress(0x1234)), 0); + }); + + (Pointer, Pointer) newMethodsInner( + Pointer counter) { + final obj1 = ArcTestObject.new1(); + obj1.setCounter_(counter); + expect(counter.value, 1); + final obj2 = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 2); + + final obj1raw = obj1.pointer; + final obj2raw = obj2.pointer; + + expect(objectRetainCount(obj1raw), 1); + expect(objectRetainCount(obj2raw), 1); + + final obj2b = + ArcTestObject.castFromPointer(obj2raw, retain: true, release: true); + expect(objectRetainCount(obj2b.pointer), 2); + + final obj2c = + ArcTestObject.castFromPointer(obj2raw, retain: true, release: true); + expect(objectRetainCount(obj2c.pointer), 3); + + return (obj1raw, obj2raw); + } + + test('new methods ref count correctly', () { + // To get the GC to work correctly, the references to the objects all have + // to be in a separate function. + final counter = calloc(); + counter.value = 0; + final (obj1raw, obj2raw) = newMethodsInner(counter); + doGC(); + expect(objectRetainCount(obj1raw), 0); + expect(objectRetainCount(obj2raw), 0); + expect(counter.value, 0); + calloc.free(counter); + }); + + (Pointer, Pointer, Pointer) + allocMethodsInner(Pointer counter) { + final obj1 = ArcTestObject.alloc().initWithCounter_(counter); + expect(counter.value, 1); + final obj2 = ArcTestObject.castFrom(ArcTestObject.alloc().init()); + obj2.setCounter_(counter); + expect(counter.value, 2); + final obj3 = ArcTestObject.allocTheThing().initWithCounter_(counter); + expect(counter.value, 3); + + final obj1raw = obj1.pointer; + final obj2raw = obj2.pointer; + final obj3raw = obj3.pointer; + + expect(objectRetainCount(obj1raw), 2); + expect(objectRetainCount(obj2raw), 3); + expect(objectRetainCount(obj3raw), 2); + + return (obj1raw, obj2raw, obj3raw); + } + + test('alloc and init methods ref count correctly', () { + final counter = calloc(); + counter.value = 0; + final (obj1raw, obj2raw, obj3raw) = allocMethodsInner(counter); + doGC(); + expect(objectRetainCount(obj1raw), 0); + expect(objectRetainCount(obj2raw), 0); + expect(objectRetainCount(obj3raw), 0); + expect(counter.value, 0); + calloc.free(counter); + }); + + ( + Pointer, + Pointer, + Pointer, + Pointer, + Pointer + ) copyMethodsInner(Pointer counter) { + final obj1 = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 1); + final obj2 = obj1.copyMe(); + expect(counter.value, 2); + final obj3 = obj1.makeACopy(); + expect(counter.value, 3); + final obj4 = obj1.copyWithZone_(nullptr); + expect(counter.value, 4); + final obj5 = obj1.copy(); + expect(counter.value, 5); + + final obj1raw = obj1.pointer; + final obj2raw = obj2.pointer; + final obj3raw = obj3.pointer; + final obj4raw = obj4.pointer; + final obj5raw = obj5.pointer; + + expect(objectRetainCount(obj1raw), 1); + expect(objectRetainCount(obj2raw), 1); + expect(objectRetainCount(obj3raw), 1); + expect(objectRetainCount(obj4raw), 1); + expect(objectRetainCount(obj5raw), 1); + + return (obj1raw, obj2raw, obj3raw, obj4raw, obj5raw); + } + + test('copy methods ref count correctly', () { + final counter = calloc(); + counter.value = 0; + final (obj1raw, obj2raw, obj3raw, obj4raw, obj5raw) = + copyMethodsInner(counter); + doGC(); + expect(objectRetainCount(obj1raw), 0); + expect(objectRetainCount(obj2raw), 0); + expect(objectRetainCount(obj3raw), 0); + expect(objectRetainCount(obj4raw), 0); + expect(objectRetainCount(obj5raw), 0); + expect(counter.value, 0); + calloc.free(counter); + }); + + Pointer autoreleaseMethodsInner(Pointer counter) { + final obj1 = ArcTestObject.makeAndAutorelease_(counter); + expect(counter.value, 1); + + final obj1raw = obj1.pointer; + expect(objectRetainCount(obj1raw), 2); + return obj1raw; + } + + test('autorelease methods ref count correctly', () { + final counter = calloc(); + counter.value = 0; + + final pool1 = lib.objc_autoreleasePoolPush(); + final obj1raw = autoreleaseMethodsInner(counter); + doGC(); + // The autorelease pool is still holding a reference to the object. + expect(counter.value, 1); + expect(objectRetainCount(obj1raw), 1); + lib.objc_autoreleasePoolPop(pool1); + expect(counter.value, 0); + expect(objectRetainCount(obj1raw), 0); + + final pool2 = lib.objc_autoreleasePoolPush(); + final obj2 = ArcTestObject.makeAndAutorelease_(counter); + final obj2raw = obj2.pointer; + expect(counter.value, 1); + expect(objectRetainCount(obj2raw), 2); + doGC(); + expect(counter.value, 1); + expect(objectRetainCount(obj2raw), 2); + lib.objc_autoreleasePoolPop(pool2); + // The obj2 variable still holds a reference to the object. + expect(counter.value, 1); + expect(objectRetainCount(obj2raw), 1); + obj2.release(); + expect(counter.value, 0); + expect(objectRetainCount(obj2raw), 0); + + calloc.free(counter); + }); + + Pointer assignPropertiesInnerInner( + Pointer counter, ArcTestObject outerObj) { + final assignObj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 2); + final assignObjRaw = assignObj.pointer; + expect(objectRetainCount(assignObjRaw), 1); + outerObj.assignedProperty = assignObj; + expect(counter.value, 2); + expect(assignObj, outerObj.assignedProperty); + expect(objectRetainCount(assignObjRaw), 2); + // To test that outerObj isn't holding a reference to assignObj, we let + // assignObj go out of scope, but keep outerObj in scope. This is + // dangerous because outerObj now has a dangling reference, so don't + // access that reference. + return assignObjRaw; + } + + (Pointer, Pointer) assignPropertiesInner( + Pointer counter) { + final outerObj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 1); + final outerObjRaw = outerObj.pointer; + expect(objectRetainCount(outerObjRaw), 1); + final assignObjRaw = assignPropertiesInnerInner(counter, outerObj); + expect(objectRetainCount(assignObjRaw), 2); + doGC(); + // assignObj has been cleaned up. + expect(counter.value, 1); + expect(objectRetainCount(assignObjRaw), 0); + expect(objectRetainCount(outerObjRaw), 1); + return (outerObjRaw, assignObjRaw); + } + + test('assign properties ref count correctly', () { + final counter = calloc(); + counter.value = 0; + final (outerObjRaw, assignObjRaw) = assignPropertiesInner(counter); + expect(objectRetainCount(assignObjRaw), 0); + expect(objectRetainCount(outerObjRaw), 1); + doGC(); + expect(counter.value, 0); + expect(objectRetainCount(assignObjRaw), 0); + expect(objectRetainCount(outerObjRaw), 0); + calloc.free(counter); + }); + + Pointer retainPropertiesInnerInner( + Pointer counter, ArcTestObject outerObj) { + final retainObj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 2); + final retainObjRaw = retainObj.pointer; + expect(objectRetainCount(retainObjRaw), 1); + outerObj.retainedProperty = retainObj; + expect(counter.value, 2); + expect(retainObj, outerObj.retainedProperty); + expect(objectRetainCount(retainObjRaw), 4); + return retainObjRaw; + } + + (Pointer, Pointer) retainPropertiesInner( + Pointer counter) { + final outerObj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 1); + final outerObjRaw = outerObj.pointer; + expect(objectRetainCount(outerObjRaw), 1); + final retainObjRaw = retainPropertiesInnerInner(counter, outerObj); + expect(objectRetainCount(retainObjRaw), 4); + doGC(); + // retainObj is still around, because outerObj retains a reference to it. + expect(objectRetainCount(retainObjRaw), 2); + expect(objectRetainCount(outerObjRaw), 1); + expect(counter.value, 2); + return (outerObjRaw, retainObjRaw); + } + + test('retain properties ref count correctly', () { + final counter = calloc(); + counter.value = 0; + // The getters of retain properties retain+autorelease the value. So we + // need an autorelease pool. + final pool = lib.objc_autoreleasePoolPush(); + final (outerObjRaw, retainObjRaw) = retainPropertiesInner(counter); + expect(objectRetainCount(retainObjRaw), 2); + expect(objectRetainCount(outerObjRaw), 1); + doGC(); + expect(objectRetainCount(retainObjRaw), 1); + expect(objectRetainCount(outerObjRaw), 0); + expect(counter.value, 1); + lib.objc_autoreleasePoolPop(pool); + expect(objectRetainCount(retainObjRaw), 0); + expect(objectRetainCount(outerObjRaw), 0); + expect(counter.value, 0); + calloc.free(counter); + }); + + (Pointer, Pointer, Pointer) + copyPropertiesInner(Pointer counter) { + final outerObj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 1); + + final copyObj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 2); + outerObj.copiedProperty = copyObj; + // Copy properties make a copy of the object, so now we have 3 objects. + expect(counter.value, 3); + expect(copyObj, isNot(outerObj.copiedProperty)); + + final anotherCopy = outerObj.copiedProperty; + // The getter doesn't copy the object. + expect(counter.value, 3); + expect(anotherCopy, outerObj.copiedProperty); + + final outerObjRaw = outerObj.pointer; + final copyObjRaw = copyObj.pointer; + final anotherCopyRaw = anotherCopy.pointer; + + expect(objectRetainCount(outerObjRaw), 1); + expect(objectRetainCount(copyObjRaw), 1); + expect(objectRetainCount(anotherCopyRaw), 7); + + return (outerObjRaw, copyObjRaw, anotherCopyRaw); + } + + test('copy properties ref count correctly', () { + final counter = calloc(); + counter.value = 0; + // The getters of copy properties retain+autorelease the value. So we need + // an autorelease pool. + final pool = lib.objc_autoreleasePoolPush(); + final (outerObjRaw, copyObjRaw, anotherCopyRaw) = + copyPropertiesInner(counter); + doGC(); + expect(counter.value, 1); + expect(objectRetainCount(outerObjRaw), 0); + expect(objectRetainCount(copyObjRaw), 0); + expect(objectRetainCount(anotherCopyRaw), 3); + lib.objc_autoreleasePoolPop(pool); + expect(counter.value, 0); + expect(objectRetainCount(outerObjRaw), 0); + expect(objectRetainCount(copyObjRaw), 0); + expect(objectRetainCount(anotherCopyRaw), 0); + calloc.free(counter); + }); + + test('Manual release', () { + final counter = calloc(); + final obj1 = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 1); + final obj2 = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 2); + final obj3 = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 3); + + final obj1raw = obj1.pointer; + final obj2raw = obj2.pointer; + final obj3raw = obj3.pointer; + + expect(objectRetainCount(obj1raw), 1); + expect(objectRetainCount(obj2raw), 1); + expect(objectRetainCount(obj3raw), 1); + + obj1.release(); + expect(counter.value, 2); + obj2.release(); + expect(counter.value, 1); + obj3.release(); + expect(counter.value, 0); + + expect(() => obj1.release(), throwsStateError); + calloc.free(counter); + + expect(objectRetainCount(obj1raw), 0); + expect(objectRetainCount(obj2raw), 0); + expect(objectRetainCount(obj3raw), 0); + }); + + void largeRefCountInner(Pointer counter) { + final obj = ArcTestObject.newWithCounter_(counter); + expect(counter.value, 1); + final objRefs = []; + for (int i = 1; i < 1000; ++i) { + final expectedCount = i < 128 ? i : 128; + expect(objectRetainCount(obj.pointer), expectedCount); + objRefs.add(ArcTestObject.castFromPointer(obj.pointer, + retain: true, release: true)); + } + expect(counter.value, 1); + } + + test("objectRetainCount large ref count", () { + // Most ObjC API methods return us a reference without incrementing the + // ref count (ie, returns us a reference we don't own). So the wrapper + // object has to take ownership by calling retain. This test verifies that + // is working correctly by holding a reference to an object returned by a + // method, after the original wrapper object is gone. + final counter = calloc(); + counter.value = 0; + largeRefCountInner(counter); + doGC(); + expect(counter.value, 0); + calloc.free(counter); + }); + }); +} diff --git a/pkgs/ffigen/test/native_objc_test/arc_test.m b/pkgs/ffigen/test/native_objc_test/arc_test.m new file mode 100644 index 0000000000..3e9d249bbf --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/arc_test.m @@ -0,0 +1,82 @@ +// 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 + +#include "util.h" + +#if !__has_feature(objc_arc) +#error "This file must be compiled with ARC enabled" +#endif + +void objc_autoreleasePoolPop(void *pool); +void *objc_autoreleasePoolPush(); + +@interface ArcTestObject : NSObject { + int32_t* counter; +} + ++ (instancetype)allocTheThing; ++ (instancetype)newWithCounter:(int32_t*) _counter; +- (instancetype)initWithCounter:(int32_t*) _counter; ++ (ArcTestObject*)makeAndAutorelease:(int32_t*) _counter; +- (void)setCounter:(int32_t*) _counter; +- (void)dealloc; +- (ArcTestObject*)copyMe; +- (ArcTestObject*)makeACopy NS_RETURNS_RETAINED; +- (id)copyWithZone:(NSZone*) zone; +- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED; + +@property (assign) ArcTestObject* assignedProperty; +@property (retain) ArcTestObject* retainedProperty; +@property (copy) ArcTestObject* copiedProperty; + +@end + +@implementation ArcTestObject + ++ (instancetype)allocTheThing { + return [ArcTestObject alloc]; +} + ++ (instancetype)newWithCounter:(int32_t*) _counter { + return [[ArcTestObject alloc] initWithCounter: _counter]; +} + +- (instancetype)initWithCounter:(int32_t*) _counter { + counter = _counter; + ++*counter; + return [super init]; +} + ++ (instancetype)makeAndAutorelease:(int32_t*) _counter { + return [[ArcTestObject alloc] initWithCounter: _counter]; +} + +- (void)setCounter:(int32_t*) _counter { + counter = _counter; + ++*counter; +} + +- (void)dealloc { + --*counter; +} + +- (ArcTestObject*)copyMe { + return [[ArcTestObject alloc] initWithCounter: counter]; +} + +- (ArcTestObject*)makeACopy NS_RETURNS_RETAINED { + return [[ArcTestObject alloc] initWithCounter: counter]; +} + +- (id)copyWithZone:(NSZone*) zone { + return [[ArcTestObject alloc] initWithCounter: counter]; +} + +- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED { + return self; +} + +@end diff --git a/pkgs/ffigen/test/native_objc_test/block_inherit_test.m b/pkgs/ffigen/test/native_objc_test/block_inherit_test.m index 2d40635b1f..567dde563c 100644 --- a/pkgs/ffigen/test/native_objc_test/block_inherit_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_inherit_test.m @@ -17,8 +17,8 @@ @implementation Platypus - (BOOL)laysEggs { return YES; } @end -typedef Mammal* (^ReturnMammal)(); -typedef Platypus* (^ReturnPlatypus)(); +typedef Mammal* (^ReturnMammal)() NS_RETURNS_RETAINED; +typedef Platypus* (^ReturnPlatypus)() NS_RETURNS_RETAINED; typedef BOOL (^AcceptMammal)(Mammal*); typedef BOOL (^AcceptPlatypus)(Platypus*); @@ -41,7 +41,7 @@ - (Mammal*) getAnimal NS_RETURNS_RETAINED { return [Mammal new]; } - (BOOL) acceptAnimal: (Platypus*)platypus { return [platypus laysEggs]; } - (ReturnMammal) getReturner NS_RETURNS_RETAINED { - return [^Mammal*() { return [Mammal new]; } copy]; + return [^Mammal*() NS_RETURNS_RETAINED { return [Mammal new]; } copy]; } - (AcceptPlatypus) getAccepter NS_RETURNS_RETAINED { @@ -54,9 +54,7 @@ - (Mammal*) invokeReturner: (ReturnPlatypus)returner NS_RETURNS_RETAINED { - (BOOL) invokeAccepter: (AcceptMammal)accepter { Mammal* mammal = [Mammal new]; - BOOL result = accepter(mammal); - [mammal release]; - return result; + return accepter(mammal); } @end @@ -74,7 +72,7 @@ - (Platypus*) getAnimal NS_RETURNS_RETAINED { return [Platypus new]; } - (BOOL) acceptAnimal: (Mammal*)mammal { return [mammal laysEggs]; } - (ReturnPlatypus) getReturner NS_RETURNS_RETAINED { - return [^Platypus*() { return [Platypus new]; } copy]; + return [^Platypus*() NS_RETURNS_RETAINED { return [Platypus new]; } copy]; } - (AcceptMammal) getAccepter NS_RETURNS_RETAINED { @@ -87,8 +85,6 @@ - (Mammal*) invokeReturner: (ReturnMammal)returner NS_RETURNS_RETAINED { - (BOOL) invokeAccepter: (AcceptPlatypus)accepter { Platypus* platypus = [Platypus new]; - BOOL result = accepter(platypus); - [platypus release]; - return result; + return accepter(platypus); } @end diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index c321753064..5a63c11d42 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -51,10 +51,10 @@ void main() { test('BlockTester is working', () { // This doesn't test any Block functionality, just that the BlockTester // itself is working correctly. - final blockTester = BlockTester.makeFromMultiplier_(10); + final blockTester = BlockTester.newFromMultiplier_(10); expect(blockTester.call_(123), 1230); final intBlock = blockTester.getBlock(); - final blockTester2 = BlockTester.makeFromBlock_(intBlock); + final blockTester2 = BlockTester.newFromBlock_(intBlock); blockTester2.pokeBlock(); expect(blockTester2.call_(456), 4560); }); @@ -62,7 +62,7 @@ void main() { test('Block from function pointer', () { final block = IntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); - final blockTester = BlockTester.makeFromBlock_(block); + final blockTester = BlockTester.newFromBlock_(block); blockTester.pokeBlock(); expect(blockTester.call_(123), 223); expect(block(123), 223); @@ -74,7 +74,7 @@ void main() { test('Block from function', () { final block = IntBlock.fromFunction(makeAdder(4000)); - final blockTester = BlockTester.makeFromBlock_(block); + final blockTester = BlockTester.newFromBlock_(block); blockTester.pokeBlock(); expect(blockTester.call_(123), 4123); expect(block(123), 4123); @@ -596,6 +596,7 @@ void main() { await hasRun.future; expect(inputBlock(123), 12300); + thread.release(); doGC(); expect(blockRetainCount(inputBlock.pointer), 1); diff --git a/pkgs/ffigen/test/native_objc_test/block_test.h b/pkgs/ffigen/test/native_objc_test/block_test.h index 9c77fb5150..a87ebe5d5f 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.h +++ b/pkgs/ffigen/test/native_objc_test/block_test.h @@ -21,21 +21,21 @@ typedef struct { @interface DummyObject : NSObject { int32_t* counter; } -+ (instancetype)newWithCounter:(int32_t*) _counter; -- (instancetype)initWithCounter:(int32_t*) _counter; -- (void)setCounter:(int32_t*) _counter; ++ (instancetype)newWithCounter:(int32_t*)_counter; +- (instancetype)initWithCounter:(int32_t*)_counter; +- (void)setCounter:(int32_t*)_counter; - (void)dealloc; @end - typedef int32_t (^IntBlock)(int32_t); typedef float (^FloatBlock)(float); typedef double (^DoubleBlock)(double); typedef Vec4 (^Vec4Block)(Vec4); typedef void (^VoidBlock)(); -typedef DummyObject* (^ObjectBlock)(DummyObject*); -typedef DummyObject* _Nullable (^NullableObjectBlock)(DummyObject* _Nullable); -typedef IntBlock (^BlockBlock)(IntBlock); +typedef DummyObject* (^ObjectBlock)(DummyObject*) NS_RETURNS_RETAINED; +typedef DummyObject* _Nullable (^NullableObjectBlock)(DummyObject* _Nullable) + NS_RETURNS_RETAINED; +typedef IntBlock (^BlockBlock)(IntBlock) NS_RETURNS_RETAINED; typedef void (^ListenerBlock)(IntBlock); typedef void (^ObjectListenerBlock)(DummyObject*); typedef void (^NullableListenerBlock)(DummyObject* _Nullable); @@ -46,27 +46,28 @@ typedef void (^NoTrampolineListenerBlock)(int32_t, Vec4, const char*); // Wrapper around a block, so that our Dart code can test creating and invoking // blocks in Objective C code. @interface BlockTester : NSObject { - IntBlock myBlock; + __strong IntBlock myBlock; } -+ (BlockTester*)makeFromBlock:(IntBlock)block; -+ (BlockTester*)makeFromMultiplier:(int32_t)mult; ++ (BlockTester*)newFromBlock:(IntBlock)block; ++ (BlockTester*)newFromMultiplier:(int32_t)mult; - (int32_t)call:(int32_t)x; -- (IntBlock)getBlock; +- (IntBlock)getBlock NS_RETURNS_RETAINED; - (void)pokeBlock; + (void)callOnSameThread:(VoidBlock)block; -+ (NSThread*)callOnNewThread:(VoidBlock)block; -+ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block; ++ (NSThread*)callOnNewThread:(VoidBlock)block NS_RETURNS_RETAINED; ++ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block NS_RETURNS_RETAINED; + (float)callFloatBlock:(FloatBlock)block; + (double)callDoubleBlock:(DoubleBlock)block; + (Vec4)callVec4Block:(Vec4Block)block; + (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED; -+ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block; ++ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block + NS_RETURNS_RETAINED; + (void)callListener:(ListenerBlock)block; + (void)callObjectListener:(ObjectListenerBlock)block; + (void)callNullableListener:(NullableListenerBlock)block; + (void)callStructListener:(StructListenerBlock)block; + (void)callNSStringListener:(NSStringListenerBlock)block x:(int32_t)x; + (void)callNoTrampolineListener:(NoTrampolineListenerBlock)block; -+ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult; -+ (BlockBlock)newBlockBlock:(int)mult; ++ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult NS_RETURNS_RETAINED; ++ (BlockBlock)newBlockBlock:(int)mult NS_RETURNS_RETAINED; @end diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m index aa055f91db..e2c523b8d8 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_test.m @@ -11,38 +11,36 @@ @implementation DummyObject -+ (instancetype)newWithCounter:(int32_t*) _counter { - return [[DummyObject alloc] initWithCounter: _counter]; ++ (instancetype)newWithCounter:(int32_t*)_counter { + return [[DummyObject alloc] initWithCounter:_counter]; } -- (instancetype)initWithCounter:(int32_t*) _counter { +- (instancetype)initWithCounter:(int32_t*)_counter { counter = _counter; ++*counter; return [super init]; } -- (void)setCounter:(int32_t*) _counter { +- (void)setCounter:(int32_t*)_counter { counter = _counter; ++*counter; } - (void)dealloc { if (counter != nil) --*counter; - [super dealloc]; } @end - @implementation BlockTester -+ (BlockTester*)makeFromBlock:(IntBlock)block { ++ (BlockTester*)newFromBlock:(IntBlock)block { BlockTester* bt = [BlockTester new]; bt->myBlock = block; return bt; } -+ (BlockTester*)makeFromMultiplier:(int32_t)mult { ++ (BlockTester*)newFromMultiplier:(int32_t)mult { BlockTester* bt = [BlockTester new]; bt->myBlock = [^int32_t(int32_t x) { return x * mult; @@ -54,21 +52,23 @@ - (int32_t)call:(int32_t)x { return myBlock(x); } -- (IntBlock)getBlock { +- (IntBlock)getBlock NS_RETURNS_RETAINED { return myBlock; } +id objc_retain(id value); +void objc_release(id value); - (void)pokeBlock { // Used to repro https://github.com/dart-lang/ffigen/issues/376 - [[myBlock retain] release]; + objc_release(objc_retain(myBlock)); } + (void)callOnSameThread:(VoidBlock)block { block(); } -+ (NSThread*)callOnNewThread:(VoidBlock)block { - return [[NSThread alloc] initWithBlock: block]; ++ (NSThread*)callOnNewThread:(VoidBlock)block NS_RETURNS_RETAINED { + return [[NSThread alloc] initWithBlock:block]; } + (void)callListener:(ListenerBlock)block { @@ -80,15 +80,12 @@ + (void)callListener:(ListenerBlock)block { // always has a ref count of 0, so we can't test the ref counting. int mult = 100; - IntBlock inputBlock = [^int(int x) { + block(^int(int x) { return mult * x; - } copy]; - // ^ copy this stack allocated block to the heap. - block(inputBlock); - [inputBlock release]; // Release the reference held by this scope. + }); } -+ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block { ++ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block NS_RETURNS_RETAINED { return [[NSThread alloc] initWithTarget:[BlockTester class] selector:@selector(callListener:) object:block]; @@ -121,7 +118,7 @@ + (void)callStructListener:(StructListenerBlock)block { block(vec2, vec4, dummy); } -+ (void)callNSStringListener:(NSStringListenerBlock)block x:(int32_t)x { ++ (void)callNSStringListener:(NSStringListenerBlock)block x:(int32_t)x { block([NSString stringWithFormat:@"Foo %d", x]); } @@ -152,32 +149,27 @@ + (Vec4)callVec4Block:(Vec4Block)block { } + (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED { - DummyObject* inputObject = [DummyObject new]; - DummyObject* outputObject = block(inputObject); - [inputObject release]; // Release the reference held by this scope. - return outputObject; + return block([DummyObject new]); } -+ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block { ++ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block + NS_RETURNS_RETAINED { return block(nil); } -+ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult { - IntBlock inputBlock = [^int(int x) { ++ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult NS_RETURNS_RETAINED { + IntBlock inputBlock = ^int(int x) { return mult * x; - } copy]; - // ^ copy this stack allocated block to the heap. - IntBlock outputBlock = block(inputBlock); - [inputBlock release]; // Release the reference held by this scope. - return outputBlock; + }; + return block(inputBlock); } -+ (BlockBlock)newBlockBlock:(int)mult { - return [^IntBlock(IntBlock block) { - return [^int(int x) { ++ (BlockBlock)newBlockBlock:(int)mult NS_RETURNS_RETAINED { + return ^IntBlock(IntBlock block) NS_RETURNS_RETAINED { + return ^int(int x) { return mult * block(x); - } copy]; - } copy]; + }; + }; } @end diff --git a/pkgs/ffigen/test/native_objc_test/automated_ref_count_config.yaml b/pkgs/ffigen/test/native_objc_test/ref_count_config.yaml similarity index 61% rename from pkgs/ffigen/test/native_objc_test/automated_ref_count_config.yaml rename to pkgs/ffigen/test/native_objc_test/ref_count_config.yaml index 7ecf98715e..c6ecdcb473 100644 --- a/pkgs/ffigen/test/native_objc_test/automated_ref_count_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/ref_count_config.yaml @@ -1,7 +1,7 @@ -name: AutomatedRefCountTestObjCLibrary -description: 'Tests automatic reference counting of Objective-C objects' +name: RefCountTestObjCLibrary +description: 'Tests reference counting of Objective-C objects' language: objc -output: 'automated_ref_count_bindings.dart' +output: 'ref_count_bindings.dart' exclude-all-by-default: true functions: include: @@ -9,10 +9,10 @@ functions: - destroyAutoreleasePool objc-interfaces: include: - - ArcTestObject + - RefCountTestObject - RefCounted headers: entry-points: - - 'automated_ref_count_test.m' + - 'ref_count_test.m' preamble: | // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field diff --git a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart b/pkgs/ffigen/test/native_objc_test/ref_count_test.dart similarity index 87% rename from pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart rename to pkgs/ffigen/test/native_objc_test/ref_count_test.dart index caf4c36b30..4ea2de758d 100644 --- a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart +++ b/pkgs/ffigen/test/native_objc_test/ref_count_test.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// 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. @@ -19,23 +19,21 @@ import 'package:ffi/ffi.dart'; import 'package:objective_c/objective_c.dart'; import 'package:test/test.dart'; import '../test_utils.dart'; -import 'automated_ref_count_bindings.dart'; +import 'ref_count_bindings.dart'; import 'util.dart'; void main() { - late AutomatedRefCountTestObjCLibrary lib; + late RefCountTestObjCLibrary lib; - group('Automatic reference counting', () { + group('Reference counting', () { setUpAll(() { // TODO(https://github.com/dart-lang/native/issues/1068): Remove this. DynamicLibrary.open('../objective_c/test/objective_c.dylib'); - final dylib = - File('test/native_objc_test/automated_ref_count_test.dylib'); + final dylib = File('test/native_objc_test/ref_count_test.dylib'); verifySetupFile(dylib); - lib = AutomatedRefCountTestObjCLibrary( - DynamicLibrary.open(dylib.absolute.path)); + lib = RefCountTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path)); - generateBindingsForCoverage('automated_ref_count'); + generateBindingsForCoverage('ref_count'); }); test('objectRetainCount edge cases', () { @@ -45,10 +43,10 @@ void main() { (Pointer, Pointer) newMethodsInner( Pointer counter) { - final obj1 = ArcTestObject.new1(); + final obj1 = RefCountTestObject.new1(); obj1.setCounter_(counter); expect(counter.value, 1); - final obj2 = ArcTestObject.newWithCounter_(counter); + final obj2 = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 2); final obj1raw = obj1.pointer; @@ -57,12 +55,12 @@ void main() { expect(objectRetainCount(obj1raw), 1); expect(objectRetainCount(obj2raw), 1); - final obj2b = - ArcTestObject.castFromPointer(obj2raw, retain: true, release: true); + final obj2b = RefCountTestObject.castFromPointer(obj2raw, + retain: true, release: true); expect(objectRetainCount(obj2b.pointer), 2); - final obj2c = - ArcTestObject.castFromPointer(obj2raw, retain: true, release: true); + final obj2c = RefCountTestObject.castFromPointer(obj2raw, + retain: true, release: true); expect(objectRetainCount(obj2c.pointer), 3); return (obj1raw, obj2raw); @@ -83,12 +81,13 @@ void main() { (Pointer, Pointer, Pointer) allocMethodsInner(Pointer counter) { - final obj1 = ArcTestObject.alloc().initWithCounter_(counter); + final obj1 = RefCountTestObject.alloc().initWithCounter_(counter); expect(counter.value, 1); - final obj2 = ArcTestObject.castFrom(ArcTestObject.alloc().init()); + final obj2 = + RefCountTestObject.castFrom(RefCountTestObject.alloc().init()); obj2.setCounter_(counter); expect(counter.value, 2); - final obj3 = ArcTestObject.allocTheThing().initWithCounter_(counter); + final obj3 = RefCountTestObject.allocTheThing().initWithCounter_(counter); expect(counter.value, 3); final obj1raw = obj1.pointer; @@ -121,7 +120,7 @@ void main() { Pointer, Pointer ) copyMethodsInner(Pointer counter) { - final obj1 = ArcTestObject.newWithCounter_(counter); + final obj1 = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); final obj2 = obj1.copyMe(); expect(counter.value, 2); @@ -163,7 +162,7 @@ void main() { }); Pointer autoreleaseMethodsInner(Pointer counter) { - final obj1 = ArcTestObject.makeAndAutorelease_(counter); + final obj1 = RefCountTestObject.makeAndAutorelease_(counter); expect(counter.value, 1); final obj1raw = obj1.pointer; @@ -186,7 +185,7 @@ void main() { expect(objectRetainCount(obj1raw), 0); final pool2 = lib.createAutoreleasePool(); - final obj2 = ArcTestObject.makeAndAutorelease_(counter); + final obj2 = RefCountTestObject.makeAndAutorelease_(counter); final obj2raw = obj2.pointer; expect(counter.value, 1); expect(objectRetainCount(obj2raw), 2); @@ -205,8 +204,8 @@ void main() { }); Pointer assignPropertiesInnerInner( - Pointer counter, ArcTestObject outerObj) { - final assignObj = ArcTestObject.newWithCounter_(counter); + Pointer counter, RefCountTestObject outerObj) { + final assignObj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 2); final assignObjRaw = assignObj.pointer; expect(objectRetainCount(assignObjRaw), 1); @@ -223,7 +222,7 @@ void main() { (Pointer, Pointer) assignPropertiesInner( Pointer counter) { - final outerObj = ArcTestObject.newWithCounter_(counter); + final outerObj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); final outerObjRaw = outerObj.pointer; expect(objectRetainCount(outerObjRaw), 1); @@ -251,8 +250,8 @@ void main() { }); Pointer retainPropertiesInnerInner( - Pointer counter, ArcTestObject outerObj) { - final retainObj = ArcTestObject.newWithCounter_(counter); + Pointer counter, RefCountTestObject outerObj) { + final retainObj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 2); final retainObjRaw = retainObj.pointer; expect(objectRetainCount(retainObjRaw), 1); @@ -265,7 +264,7 @@ void main() { (Pointer, Pointer) retainPropertiesInner( Pointer counter) { - final outerObj = ArcTestObject.newWithCounter_(counter); + final outerObj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); final outerObjRaw = outerObj.pointer; expect(objectRetainCount(outerObjRaw), 1); @@ -301,10 +300,10 @@ void main() { (Pointer, Pointer, Pointer) copyPropertiesInner(Pointer counter) { - final outerObj = ArcTestObject.newWithCounter_(counter); + final outerObj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); - final copyObj = ArcTestObject.newWithCounter_(counter); + final copyObj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 2); outerObj.copiedProperty = copyObj; // Copy properties make a copy of the object, so now we have 3 objects. @@ -384,11 +383,11 @@ void main() { test('Manual release', () { final counter = calloc(); - final obj1 = ArcTestObject.newWithCounter_(counter); + final obj1 = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); - final obj2 = ArcTestObject.newWithCounter_(counter); + final obj2 = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 2); - final obj3 = ArcTestObject.newWithCounter_(counter); + final obj3 = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 3); final obj1raw = obj1.pointer; @@ -415,7 +414,7 @@ void main() { }); Pointer manualRetainInner(Pointer counter) { - final obj = ArcTestObject.newWithCounter_(counter); + final obj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); final objRaw = obj.retainAndReturnPointer(); expect(objectRetainCount(objRaw), 2); @@ -423,8 +422,8 @@ void main() { } manualRetainInner2(Pointer counter, Pointer objRaw) { - final obj = - ArcTestObject.castFromPointer(objRaw, retain: false, release: true); + final obj = RefCountTestObject.castFromPointer(objRaw, + retain: false, release: true); expect(counter.value, 1); expect(objectRetainCount(objRaw), 1); } @@ -444,8 +443,8 @@ void main() { calloc.free(counter); }); - ArcTestObject unownedReferenceInner2(Pointer counter) { - final obj1 = ArcTestObject.new1(); + RefCountTestObject unownedReferenceInner2(Pointer counter) { + final obj1 = RefCountTestObject.new1(); obj1.setCounter_(counter); expect(counter.value, 1); expect(objectRetainCount(obj1.pointer), 1); @@ -456,7 +455,7 @@ void main() { // Make a second object so that the counter check in unownedReferenceInner // sees some sort of change. Otherwise this test could pass just by the GC // not working correctly. - final obj2 = ArcTestObject.new1(); + final obj2 = RefCountTestObject.new1(); obj2.setCounter_(counter); expect(counter.value, 2); expect(objectRetainCount(obj2.pointer), 1); @@ -489,13 +488,13 @@ void main() { }); void largeRefCountInner(Pointer counter) { - final obj = ArcTestObject.newWithCounter_(counter); + final obj = RefCountTestObject.newWithCounter_(counter); expect(counter.value, 1); - final objRefs = []; + final objRefs = []; for (int i = 1; i < 1000; ++i) { final expectedCount = i < 128 ? i : 128; expect(objectRetainCount(obj.pointer), expectedCount); - objRefs.add(ArcTestObject.castFromPointer(obj.pointer, + objRefs.add(RefCountTestObject.castFromPointer(obj.pointer, retain: true, release: true)); } expect(counter.value, 1); diff --git a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m b/pkgs/ffigen/test/native_objc_test/ref_count_test.m similarity index 60% rename from pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m rename to pkgs/ffigen/test/native_objc_test/ref_count_test.m index 420064cc56..7caccc6827 100644 --- a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m +++ b/pkgs/ffigen/test/native_objc_test/ref_count_test.m @@ -1,4 +1,4 @@ -// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// 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. @@ -7,25 +7,29 @@ #include "util.h" -@interface ArcTestObject : NSObject { +#if __has_feature(objc_arc) +#error "This file must be compiled with ARC disabled" +#endif + +@interface RefCountTestObject : NSObject { int32_t* counter; } + (instancetype)allocTheThing; + (instancetype)newWithCounter:(int32_t*) _counter; - (instancetype)initWithCounter:(int32_t*) _counter; -+ (ArcTestObject*)makeAndAutorelease:(int32_t*) _counter; ++ (RefCountTestObject*)makeAndAutorelease:(int32_t*) _counter; - (void)setCounter:(int32_t*) _counter; - (void)dealloc; -- (ArcTestObject*)unownedReference; -- (ArcTestObject*)copyMe; -- (ArcTestObject*)makeACopy; +- (RefCountTestObject*)unownedReference; +- (RefCountTestObject*)copyMe; +- (RefCountTestObject*)makeACopy; - (id)copyWithZone:(NSZone*) zone; -- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED; +- (RefCountTestObject*)returnsRetained NS_RETURNS_RETAINED; -@property (assign) ArcTestObject* assignedProperty; -@property (retain) ArcTestObject* retainedProperty; -@property (copy) ArcTestObject* copiedProperty; +@property (assign) RefCountTestObject* assignedProperty; +@property (retain) RefCountTestObject* retainedProperty; +@property (copy) RefCountTestObject* copiedProperty; @end @@ -37,14 +41,14 @@ - (int64_t) meAsInt; @end -@implementation ArcTestObject +@implementation RefCountTestObject + (instancetype)allocTheThing { - return [ArcTestObject alloc]; + return [RefCountTestObject alloc]; } + (instancetype)newWithCounter:(int32_t*) _counter { - return [[ArcTestObject alloc] initWithCounter: _counter]; + return [[RefCountTestObject alloc] initWithCounter: _counter]; } - (instancetype)initWithCounter:(int32_t*) _counter { @@ -54,7 +58,7 @@ - (instancetype)initWithCounter:(int32_t*) _counter { } + (instancetype)makeAndAutorelease:(int32_t*) _counter { - return [[[ArcTestObject alloc] initWithCounter: _counter] autorelease]; + return [[[RefCountTestObject alloc] initWithCounter: _counter] autorelease]; } - (void)setCounter:(int32_t*) _counter { @@ -69,23 +73,23 @@ - (void)dealloc { [super dealloc]; } -- (ArcTestObject*)unownedReference { +- (RefCountTestObject*)unownedReference { return self; } -- (ArcTestObject*)copyMe { - return [[ArcTestObject alloc] initWithCounter: counter]; +- (RefCountTestObject*)copyMe { + return [[RefCountTestObject alloc] initWithCounter: counter]; } -- (ArcTestObject*)makeACopy { - return [[ArcTestObject alloc] initWithCounter: counter]; +- (RefCountTestObject*)makeACopy { + return [[RefCountTestObject alloc] initWithCounter: counter]; } - (id)copyWithZone:(NSZone*) zone { - return [[ArcTestObject alloc] initWithCounter: counter]; + return [[RefCountTestObject alloc] initWithCounter: counter]; } -- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED { +- (RefCountTestObject*)returnsRetained NS_RETURNS_RETAINED { return [self retain]; } diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart index c08961ec88..eccf1ddf71 100644 --- a/pkgs/ffigen/test/native_objc_test/setup.dart +++ b/pkgs/ffigen/test/native_objc_test/setup.dart @@ -5,6 +5,11 @@ import 'dart:async'; import 'dart:io'; +// All ObjC source files are compiled with ARC enabled except these. +const arcDisabledFiles = { + 'ref_count_test.m', +}; + Future _runClang(List flags, String output) async { final args = [...flags, '-o', output]; final process = await Process.start('clang', args); @@ -19,7 +24,15 @@ Future _runClang(List flags, String output) async { Future _buildObject(String input) async { final output = '$input.o'; - await _runClang(['-x', 'objective-c', '-c', input, '-fpic'], output); + await _runClang([ + '-x', + 'objective-c', + if (!arcDisabledFiles.contains(input)) '-fobjc-arc', + '-Wno-nullability-completeness', + '-c', + input, + '-fpic' + ], output); return output; } diff --git a/pkgs/ffigen/test/native_objc_test/static_func_config.yaml b/pkgs/ffigen/test/native_objc_test/static_func_config.yaml index 18e8a4dfbb..826472162e 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/static_func_config.yaml @@ -10,6 +10,8 @@ functions: - staticFuncOfBlock - staticFuncReturnsRetained - staticFuncReturnsRetainedArg + - objc_autoreleasePoolPush + - objc_autoreleasePoolPop headers: entry-points: - 'static_func_test.m' diff --git a/pkgs/ffigen/test/native_objc_test/static_func_native_config.yaml b/pkgs/ffigen/test/native_objc_test/static_func_native_config.yaml index 8ced29aec1..4fa0645dca 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_native_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/static_func_native_config.yaml @@ -11,6 +11,8 @@ functions: - staticFuncOfBlock - staticFuncReturnsRetained - staticFuncReturnsRetainedArg + - objc_autoreleasePoolPush + - objc_autoreleasePoolPop headers: entry-points: - 'static_func_test.m' diff --git a/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart b/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart index 7bfca84248..5d6e33c4ed 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart +++ b/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart @@ -42,7 +42,9 @@ void main() { final obj = StaticFuncTestObj.newWithCounter_(counter); expect(counter.value, 1); + final pool = objc_autoreleasePoolPush(); final outputObj = staticFuncOfObject(obj); + objc_autoreleasePoolPop(pool); expect(obj, outputObj); expect(counter.value, 1); @@ -64,7 +66,9 @@ void main() { final obj = StaticFuncTestObj.newWithCounter_(counter); expect(counter.value, 1); + final pool = objc_autoreleasePoolPush(); final outputObj = staticFuncOfNullableObject(obj); + objc_autoreleasePoolPop(pool); expect(obj, outputObj); expect(counter.value, 1); @@ -86,7 +90,9 @@ void main() { final block = IntBlock.fromFunction((int x) => 2 * x); expect(blockRetainCount(block.pointer.cast()), 1); + final pool = objc_autoreleasePoolPush(); final outputBlock = staticFuncOfBlock(block); + objc_autoreleasePoolPop(pool); expect(block, outputBlock); expect(blockRetainCount(block.pointer.cast()), 2); diff --git a/pkgs/ffigen/test/native_objc_test/static_func_test.dart b/pkgs/ffigen/test/native_objc_test/static_func_test.dart index bd7728461f..8eb02224dc 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_test.dart +++ b/pkgs/ffigen/test/native_objc_test/static_func_test.dart @@ -44,7 +44,9 @@ void main() { final obj = StaticFuncTestObj.newWithCounter_(counter); expect(counter.value, 1); + final pool = lib.objc_autoreleasePoolPush(); final outputObj = lib.staticFuncOfObject(obj); + lib.objc_autoreleasePoolPop(pool); expect(obj, outputObj); expect(counter.value, 1); @@ -66,7 +68,9 @@ void main() { final obj = StaticFuncTestObj.newWithCounter_(counter); expect(counter.value, 1); + final pool = lib.objc_autoreleasePoolPush(); final outputObj = lib.staticFuncOfNullableObject(obj); + lib.objc_autoreleasePoolPop(pool); expect(obj, outputObj); expect(counter.value, 1); @@ -88,7 +92,9 @@ void main() { final block = IntBlock.fromFunction((int x) => 2 * x); expect(blockRetainCount(block.pointer.cast()), 1); + final pool = lib.objc_autoreleasePoolPush(); final outputBlock = lib.staticFuncOfBlock(block); + lib.objc_autoreleasePoolPop(pool); expect(block, outputBlock); expect(blockRetainCount(block.pointer.cast()), 2); diff --git a/pkgs/ffigen/test/native_objc_test/static_func_test.m b/pkgs/ffigen/test/native_objc_test/static_func_test.m index 5fc3a14d72..f21283d384 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_test.m +++ b/pkgs/ffigen/test/native_objc_test/static_func_test.m @@ -6,12 +6,14 @@ #include "util.h" +void *objc_autoreleasePoolPush(); +void objc_autoreleasePoolPop(void *pool); + @interface StaticFuncTestObj : NSObject { int32_t* counter; } + (instancetype)newWithCounter:(int32_t*) _counter; - (instancetype)initWithCounter:(int32_t*) _counter; -- (void)setCounter:(int32_t*) _counter; - (void)dealloc; @end @@ -34,9 +36,9 @@ IntBlock staticFuncOfBlock(IntBlock a) { return [StaticFuncTestObj newWithCounter: counter]; } -NS_RETURNS_RETAINED StaticFuncTestObj* staticFuncReturnsRetainedArg( +__attribute((ns_returns_retained)) StaticFuncTestObj* staticFuncReturnsRetainedArg( StaticFuncTestObj* a) { - return [a retain]; + return a; } @@ -51,13 +53,7 @@ - (instancetype)initWithCounter:(int32_t*) _counter { return [super init]; } -- (void)setCounter:(int32_t*) _counter { - counter = _counter; - ++*counter; -} - - (void)dealloc { if (counter != nil) --*counter; - [super dealloc]; } @end diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md index c31d85a2e6..6bbfa0e123 100644 --- a/pkgs/objective_c/CHANGELOG.md +++ b/pkgs/objective_c/CHANGELOG.md @@ -3,6 +3,7 @@ - Drop API methods that are deprecated in the oldest versions of iOS and macOS that flutter supports. - Added `ObjCBlock`, which is the new user-facing representation of ObjC blocks. +- Migrate to ARC (Automatic Reference Counting). ## 1.1.0 diff --git a/pkgs/objective_c/example/README.md b/pkgs/objective_c/example/README.md index 1690cef718..ef2deb7f61 100644 --- a/pkgs/objective_c/example/README.md +++ b/pkgs/objective_c/example/README.md @@ -1,3 +1,5 @@ # objective_c example Demonstrates how to use the objective_c plugin. + +TODO(https://github.com/dart-lang/native/issues/1068): Migrate to native assets. diff --git a/pkgs/objective_c/example/ios/Podfile.lock b/pkgs/objective_c/example/ios/Podfile.lock deleted file mode 100644 index 339f20a546..0000000000 --- a/pkgs/objective_c/example/ios/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - Flutter (1.0.0) - - objective_c (0.0.1): - - Flutter - -DEPENDENCIES: - - Flutter (from `Flutter`) - - objective_c (from `.symlinks/plugins/objective_c/ios`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - objective_c: - :path: ".symlinks/plugins/objective_c/ios" - -SPEC CHECKSUMS: - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - objective_c: 77e887b5ba1827970907e10e832eec1683f3431d - -PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 - -COCOAPODS: 1.11.2 diff --git a/pkgs/objective_c/example/ios/Runner/AppDelegate.swift b/pkgs/objective_c/example/ios/Runner/AppDelegate.swift index 9074fee929..626664468b 100644 --- a/pkgs/objective_c/example/ios/Runner/AppDelegate.swift +++ b/pkgs/objective_c/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/pkgs/objective_c/example/macos/Runner.xcodeproj/project.pbxproj b/pkgs/objective_c/example/macos/Runner.xcodeproj/project.pbxproj index f6e37da45f..7fb6863fbd 100644 --- a/pkgs/objective_c/example/macos/Runner.xcodeproj/project.pbxproj +++ b/pkgs/objective_c/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 5C3BF24AE85F5C730B12E1C3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76CD4A3B26D45B1D35ED40BC /* Pods_Runner.framework */; }; + 98677E5D0F050EAF7F207C67 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A9FB1952D96C5AEFCA4D69B /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 253A21F847FB1AF244672D0E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* objective_c_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "objective_c_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* objective_c_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = objective_c_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +79,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 6A9FB1952D96C5AEFCA4D69B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 76CD4A3B26D45B1D35ED40BC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AC25B0021603A0772CA9840F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + E5900647B1760244CA95C2A6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E739196F186306D59FB11177 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + ECD2E4ECDB0FCE6DA971A318 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + EE079268A4FB02480F3FCA89 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 98677E5D0F050EAF7F207C67 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5C3BF24AE85F5C730B12E1C3 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 96BE0B041091E6177B65072F /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 96BE0B041091E6177B65072F /* Pods */ = { + isa = PBXGroup; + children = ( + EE079268A4FB02480F3FCA89 /* Pods-Runner.debug.xcconfig */, + E739196F186306D59FB11177 /* Pods-Runner.release.xcconfig */, + E5900647B1760244CA95C2A6 /* Pods-Runner.profile.xcconfig */, + ECD2E4ECDB0FCE6DA971A318 /* Pods-RunnerTests.debug.xcconfig */, + AC25B0021603A0772CA9840F /* Pods-RunnerTests.release.xcconfig */, + 253A21F847FB1AF244672D0E /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 76CD4A3B26D45B1D35ED40BC /* Pods_Runner.framework */, + 6A9FB1952D96C5AEFCA4D69B /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + DD8A915821E91B2D640F20A1 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + FF16D30B988DB2BCD781BB14 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 774CDE4872C6F2C91B1264E0 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 774CDE4872C6F2C91B1264E0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + DD8A915821E91B2D640F20A1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + FF16D30B988DB2BCD781BB14 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = ECD2E4ECDB0FCE6DA971A318 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = AC25B0021603A0772CA9840F /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 253A21F847FB1AF244672D0E /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/pkgs/objective_c/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/pkgs/objective_c/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16ed..21a3cc14c7 100644 --- a/pkgs/objective_c/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/pkgs/objective_c/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pkgs/objective_c/example/macos/Runner/AppDelegate.swift b/pkgs/objective_c/example/macos/Runner/AppDelegate.swift index d53ef64377..b3c1761412 100644 --- a/pkgs/objective_c/example/macos/Runner/AppDelegate.swift +++ b/pkgs/objective_c/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/pkgs/objective_c/ffigen_c.yaml b/pkgs/objective_c/ffigen_c.yaml index d16df6fe43..cf84356af9 100644 --- a/pkgs/objective_c/ffigen_c.yaml +++ b/pkgs/objective_c/ffigen_c.yaml @@ -17,7 +17,6 @@ functions: - 'object_getClass' - 'sel_registerName' - 'protocol_getMethodDescription' - - '_Block_.*' - 'disposeObjCBlockWithClosure' - 'isValidBlock' - 'isValidObject' @@ -30,6 +29,7 @@ functions: 'objc_getClass': 'getClass' 'objc_retain': 'objectRetain' 'objc_release': 'objectRelease' + 'objc_retainBlock': 'blockRetain' 'objc_msgSend': 'msgSend' 'objc_msgSend_fpret': 'msgSendFpret' 'objc_msgSend_stret': 'msgSendStret' @@ -37,8 +37,6 @@ functions: 'objc_copyClassList': 'copyClassList' 'objc_getProtocol': 'getProtocol' 'protocol_getMethodDescription': 'getMethodDescription' - '_Block_copy': 'blockCopy' - '_Block_release': 'blockRelease' globals: include: - '_NSConcrete.*Block' diff --git a/pkgs/objective_c/ios/objective_c.podspec b/pkgs/objective_c/ios/objective_c.podspec index c9d3ae86b6..e784ccd7c0 100644 --- a/pkgs/objective_c/ios/objective_c.podspec +++ b/pkgs/objective_c/ios/objective_c.podspec @@ -21,7 +21,6 @@ A library to access Objective C from Flutter that acts as a support library for s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '12.0' - s.requires_arc = [] # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/pkgs/objective_c/lib/objective_c.dart b/pkgs/objective_c/lib/objective_c.dart index d2be3536b0..1d23ace557 100644 --- a/pkgs/objective_c/lib/objective_c.dart +++ b/pkgs/objective_c/lib/objective_c.dart @@ -8,8 +8,7 @@ export 'src/c_bindings_generated.dart' ObjCBlockImpl, ObjCObject, ObjCSelector, - blockCopy, - blockRelease, + blockRetain, objectRelease, objectRetain; export 'src/internal.dart' diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart index 03a918e4ee..3ec728a4e1 100644 --- a/pkgs/objective_c/lib/src/c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart @@ -54,6 +54,12 @@ external ffi.Pointer objectRetain( ffi.Pointer object, ); +@ffi.Native Function(ffi.Pointer)>( + symbol: "objc_retainBlock", isLeaf: true) +external ffi.Pointer blockRetain( + ffi.Pointer object, +); + @ffi.Native)>( symbol: "objc_release", isLeaf: true) external void objectRelease( @@ -104,18 +110,6 @@ external ffi.Array> NSConcreteFinalizingBlock; @ffi.Native>>(symbol: "_NSConcreteGlobalBlock") external ffi.Array> NSConcreteGlobalBlock; -@ffi.Native Function(ffi.Pointer)>( - symbol: "_Block_copy", isLeaf: true) -external ffi.Pointer blockCopy( - ffi.Pointer object, -); - -@ffi.Native)>( - symbol: "_Block_release", isLeaf: true) -external void blockRelease( - ffi.Pointer object, -); - @ffi.Native Function(ffi.Pointer)>( symbol: "objc_getProtocol", isLeaf: true) external ffi.Pointer getProtocol( diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart index 56fe7ed10c..88dbf160b6 100644 --- a/pkgs/objective_c/lib/src/internal.dart +++ b/pkgs/objective_c/lib/src/internal.dart @@ -190,8 +190,9 @@ class ObjCBlockBase extends _ObjCFinalizable { ObjCBlockBase(super.ptr, {required super.retain, required super.release}); static final _blockFinalizer = NativeFinalizer( - Native.addressOf)>>( - c.blockRelease)); + Native.addressOf)>>( + c.objectRelease) + .cast()); @override NativeFinalizer get _finalizer => _blockFinalizer; @@ -199,13 +200,13 @@ class ObjCBlockBase extends _ObjCFinalizable { @override void _retain(Pointer ptr) { assert(c.isValidBlock(ptr)); - c.blockCopy(ptr.cast()); + c.blockRetain(ptr.cast()); } @override void _release(Pointer ptr) { assert(c.isValidBlock(ptr)); - c.blockRelease(ptr.cast()); + c.objectRelease(ptr.cast()); } } @@ -238,7 +239,7 @@ Pointer _newBlock(Pointer invoke, Pointer target, b.ref.dispose_port = disposePort; b.ref.descriptor = descriptor; assert(c.isValidBlock(b)); - final copy = c.blockCopy(b.cast()).cast(); + final copy = c.blockRetain(b.cast()).cast(); calloc.free(b); assert(copy.ref.isa == Native.addressOf>>(c.NSConcreteMallocBlock).cast()); diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index fb8f8a60d2..9a8c9c6da6 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -14028,11 +14028,6 @@ class DartProxyBuilder extends NSObject { return DartProxyBuilder.castFromPointer(_ret, retain: true, release: true); } - /// dealloc - void dealloc() { - _objc_msgSend_1(this.pointer, _sel_dealloc); - } - /// implementMethod:withSignature:andBlock: void implementMethod_withSignature_andBlock_( ffi.Pointer sel, @@ -14127,11 +14122,6 @@ class DartProxy extends NSProxy { return DartProxy.castFromPointer(_ret, retain: true, release: true); } - /// dealloc - void dealloc() { - _objc_msgSend_1(this.pointer, _sel_dealloc); - } - /// respondsToSelector: static bool respondsToSelector_(ffi.Pointer aSelector) { return _objc_msgSend_4( diff --git a/pkgs/objective_c/macos/objective_c.podspec b/pkgs/objective_c/macos/objective_c.podspec index 803e834093..f3ae43b6b3 100644 --- a/pkgs/objective_c/macos/objective_c.podspec +++ b/pkgs/objective_c/macos/objective_c.podspec @@ -20,7 +20,6 @@ A library to access Objective C from Flutter that acts as a support library for s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.requires_arc = [] s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/pkgs/objective_c/src/objective_c_runtime.h b/pkgs/objective_c/src/objective_c_runtime.h index a3db332f1e..aecb1e63bd 100644 --- a/pkgs/objective_c/src/objective_c_runtime.h +++ b/pkgs/objective_c/src/objective_c_runtime.h @@ -18,6 +18,7 @@ typedef struct _ObjCProtocol ObjCProtocol; ObjCSelector *sel_registerName(const char *name); ObjCObject *objc_getClass(const char *name); ObjCObject *objc_retain(ObjCObject *object); +ObjCObject *objc_retainBlock(const ObjCObject *object); void objc_release(ObjCObject *object); ObjCObject *object_getClass(ObjCObject *object); ObjCObject** objc_copyClassList(unsigned int* count); @@ -57,9 +58,6 @@ extern void *_NSConcreteFinalizingBlock[32]; extern void *_NSConcreteGlobalBlock[32]; extern void *_NSConcreteWeakBlockVariable[32]; -void *_Block_copy(const void *object); -void _Block_release(const void *object); - typedef struct _ObjCMethodDesc { ObjCSelector* name; const char* types; diff --git a/pkgs/objective_c/src/proxy.h b/pkgs/objective_c/src/proxy.h index af80036d25..adb2955bee 100644 --- a/pkgs/objective_c/src/proxy.h +++ b/pkgs/objective_c/src/proxy.h @@ -10,19 +10,17 @@ @interface DartProxyBuilder : NSObject + (instancetype)new; - (instancetype)init; -- (void)dealloc; - (void)implementMethod:(SEL) sel - withSignature:(NSMethodSignature *)signature - andBlock:(void *)block; + withSignature:(__strong NSMethodSignature *)signature + andBlock:(void*)block; @end @interface DartProxy : NSProxy -+ (instancetype)newFromBuilder:(DartProxyBuilder*)builder; -- (instancetype)initFromBuilder:(DartProxyBuilder*)builder; -- (void)dealloc; ++ (instancetype)newFromBuilder:(__strong DartProxyBuilder*)builder; +- (instancetype)initFromBuilder:(__strong DartProxyBuilder*)builder; - (BOOL)respondsToSelector:(SEL)sel; - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; -- (void)forwardInvocation:(NSInvocation *)invocation; +- (void)forwardInvocation:(__strong NSInvocation *)invocation; @end #endif // OBJECTIVE_C_SRC_PROXY_H_ diff --git a/pkgs/objective_c/src/proxy.m b/pkgs/objective_c/src/proxy.m index 4c0a934b68..570ccfa20f 100644 --- a/pkgs/objective_c/src/proxy.m +++ b/pkgs/objective_c/src/proxy.m @@ -9,18 +9,15 @@ #import #import +#if !__has_feature(objc_arc) +#error "This file must be compiled with ARC enabled" +#endif + @interface ProxyMethod : NSObject @property(strong) NSMethodSignature *signature; @property(strong) id block; -- (void)dealloc; @end - @implementation ProxyMethod -- (void)dealloc { - self.signature = nil; - self.block = nil; - [super dealloc]; -} @end @implementation DartProxyBuilder { @@ -38,11 +35,6 @@ - (instancetype)init { return self; } -- (void)dealloc { - [methods release]; - [super dealloc]; -} - - (void)implement:(SEL)sel withMethod:(ProxyMethod*)m { @synchronized(methods) { [methods setObject:m forKey:[NSValue valueWithPointer:sel]]; @@ -51,15 +43,14 @@ - (void)implement:(SEL)sel withMethod:(ProxyMethod*)m { - (void)implementMethod:(SEL)sel withSignature:(NSMethodSignature *)signature - andBlock:(void *)block { + andBlock:(void*)block { ProxyMethod *m = [ProxyMethod new]; m.signature = signature; - m.block = block; + m.block = (__bridge id)block; [self implement:sel withMethod:m]; - [m release]; } -- (NSDictionary*)copyMethods { +- (NSDictionary*)copyMethods NS_RETURNS_RETAINED { return [methods copy]; } @end @@ -83,11 +74,6 @@ - (instancetype)initFromBuilder:(DartProxyBuilder*)builder { return self; } -- (void)dealloc { - [methods release]; - [super dealloc]; -} - - (BOOL)respondsToSelector:(SEL)sel { return [self getMethod:sel] != nil; } diff --git a/pkgs/objective_c/test/setup.dart b/pkgs/objective_c/test/setup.dart index eccddb219e..c59f4619c1 100644 --- a/pkgs/objective_c/test/setup.dart +++ b/pkgs/objective_c/test/setup.dart @@ -14,7 +14,13 @@ import 'dart:io'; const cFiles = ['src/objective_c.c', 'src/include/dart_api_dl.c']; const objCFiles = ['src/proxy.m']; -const objCFlags = ['-x', 'objective-c', '-framework', 'Foundation']; +const objCFlags = [ + '-x', + 'objective-c', + '-fobjc-arc', + '-framework', + 'Foundation' +]; const outputFile = 'test/objective_c.dylib'; void _runClang(List flags, String output) { @@ -37,7 +43,7 @@ void _runClang(List flags, String output) { String _buildObject(String input, List flags) { final output = '$input.o'; - _runClang(['-c', input, '-fpic', '-I', 'src', ...flags], output); + _runClang([...flags, '-c', input, '-fpic', '-I', 'src'], output); return output; } diff --git a/pkgs/swift2objc/test/integration/temp/.gitignore b/pkgs/swift2objc/test/integration/temp/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/pkgs/swift2objc/test/integration/temp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore