|
| 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