Repository: airbnb/lottie-ios
File: Sources/Private/CoreAnimation/ValueProviderStore.swift (Swift)

Code before:
      return nil
    }

    // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure
    let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe<Value>? in
      guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value) else {
        logger.assertionFailure("""
          Could not convert value of type \(type(of: typeErasedKeyframe.value)) to expected type \(Value.self)
          """)
        return nil
      }

      return typeErasedKeyframe.withValue(convertedValue)
    }

    // Verify that all of the keyframes were successfully converted to the expected type
    guard typedKeyframes.count == typeErasedKeyframes.count else {
      return nil
    }

    return KeyframeGroup(keyframes: ContiguousArray(typedKeyframes))
  }

  // MARK: Private

  private let logger: LottieLogger
  private var valueProviders = [AnimationKeypath: AnyValueProvider]()
  private var orderedKeyPaths = NSMutableOrderedSet()

  /// Retrieves the most-recently-registered Value Provider that matches the given keypath.
  private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? {
    // Find the last keypath matching the given keypath,
    // so we return the value provider that was registered most-recently
    guard
      let matchingPath = orderedKeyPaths.reversed.first(where: { element in
        guard let registeredKeypath = (element as? AnimationKeypath) else { return false }
        return keypath.matches(registeredKeypath)
      }) as? AnimationKeypath else { return nil }
    return valueProviders[matchingPath]
  }

}

extension AnyValueProviderStorage {
  /// Whether or not this type of value provider is supported
  /// by the new Core Animation rendering engine
  var isSupportedByCoreAnimationRenderingEngine: Bool {
    switch self {
    case .singleValue, .keyframes:
      return true
    case .closure:

Review comment (performance):
Why do we need to use a separate `Dictionary` and `NSMutableOrderedSet` as opposed to just using the existing array? It seems like all of the operations you used could be done on the existing array. The only operation I can see that may be slightly more performant with the `NSMutableOrderedSet` is `orderedKeyPaths.remove`, but I'm not sure that performance gain is worth the overhead of maintaining multiple data structures that have to be kept in sync manually.

Thoughts on keeping this as an array?

Code after:
    }

    // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure
    let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe<Value>? in
      guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value) else {
        logger.assertionFailure("""
          Could not convert value of type \(type(of: typeErasedKeyframe.value)) to expected type \(Value.self)
          """)
        return nil
      }

      return typeErasedKeyframe.withValue(convertedValue)
    }

    // Verify that all of the keyframes were successfully converted to the expected type
    guard typedKeyframes.count == typeErasedKeyframes.count else {
      return nil
    }

    return KeyframeGroup(keyframes: ContiguousArray(typedKeyframes))
  }

  // MARK: Private

  private let logger: LottieLogger
  private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]()

  /// Retrieves the most-recently-registered Value Provider that matches the given keypath.
  private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? {
    // Find the last keypath matching the given keypath,
    // so we return the value provider that was registered most-recently
    valueProviders.last(where: { registeredKeypath, _ in
      keypath.matches(registeredKeypath)
    })?.valueProvider
  }

}

extension AnyValueProviderStorage {
  /// Whether or not this type of value provider is supported
  /// by the new Core Animation rendering engine
  var isSupportedByCoreAnimationRenderingEngine: Bool {
    switch self {
    case .singleValue, .keyframes:
      return true
    case .closure:
      return false
    }
  }
}