Skip to content

Commit 0da80be

Browse files
[pigeon] Swift implementation for ProxyApis (#6602)
Swift portion of flutter/flutter#134777
1 parent 869a3de commit 0da80be

24 files changed

+13250
-73
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 22.5.0
2+
3+
* [swift] Adds implementation for `@ProxyApi`.
4+
15
## 22.4.2
26

37
* Updates `README.md` to replace the deprecated `flutter pub run pigeon` command with `dart run pigeon`.

packages/pigeon/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ This is what the temporary generated code that the _PigeonIsolate_ executes
6262
looks like (see [State Diagram](#state-diagram)):
6363

6464
```dart
65-
import 'path/to/supplied/pigeon/file.dart'
65+
import 'path/to/supplied/pigeon/file.dart';
6666
import 'dart:io';
6767
import 'dart:isolate';
6868
import 'package:pigeon/pigeon_lib.dart';

packages/pigeon/lib/ast.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
88
import 'generator_tools.dart';
99
import 'kotlin_generator.dart' show KotlinProxyApiOptions;
1010
import 'pigeon_lib.dart';
11+
import 'swift_generator.dart' show SwiftProxyApiOptions;
1112

1213
typedef _ListEquals = bool Function(List<Object?>, List<Object?>);
1314

@@ -142,6 +143,7 @@ class AstProxyApi extends Api {
142143
required this.fields,
143144
this.superClass,
144145
this.interfaces = const <TypeDeclaration>{},
146+
this.swiftOptions,
145147
this.kotlinOptions,
146148
});
147149

@@ -157,6 +159,10 @@ class AstProxyApi extends Api {
157159
/// Name of the classes this class considers to be implemented.
158160
Set<TypeDeclaration> interfaces;
159161

162+
/// Options that control how Swift code will be generated for a specific
163+
/// ProxyApi.
164+
final SwiftProxyApiOptions? swiftOptions;
165+
160166
/// Options that control how Kotlin code will be generated for a specific
161167
/// ProxyApi.
162168
final KotlinProxyApiOptions? kotlinOptions;

packages/pigeon/lib/generator_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'ast.dart';
1414
/// The current version of pigeon.
1515
///
1616
/// This must match the version in pubspec.yaml.
17-
const String pigeonVersion = '22.4.2';
17+
const String pigeonVersion = '22.5.0';
1818

1919
/// Read all the content from [stdin] to a String.
2020
String readStdin() {

packages/pigeon/lib/pigeon.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ export 'java_generator.dart' show JavaOptions;
1111
export 'kotlin_generator.dart' show KotlinOptions, KotlinProxyApiOptions;
1212
export 'objc_generator.dart' show ObjcOptions;
1313
export 'pigeon_lib.dart';
14-
export 'swift_generator.dart' show SwiftOptions;
14+
export 'swift_generator.dart' show SwiftOptions, SwiftProxyApiOptions;

packages/pigeon/lib/pigeon_lib.dart

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'package:analyzer/error/error.dart' show AnalysisError;
2323
import 'package:args/args.dart';
2424
import 'package:collection/collection.dart' as collection;
2525
import 'package:path/path.dart' as path;
26+
import 'package:pub_semver/pub_semver.dart';
2627

2728
import 'ast.dart';
2829
import 'ast_generator.dart';
@@ -139,7 +140,7 @@ class FlutterApi {
139140
/// methods.
140141
class ProxyApi {
141142
/// Parametric constructor for [ProxyApi].
142-
const ProxyApi({this.superClass, this.kotlinOptions});
143+
const ProxyApi({this.superClass, this.kotlinOptions, this.swiftOptions});
143144

144145
/// The proxy api that is a super class to this one.
145146
///
@@ -150,6 +151,10 @@ class ProxyApi {
150151
/// with inherited method names.
151152
final Type? superClass;
152153

154+
/// Options that control how Swift code will be generated for a specific
155+
/// ProxyApi.
156+
final SwiftProxyApiOptions? swiftOptions;
157+
153158
/// Options that control how Kotlin code will be generated for a specific
154159
/// ProxyApi.
155160
final KotlinProxyApiOptions? kotlinOptions;
@@ -1674,6 +1679,40 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
16741679
}
16751680
}
16761681

1682+
SwiftProxyApiOptions? swiftOptions;
1683+
final Map<String, Object?>? swiftOptionsMap =
1684+
annotationMap['swiftOptions'] as Map<String, Object?>?;
1685+
if (swiftOptionsMap != null) {
1686+
swiftOptions = SwiftProxyApiOptions(
1687+
name: swiftOptionsMap['name'] as String?,
1688+
import: swiftOptionsMap['import'] as String?,
1689+
minIosApi: swiftOptionsMap['minIosApi'] as String?,
1690+
minMacosApi: swiftOptionsMap['minMacosApi'] as String?,
1691+
supportsIos: swiftOptionsMap['supportsIos'] as bool? ?? true,
1692+
supportsMacos: swiftOptionsMap['supportsMacos'] as bool? ?? true,
1693+
);
1694+
}
1695+
1696+
void tryParseApiRequirement(String? version) {
1697+
if (version == null) {
1698+
return;
1699+
}
1700+
try {
1701+
Version.parse(version);
1702+
} on FormatException catch (error) {
1703+
_errors.add(
1704+
Error(
1705+
message:
1706+
'Could not parse version: ${error.message}. Please use semantic versioning format: "1.2.3".',
1707+
lineNumber: _calculateLineNumber(source, node.offset),
1708+
),
1709+
);
1710+
}
1711+
}
1712+
1713+
tryParseApiRequirement(swiftOptions?.minIosApi);
1714+
tryParseApiRequirement(swiftOptions?.minMacosApi);
1715+
16771716
KotlinProxyApiOptions? kotlinOptions;
16781717
final Map<String, Object?>? kotlinOptionsMap =
16791718
annotationMap['kotlinOptions'] as Map<String, Object?>?;
@@ -1691,6 +1730,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
16911730
fields: <ApiField>[],
16921731
superClass: superClass,
16931732
interfaces: interfaces,
1733+
swiftOptions: swiftOptions,
16941734
kotlinOptions: kotlinOptions,
16951735
documentationComments:
16961736
_documentationCommentsParser(node.documentationComment?.tokens),
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../generator_tools.dart';
6+
import '../pigeon.dart';
7+
8+
/// Name of delegate that handles the callback when an object is deallocated
9+
/// in an `InstanceManager`.
10+
String instanceManagerFinalizerDelegateName(SwiftOptions options) =>
11+
'${_instanceManagerFinalizerName(options)}Delegate';
12+
13+
/// The name of the registrar containing all the ProxyApi implementations.
14+
String proxyApiRegistrarName(SwiftOptions options) =>
15+
'${options.fileSpecificClassNameComponent ?? ''}${proxyApiClassNamePrefix}ProxyApiRegistrar';
16+
17+
/// The name of the `ReaderWriter` that handles ProxyApis.
18+
String proxyApiReaderWriterName(SwiftOptions options) =>
19+
'${options.fileSpecificClassNameComponent ?? ''}${classNamePrefix}ProxyApiCodecReaderWriter';
20+
21+
/// Name of the Swift `InstanceManager`.
22+
String swiftInstanceManagerClassName(SwiftOptions options) =>
23+
'${options.fileSpecificClassNameComponent ?? ''}${proxyApiClassNamePrefix}InstanceManager';
24+
25+
/// Template for delegate with callback when an object is deallocated.
26+
String instanceManagerFinalizerDelegateTemplate(SwiftOptions options) => '''
27+
/// Handles the callback when an object is deallocated.
28+
protocol ${instanceManagerFinalizerDelegateName(options)}: AnyObject {
29+
/// Invoked when the strong reference of an object is deallocated in an `InstanceManager`.
30+
func onDeinit(identifier: Int64)
31+
}
32+
33+
''';
34+
35+
/// Template for an object that tracks when an object is deallocated.
36+
String instanceManagerFinalizerTemplate(SwiftOptions options) => '''
37+
// Attaches to an object to receive a callback when the object is deallocated.
38+
internal final class ${_instanceManagerFinalizerName(options)} {
39+
private static let associatedObjectKey = malloc(1)!
40+
41+
private let identifier: Int64
42+
// Reference to the delegate is weak because the callback should be ignored if the
43+
// `InstanceManager` is deallocated.
44+
private weak var delegate: ${instanceManagerFinalizerDelegateName(options)}?
45+
46+
private init(identifier: Int64, delegate: ${instanceManagerFinalizerDelegateName(options)}) {
47+
self.identifier = identifier
48+
self.delegate = delegate
49+
}
50+
51+
internal static func attach(
52+
to instance: AnyObject, identifier: Int64, delegate: ${instanceManagerFinalizerDelegateName(options)}
53+
) {
54+
let finalizer = ${_instanceManagerFinalizerName(options)}(identifier: identifier, delegate: delegate)
55+
objc_setAssociatedObject(instance, associatedObjectKey, finalizer, .OBJC_ASSOCIATION_RETAIN)
56+
}
57+
58+
static func detach(from instance: AnyObject) {
59+
objc_setAssociatedObject(instance, associatedObjectKey, nil, .OBJC_ASSOCIATION_ASSIGN)
60+
}
61+
62+
deinit {
63+
delegate?.onDeinit(identifier: identifier)
64+
}
65+
}
66+
67+
''';
68+
69+
/// The Swift `InstanceManager`.
70+
String instanceManagerTemplate(SwiftOptions options) {
71+
return '''
72+
/// Maintains instances used to communicate with the corresponding objects in Dart.
73+
///
74+
/// Objects stored in this container are represented by an object in Dart that is also stored in
75+
/// an InstanceManager with the same identifier.
76+
///
77+
/// When an instance is added with an identifier, either can be used to retrieve the other.
78+
///
79+
/// Added instances are added as a weak reference and a strong reference. When the strong
80+
/// reference is removed and the weak reference is deallocated,`${instanceManagerFinalizerDelegateName(options)}.onDeinit`
81+
/// is called with the instance's identifier. However, if the strong reference is removed and then the identifier is
82+
/// retrieved with the intention to pass the identifier to Dart (e.g. by calling `identifierWithStrongReference`),
83+
/// the strong reference to the instance is re-added. The strong reference will then need to be removed manually
84+
/// again.
85+
///
86+
/// Accessing and inserting to an InstanceManager is thread safe.
87+
final class ${swiftInstanceManagerClassName(options)} {
88+
// Identifiers are locked to a specific range to avoid collisions with objects
89+
// created simultaneously from Dart.
90+
// Host uses identifiers >= 2^16 and Dart is expected to use values n where,
91+
// 0 <= n < 2^16.
92+
private static let minHostCreatedIdentifier: Int64 = 65536
93+
94+
private let lockQueue = DispatchQueue(label: "${swiftInstanceManagerClassName(options)}")
95+
private let identifiers: NSMapTable<AnyObject, NSNumber> = NSMapTable(
96+
keyOptions: [.weakMemory, .objectPointerPersonality], valueOptions: .strongMemory)
97+
private let weakInstances: NSMapTable<NSNumber, AnyObject> = NSMapTable(
98+
keyOptions: .strongMemory, valueOptions: [.weakMemory, .objectPointerPersonality])
99+
private let strongInstances: NSMapTable<NSNumber, AnyObject> = NSMapTable(
100+
keyOptions: .strongMemory, valueOptions: [.strongMemory, .objectPointerPersonality])
101+
private let finalizerDelegate: ${instanceManagerFinalizerDelegateName(options)}
102+
private var nextIdentifier: Int64 = minHostCreatedIdentifier
103+
104+
public init(finalizerDelegate: ${instanceManagerFinalizerDelegateName(options)}) {
105+
self.finalizerDelegate = finalizerDelegate
106+
}
107+
108+
/// Adds a new instance that was instantiated from Dart.
109+
///
110+
/// The same instance can be added multiple times, but each identifier must be unique. This allows
111+
/// two objects that are equivalent (e.g. conforms to `Equatable`) to both be added.
112+
///
113+
/// - Parameters:
114+
/// - instance: the instance to be stored
115+
/// - identifier: the identifier to be paired with instance. This value must be >= 0 and unique
116+
func addDartCreatedInstance(_ instance: AnyObject, withIdentifier identifier: Int64) {
117+
lockQueue.async {
118+
self.addInstance(instance, withIdentifier: identifier)
119+
}
120+
}
121+
122+
/// Adds a new instance that was instantiated from the host platform.
123+
///
124+
/// - Parameters:
125+
/// - instance: the instance to be stored. This must be unique to all other added instances.
126+
/// - Returns: the unique identifier (>= 0) stored with instance
127+
func addHostCreatedInstance(_ instance: AnyObject) -> Int64 {
128+
assert(!containsInstance(instance), "Instance of \\(instance) has already been added.")
129+
var identifier: Int64 = -1
130+
lockQueue.sync {
131+
identifier = nextIdentifier
132+
nextIdentifier += 1
133+
self.addInstance(instance, withIdentifier: identifier)
134+
}
135+
return identifier
136+
}
137+
138+
/// Removes `instanceIdentifier` and its associated strongly referenced instance, if present, from the manager.
139+
///
140+
/// - Parameters:
141+
/// - instanceIdentifier: the identifier paired to an instance.
142+
/// - Returns: removed instance if the manager contains the given identifier, otherwise `nil` if
143+
/// the manager doesn't contain the value
144+
func removeInstance<T: AnyObject>(withIdentifier instanceIdentifier: Int64) throws -> T? {
145+
var instance: AnyObject? = nil
146+
lockQueue.sync {
147+
instance = strongInstances.object(forKey: NSNumber(value: instanceIdentifier))
148+
strongInstances.removeObject(forKey: NSNumber(value: instanceIdentifier))
149+
}
150+
return instance as? T
151+
}
152+
153+
/// Retrieves the instance associated with identifier.
154+
///
155+
/// - Parameters:
156+
/// - instanceIdentifier: the identifier associated with an instance
157+
/// - Returns: the instance associated with `instanceIdentifier` if the manager contains the value, otherwise
158+
/// `nil` if the manager doesn't contain the value
159+
func instance<T: AnyObject>(forIdentifier instanceIdentifier: Int64) -> T? {
160+
var instance: AnyObject? = nil
161+
lockQueue.sync {
162+
instance = weakInstances.object(forKey: NSNumber(value: instanceIdentifier))
163+
}
164+
return instance as? T
165+
}
166+
167+
private func addInstance(_ instance: AnyObject, withIdentifier identifier: Int64) {
168+
assert(identifier >= 0)
169+
assert(
170+
weakInstances.object(forKey: identifier as NSNumber) == nil,
171+
"Identifier has already been added: \\(identifier)")
172+
identifiers.setObject(NSNumber(value: identifier), forKey: instance)
173+
weakInstances.setObject(instance, forKey: NSNumber(value: identifier))
174+
strongInstances.setObject(instance, forKey: NSNumber(value: identifier))
175+
${_instanceManagerFinalizerName(options)}.attach(to: instance, identifier: identifier, delegate: finalizerDelegate)
176+
}
177+
178+
/// Retrieves the identifier paired with an instance.
179+
///
180+
/// If the manager contains a strong reference to `instance`, it will return the identifier
181+
/// associated with `instance`. If the manager contains only a weak reference to `instance`, a new
182+
/// strong reference to `instance` will be added and will need to be removed again with `removeInstance`.
183+
///
184+
/// If this method returns a nonnull identifier, this method also expects the Dart
185+
/// `${swiftInstanceManagerClassName(options)}` to have, or recreate, a weak reference to the Dart instance the
186+
/// identifier is associated with.
187+
///
188+
/// - Parameters:
189+
/// - instance: an instance that may be stored in the manager
190+
/// - Returns: the identifier associated with `instance` if the manager contains the value, otherwise
191+
/// `nil` if the manager doesn't contain the value
192+
func identifierWithStrongReference(forInstance instance: AnyObject) -> Int64? {
193+
var identifier: Int64? = nil
194+
lockQueue.sync {
195+
if let existingIdentifier = identifiers.object(forKey: instance)?.int64Value {
196+
strongInstances.setObject(instance, forKey: NSNumber(value: existingIdentifier))
197+
identifier = existingIdentifier
198+
}
199+
}
200+
return identifier
201+
}
202+
203+
/// Whether this manager contains the given `instance`.
204+
///
205+
/// - Parameters:
206+
/// - instance: the instance whose presence in this manager is to be tested
207+
/// - Returns: whether this manager contains the given `instance`
208+
func containsInstance(_ instance: AnyObject) -> Bool {
209+
var containsInstance = false
210+
lockQueue.sync {
211+
containsInstance = identifiers.object(forKey: instance) != nil
212+
}
213+
return containsInstance
214+
}
215+
216+
/// Removes all of the instances from this manager.
217+
///
218+
/// The manager will be empty after this call returns.
219+
func removeAllObjects() throws {
220+
lockQueue.sync {
221+
identifiers.removeAllObjects()
222+
weakInstances.removeAllObjects()
223+
strongInstances.removeAllObjects()
224+
nextIdentifier = ${swiftInstanceManagerClassName(options)}.minHostCreatedIdentifier
225+
}
226+
}
227+
228+
/// The number of instances stored as a strong reference.
229+
///
230+
/// For debugging and testing purposes.
231+
internal var strongInstanceCount: Int {
232+
var count: Int = 0
233+
lockQueue.sync {
234+
count = strongInstances.count
235+
}
236+
return count
237+
}
238+
239+
/// The number of instances stored as a weak reference.
240+
///
241+
/// For debugging and testing purposes. NSMapTables that store keys or objects as weak
242+
/// reference will be reclaimed non-deterministically.
243+
internal var weakInstanceCount: Int {
244+
var count: Int = 0
245+
lockQueue.sync {
246+
count = weakInstances.count
247+
}
248+
return count
249+
}
250+
}
251+
252+
''';
253+
}
254+
255+
String _instanceManagerFinalizerName(SwiftOptions options) =>
256+
'${options.fileSpecificClassNameComponent ?? ''}${classNamePrefix}Finalizer';

0 commit comments

Comments
 (0)