-
Notifications
You must be signed in to change notification settings - Fork 68
Use NSProxy to implement ObjC protocols #1144
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
Conversation
PR HealthCoverage
|
File | Coverage |
---|---|
pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart | 💔 Not covered |
pkgs/objective_c/lib/objective_c.dart | 💔 Not covered |
pkgs/objective_c/lib/src/c_bindings_generated.dart | 💔 Not covered |
pkgs/objective_c/lib/src/internal.dart | 💔 Not covered |
pkgs/objective_c/lib/src/objective_c_bindings_generated.dart | 💔 Not covered |
This check for test coverage is informational (issues shown here will not fail the PR).
This check can be disabled by tagging the PR with skip-coverage-check
.
License Headers ✔️
Details
// 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.
Files |
---|
no missing headers |
All source files should start with a license header.
Unrelated files missing license headers
Files |
---|
pkgs/ffi/example/main.dart |
pkgs/ffigen/example/libclang-example/generated_bindings.dart |
pkgs/ffigen/example/shared_bindings/generate.dart |
pkgs/ffigen/example/shared_bindings/lib/generated/a_gen.dart |
pkgs/ffigen/example/shared_bindings/lib/generated/a_shared_b_gen.dart |
pkgs/ffigen/example/shared_bindings/lib/generated/base_gen.dart |
pkgs/ffigen/example/simple/generated_bindings.dart |
pkgs/ffigen/lib/src/config_provider/config_spec.dart |
pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart |
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_decl_decl_collision_bindings.dart |
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_decl_symbol_address_collision_bindings.dart |
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_decl_type_name_collision_bindings.dart |
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_reserved_keyword_collision_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_comment_markup_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_dart_handle_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_forward_decl_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_functions_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_imported_types_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_native_func_typedef_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_regress_384_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_struct_fptr_fields_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_typedef_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart |
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart |
pkgs/ffigen/test/large_integration_tests/_expected_cjson_bindings.dart |
pkgs/ffigen/test/large_integration_tests/_expected_libclang_bindings.dart |
pkgs/ffigen/test/large_integration_tests/_expected_sqlite_bindings.dart |
pkgs/ffigen/test/native_test/_expected_native_test_bindings.dart |
pkgs/jni/lib/src/lang/jcharacter.dart |
pkgs/jni/lib/src/third_party/generated_bindings.dart |
pkgs/jni/lib/src/third_party/global_env_extensions.dart |
pkgs/jni/lib/src/third_party/jni_bindings_generated.dart |
pkgs/jnigen/android_test_runner/lib/main.dart |
pkgs/jnigen/example/in_app_java/lib/android_utils.dart |
pkgs/jnigen/example/kotlin_plugin/example/lib/main.dart |
pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart |
pkgs/jnigen/example/kotlin_plugin/lib/kotlin_plugin.dart |
pkgs/jnigen/example/pdfbox_plugin/lib/pdfbox_plugin.dart |
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart |
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart |
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/_package.dart |
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart |
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/_package.dart |
pkgs/jnigen/lib/src/bindings/descriptor.dart |
pkgs/jnigen/lib/src/elements/elements.g.dart |
pkgs/jnigen/test/jackson_core_test/third_party/bindings/com/fasterxml/jackson/core/_package.dart |
pkgs/jnigen/tool/command_runner.dart |
pkgs/native_assets_cli/test/model/checksum_test.dart |
Package publish validation ✔️
Details
Package | Version | Status |
---|---|---|
package:ffi | 2.1.2 | already published at pub.dev |
package:ffigen | 13.0.0-wip | WIP (no publish necessary) |
package:jni | 0.9.3-wip | WIP (no publish necessary) |
package:jnigen | 0.9.1 | already published at pub.dev |
package:native_assets_builder | 0.7.0 | already published at pub.dev |
package:native_assets_cli | 0.6.0 | already published at pub.dev |
package:native_toolchain_c | 0.4.2 | already published at pub.dev |
package:objective_c | 1.1.0-wip | WIP (no publish necessary) |
package:swiftgen | 0.0.1-wip | WIP (no publish necessary) |
Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
Let me see if I understand the high level picture:
I think it might be useful to add the code gen to this PR (or stack a PR on top of this), and look at some example code users would write with the code-genned code to ensure we don't accidentally create extra cyclic references. |
Yep, that's all accurate.
Ok. I'll start working on that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm based on pkgs/ffigen/test/native_objc_test/protocol_test.dart
I'm thinking that there are likely going to be changes to some of this code once code gen is built on top and we know better what we want/need. But I'm fine landing this as some lower layer infra that's tested by itself. Feel free to land, or layer the code-gen PR on top and re-request review once the code-gen PR is ready.
pkgs/objective_c/CHANGELOG.md
Outdated
@@ -1,3 +1,8 @@ | |||
## 1.1.0-wip | |||
|
|||
- Add `DartProxy`, which is an implementation of `NSProxy` that allows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allows -> enables
(access control versus technically possible)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
isRequired: true, isInstance: true); | ||
final block = DartInstanceMethodBlock.fromFunction( | ||
(Pointer<Void> p, NSString s, double x) { | ||
return 'DartProxy: $s: $x'.toNSString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use p
? Or what is the p
for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the method selector. I'll hide it in the code gen layer. The only way to hide it from this layer would be option 4 from the doc, which has technical issues.
final otherSignature = getProtocolMethodSignature(secondProto, otherSel, | ||
isRequired: true, isInstance: true); | ||
final otherBlock = DartOtherMethodBlock.fromFunction( | ||
(Pointer<Void> p, int a, int b, int c, int d) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, what is the p
for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to land this one. We're still working out the details of the code gen layer, but none of what we're discussing there will affect this layer.
isRequired: true, isInstance: true); | ||
final block = DartInstanceMethodBlock.fromFunction( | ||
(Pointer<Void> p, NSString s, double x) { | ||
return 'DartProxy: $s: $x'.toNSString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the method selector. I'll hide it in the code gen layer. The only way to hide it from this layer would be option 4 from the doc, which has technical issues.
pkgs/objective_c/CHANGELOG.md
Outdated
@@ -1,3 +1,8 @@ | |||
## 1.1.0-wip | |||
|
|||
- Add `DartProxy`, which is an implementation of `NSProxy` that allows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
This is half the solution to protocol implementation. It adds a user visible class in package:objective_c called
DartProxy
, which is based onNSProxy
. It stores a map from selector to block, so users can implement methods using blocks.DartProxy
objects are built usingDartProxyBuilder
, which is just a helper to build that map. There are no changes to ffigen code generation in this PR.The other half of the solution will be code-gen for protocols that will make things more ergonomic, by hiding the details of
DartProxy
. It may be the case that some advanced use cases (eg implementing multiple protocols) may still need to useDartProxy
, but I can still make it a bit more ergonomic than it is inprotocol_test.dart
once that code-gen lands.Overview:
SEL
(aka selector) represents the name of a method. It does not encode the arg/return types. It's essentially a canonicalized string. We create it usingsel_registerName
.NSMethodSignature
represents the type of a method. It doesn't include the name. We can construct it from aconst char*
that encodes the arg/return types. We don't actually have to implement these signature encoding rules though, because we can doprotocol_getMethodDescription(proto, selector,...).types
.NSInvocation
represents a single method call. It contains aSEL
, aNSMethodSignature
, a target object (which we bypass usinginvokeWithTarget
), and a list of arguments. It also has space for the result.NSProxy
is a class that lets us respond to arbitrary methods. It checks whether we implement a method usingrespondsToSelector
, checks the signature usingmethodSignatureForSelector
, and then invokes the method usingforwardInvocation
.DartProxy
is our implementation ofNSProxy
, and has a map fromSEL
to(NSMethodSignature, user's block)
. SinceSEL
s are already canonicalzed, we can use these pointers directly as map keys.DartProxyBuilder
is used to buildDartProxy
s. It has the same sort of map asDartProxy
, but it's mutable. When constructing aDartProxy
, we make an immutable (shallow) copy of that map, so thatDartProxy
objects can be accessed from arbitrary threads without worrying about synchronization.selector
selecting which method is invoked, it's passed as an ordinary argument.NSInvocation
args for an ordinary object look like[target, selector, first arg, second arg...]
, whereas for a block they look like[block, first arg, second arg...]
.DartProxy.forwardInvocation
we could construct a newNSInvocation
and copy across all the args except theselector
, or we could just write blocks that take theSEL
as an extra arg that they ignore. I went with the second option.#1040