instanceManagerTemplate function

String instanceManagerTemplate(
  1. SwiftOptions options
)

The Swift InstanceManager.

Implementation

String instanceManagerTemplate(SwiftOptions options) {
  return '''
/// Maintains instances used to communicate with the corresponding objects in Dart.
///
/// Objects stored in this container are represented by an object in Dart that is also stored in
/// an InstanceManager with the same identifier.
///
/// When an instance is added with an identifier, either can be used to retrieve the other.
///
/// Added instances are added as a weak reference and a strong reference. When the strong
/// reference is removed and the weak reference is deallocated,`${instanceManagerFinalizerDelegateName(options)}.onDeinit`
/// is called with the instance's identifier. However, if the strong reference is removed and then the identifier is
/// retrieved with the intention to pass the identifier to Dart (e.g. by calling `identifierWithStrongReference`),
/// the strong reference to the instance is re-added. The strong reference will then need to be removed manually
/// again.
///
/// Accessing and inserting to an InstanceManager is thread safe.
final class ${swiftInstanceManagerClassName(options)} {
  // Identifiers are locked to a specific range to avoid collisions with objects
  // created simultaneously from Dart.
  // Host uses identifiers >= 2^16 and Dart is expected to use values n where,
  // 0 <= n < 2^16.
  private static let minHostCreatedIdentifier: Int64 = 65536

  private let lockQueue = DispatchQueue(label: "${swiftInstanceManagerClassName(options)}")
  private let identifiers: NSMapTable<AnyObject, NSNumber> = NSMapTable(
    keyOptions: [.weakMemory, .objectPointerPersonality], valueOptions: .strongMemory)
  private let weakInstances: NSMapTable<NSNumber, AnyObject> = NSMapTable(
    keyOptions: .strongMemory, valueOptions: [.weakMemory, .objectPointerPersonality])
  private let strongInstances: NSMapTable<NSNumber, AnyObject> = NSMapTable(
    keyOptions: .strongMemory, valueOptions: [.strongMemory, .objectPointerPersonality])
  private let finalizerDelegate: ${instanceManagerFinalizerDelegateName(options)}
  private var nextIdentifier: Int64 = minHostCreatedIdentifier

  public init(finalizerDelegate: ${instanceManagerFinalizerDelegateName(options)}) {
    self.finalizerDelegate = finalizerDelegate
  }

  /// Adds a new instance that was instantiated from Dart.
  ///
  /// The same instance can be added multiple times, but each identifier must be unique. This allows
  /// two objects that are equivalent (e.g. conforms to `Equatable`)  to both be added.
  ///
  /// - Parameters:
  ///   - instance: the instance to be stored
  ///   - identifier: the identifier to be paired with instance. This value must be >= 0 and unique
  func addDartCreatedInstance(_ instance: AnyObject, withIdentifier identifier: Int64) {
    lockQueue.async {
      self.addInstance(instance, withIdentifier: identifier)
    }
  }

  /// Adds a new instance that was instantiated from the host platform.
  ///
  /// - Parameters:
  ///   - instance: the instance to be stored. This must be unique to all other added instances.
  /// - Returns: the unique identifier (>= 0) stored with instance
  func addHostCreatedInstance(_ instance: AnyObject) -> Int64 {
    assert(!containsInstance(instance), "Instance of \\(instance) has already been added.")
    var identifier: Int64 = -1
    lockQueue.sync {
      identifier = nextIdentifier
      nextIdentifier += 1
      self.addInstance(instance, withIdentifier: identifier)
    }
    return identifier
  }

  /// Removes `instanceIdentifier` and its associated strongly referenced instance, if present, from the manager.
  ///
  /// - Parameters:
  ///   - instanceIdentifier: the identifier paired to an instance.
  /// - Returns: removed instance if the manager contains the given identifier, otherwise `nil` if
  ///   the manager doesn't contain the value
  func removeInstance<T: AnyObject>(withIdentifier instanceIdentifier: Int64) throws -> T? {
    var instance: AnyObject? = nil
    lockQueue.sync {
      instance = strongInstances.object(forKey: NSNumber(value: instanceIdentifier))
      strongInstances.removeObject(forKey: NSNumber(value: instanceIdentifier))
    }
    return instance as? T
  }

  /// Retrieves the instance associated with identifier.
  ///
  /// - Parameters:
  ///   - instanceIdentifier: the identifier associated with an instance
  /// - Returns: the instance associated with `instanceIdentifier` if the manager contains the value, otherwise
  ///   `nil` if the manager doesn't contain the value
  func instance<T: AnyObject>(forIdentifier instanceIdentifier: Int64) -> T? {
    var instance: AnyObject? = nil
    lockQueue.sync {
      instance = weakInstances.object(forKey: NSNumber(value: instanceIdentifier))
    }
    return instance as? T
  }

  private func addInstance(_ instance: AnyObject, withIdentifier identifier: Int64) {
    assert(identifier >= 0)
    assert(
      weakInstances.object(forKey: identifier as NSNumber) == nil,
      "Identifier has already been added: \\(identifier)")
    identifiers.setObject(NSNumber(value: identifier), forKey: instance)
    weakInstances.setObject(instance, forKey: NSNumber(value: identifier))
    strongInstances.setObject(instance, forKey: NSNumber(value: identifier))
    ${_instanceManagerFinalizerName(options)}.attach(to: instance, identifier: identifier, delegate: finalizerDelegate)
  }

  /// Retrieves the identifier paired with an instance.
  ///
  /// If the manager contains a strong reference to `instance`, it will return the identifier
  /// associated with `instance`. If the manager contains only a weak reference to `instance`, a new
  /// strong reference to `instance` will be added and will need to be removed again with `removeInstance`.
  ///
  /// If this method returns a nonnull identifier, this method also expects the Dart
  /// `${swiftInstanceManagerClassName(options)}` to have, or recreate, a weak reference to the Dart instance the
  /// identifier is associated with.
  ///
  /// - Parameters:
  ///   - instance: an instance that may be stored in the manager
  /// - Returns: the identifier associated with `instance` if the manager contains the value, otherwise
  ///   `nil` if the manager doesn't contain the value
  func identifierWithStrongReference(forInstance instance: AnyObject) -> Int64? {
    var identifier: Int64? = nil
    lockQueue.sync {
      if let existingIdentifier = identifiers.object(forKey: instance)?.int64Value {
        strongInstances.setObject(instance, forKey: NSNumber(value: existingIdentifier))
        identifier = existingIdentifier
      }
    }
    return identifier
  }

  /// Whether this manager contains the given `instance`.
  ///
  /// - Parameters:
  ///   - instance: the instance whose presence in this manager is to be tested
  /// - Returns: whether this manager contains the given `instance`
  func containsInstance(_ instance: AnyObject) -> Bool {
    var containsInstance = false
    lockQueue.sync {
      containsInstance = identifiers.object(forKey: instance) != nil
    }
    return containsInstance
  }

  /// Removes all of the instances from this manager.
  ///
  /// The manager will be empty after this call returns.
  func removeAllObjects() throws {
    lockQueue.sync {
      identifiers.removeAllObjects()
      weakInstances.removeAllObjects()
      strongInstances.removeAllObjects()
      nextIdentifier = ${swiftInstanceManagerClassName(options)}.minHostCreatedIdentifier
    }
  }

  /// The number of instances stored as a strong reference.
  ///
  /// For debugging and testing purposes.
  internal var strongInstanceCount: Int {
    var count: Int = 0
    lockQueue.sync {
      count = strongInstances.count
    }
    return count
  }

  /// The number of instances stored as a weak reference.
  ///
  /// For debugging and testing purposes. NSMapTables that store keys or objects as weak
  /// reference will be reclaimed non-deterministically.
  internal var weakInstanceCount: Int {
    var count: Int = 0
    lockQueue.sync {
      count = weakInstances.count
    }
    return count
  }
}

''';
}