Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

objective_c throws '_isValidObject(ptr)': is not true #1340

Closed
yanshouwang opened this issue Jul 15, 2024 · 7 comments
Closed

objective_c throws '_isValidObject(ptr)': is not true #1340

yanshouwang opened this issue Jul 15, 2024 · 7 comments
Assignees

Comments

@yanshouwang
Copy link

I have written a method with Swift and use ffigen to generate the dart bindings.

When I call the metod from Dart, the method throws an error like this.

[log] 'package:objective_c/src/internal.dart': Failed assertion: line 113 pos 12: '_isValidObject(ptr)': is not true.
[log] #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
      #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
      #2      ObjCObjectBase._retain (package:objective_c/src/internal.dart:113:12)
      #3      new _ObjCFinalizable (package:objective_c/src/internal.dart:54:7)
      #4      new ObjCObjectBase (package:objective_c/src/internal.dart)
      #5      new NSObject._ (package:objective_c/src/objective_c_bindings_generated.dart:25:9)
      #6      new NSObject.castFromPointer (package:objective_c/src/objective_c_bindings_generated.dart:34:14)
      #7      new NSString._ (package:objective_c/src/objective_c_bindings_generated.dart:506:15)
      #8      new NSString.castFromPointer (package:objective_c/src/objective_c_bindings_generated.dart:515:14)
      #9      new ObjCBlock_ffiVoid_NSString_NSError.listener.<anonymous closure> (package:camerax_ios/src/ffi.g.dart:37907:58)
      #10     _ObjCBlock_ffiVoid_NSString_NSError_closureTrampoline (package:camerax_ios/src/ffi.g.dart:37825:39)
      #11     new ObjCBlock_ffiVoid_NSString_NSError.listener.<anonymous closure> (package:camerax_ios/src/ffi.g.dart)
      #12     _rootRunUnary (dart:async/zone.dart:1415:13)
      #13     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
      #14     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
      #15     _CustomZone.bindUnaryCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1254:26)
      #16     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

Here is the Swift method.

    @objc public func takePictureToAlbum(name: String?, completionHandler handler: @escaping (String?, (any Error)?) -> Void) {
        requestPhotoLibraryAuthorization() { [self] status in
            if status == .authorized {
                let settings = AVCapturePhotoSettings(from: capturePhotoSettings)
                let delegate = CameraCapturePhotoCaptureDelegate() { [self] photo, error in
                    if let error {
                        handler(nil, error)
                    } else {
                        savePhoto(photo, name: name, completionHandler: handler)
                    }
                }
                capturePhotoDelegates.append(delegate)
                capturePhotoOutput.capturePhoto(with: settings, delegate: delegate)
            } else {
                handler(nil, CameraError.unauthorized)
            }
        }
    }

Here is the Dart caller.

  @override
  Future<Uri> takePictureToAlbum({String? name}) async {
    final completer = Completer<Uri>();
    final handler =
        ffi.ObjCBlock_ffiVoid_NSString_NSError.listener((savedPath, error) {
      if (error == null) {
        final savedUri = Uri.file('$savedPath');
        completer.complete(savedUri);
      } else {
        completer.completeError(error);
      }
    });
    ffiValue.takePictureToAlbumWithName_completionHandler_(
      name?.toNSString(),
      handler,
    );
    final savedUri = await completer.future;
    return savedUri;
  }
@liamappelbe
Copy link
Contributor

I'm guessing it's the completion handler that throws the error. There are a few places where the handler is being called in your swift function. Do you know which code path you're hitting? I can't tell much from the Dart stack trace.

Anyway, that assertion is designed to catch cases where we try to use an ObjC object in Dart when it's already been freed. This used to be a big issue for listener blocks, because they are called asynchronously, so their arguments may be freed before the callback is run. That bug is fixed now, but the fix hasn't been published yet.

Which version of ffigen are you using?

@yanshouwang
Copy link
Author

I'm guessing it's the completion handler that throws the error. There are a few places where the handler is being called in your swift function. Do you know which code path you're hitting? I can't tell much from the Dart stack trace.

Anyway, that assertion is designed to catch cases where we try to use an ObjC object in Dart when it's already been freed. This used to be a big issue for listener blocks, because they are called asynchronously, so their arguments may be freed before the callback is run. That bug is fixed now, but the fix hasn't been published yet.

Which version of ffigen are you using?

Here is the savePhoto method which I passed the completion handler.

    private func savePhoto(_ photo: AVCapturePhoto, name: String?, completionHandler handler: @escaping (String?, (any Error)?) -> Void) {
        do {
            if let data = photo.fileDataRepresentation() {
                let fileManager = FileManager.default
                guard let documentUrl = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
                    throw CameraError.saveUrlNil
                }
                let photosUrl = documentUrl.appendingPathComponent("Photos")
                if !fileManager.fileExists(atPath: photosUrl.path) {
                    try fileManager.createDirectory(at: photosUrl, withIntermediateDirectories: true)
                }
                let dataUri = photosUrl.appendingPathComponent(UUID().uuidString).appendingPathExtension("JPG")
                try data.write(to: dataUri, options: .atomic)
                debugPrint("saved uri \(dataUri.path)")
                PHPhotoLibrary.shared().performChanges() {
                    let creationRequest = PHAssetCreationRequest.forAsset()
                    let options = PHAssetResourceCreationOptions()
                    options.originalFilename = name
                    creationRequest.addResource(with: .photo, data: data, options: options)
                } completionHandler: { success, error in
                    if let error {
                        handler(nil, error)
                    } else {
                        handler(dataUri.path, nil)
                    }
                }
            } else {
                throw CameraError.savePhotoNil
            }
        } catch {
            handler(nil, error)
        }
    }

I'm using ffigen: 12.0.0

@liamappelbe
Copy link
Contributor

Ok, then this is a dupe of #835. if you're on ffigen 12, you don't have the fix yet. I'm planning to publish ffigen 13 later this week, so just wait for that (or add it as a git dependency if you want to try the fix now).

The fix involves generated ObjC code, in addition to the generated Dart code. So you'll need to include the generated ObjC file in your app/plugin build.

@github-project-automation github-project-automation bot moved this from Todo to Done in ObjC/Swift interop Jul 16, 2024
@yanshouwang
Copy link
Author

Sometime it causes a breakpoint with debug mode.

截屏2024-07-16 09 00 03

@liamappelbe
Copy link
Contributor

ffigen 13.0.0 is published

@yanshouwang
Copy link
Author

yanshouwang commented Jul 19, 2024

ffigen 13.0.0 is published

Should I change the plugin to plugin_ffi and include the generated .m file to CMakeList?

Or just add the .m file to the Classes folder?

@liamappelbe
Copy link
Contributor

Should I change the plugin to plugin_ffi and include the generated .m file to CMakeList?

Or just add the .m file to the Classes folder?

Either should work. You just have to include the .m file in the build somehow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

No branches or pull requests

2 participants