Repository: airbnb/lottie-ios
File: Sources/Public/DotLottie/DotLottieFileHelpers.swift (Swift)

Code before:
          let lottie = try DotLottieFile(data: data, filename: url.deletingPathExtension().lastPathComponent)
          DispatchQueue.main.async {
            dotLottieCache?.setFile(lottie, forKey: url.absoluteString)
            handleResult(.success(lottie))
          }
        } catch {
          DispatchQueue.main.async {
            handleResult(.failure(error))
          }
        }
      }
      task.resume()
    }
  }

  /// Loads an DotLottie from a data synchronously. Returns a `Result<DotLottieFile, Error>`
  /// - Parameters:
  ///   - data: The data(`Foundation.Data`) object to load DotLottie from
  ///   - filename: The name of the lottie file without the lottie extension. eg. "StarAnimation"
  public static func loadedFrom(
    data: Data,
    filename: String)
    -> Result<DotLottieFile, Error>
  {
    do {
      let dotLottieFile = try DotLottieFile(data: data, filename: filename)
      return .success(dotLottieFile)
    } catch {
      return .failure(error)
    }
  }

}


Review comment (suggestion):
`DotLottieFile.init` performs a lot of work on the calling thread (it decompresses a zip file). This method should be asynchronous with a completion callback so the work isn't done on the main thread. We should also provide an `async` version that wraps the implementation that takes a completion handler (using `withCheckedThrowingContinuation`).

Code after:
  /// - Parameter url: The url to load the animation from.
  /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LRUAnimationCache.sharedCache`. Optional.
  /// - Parameter handleResult: A closure to be called when the animation has loaded.
  public static func loadedFrom(
    url: URL,
    session: URLSession = .shared,
    dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache,
    handleResult: @escaping (Result<DotLottieFile, Error>) -> Void)
  {
    if let dotLottieCache = dotLottieCache, let lottie = dotLottieCache.file(forKey: url.absoluteString) {
      handleResult(.success(lottie))
    } else {
      let task = session.dataTask(with: url) { data, _, error in
        do {
          if let error = error {
            throw error
          }
          guard let data = data else {
            throw DotLottieError.noDataLoaded
          }
          let lottie = try DotLottieFile(data: data, filename: url.deletingPathExtension().lastPathComponent)
          DispatchQueue.main.async {
            dotLottieCache?.setFile(lottie, forKey: url.absoluteString)
            handleResult(.success(lottie))
          }
        } catch {
          DispatchQueue.main.async {
            handleResult(.failure(error))
          }
        }
      }
      task.resume()
    }
  }

  /// Loads an DotLottie from a data asynchronously.
  ///
  /// - Parameters:
  ///   - data: The data(`Foundation.Data`) object to load DotLottie from
  ///   - filename: The name of the lottie file without the lottie extension. eg. "StarAnimation"
  ///   - dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional.
  ///   - handleResult: A closure to be called when the file has loaded.
  public static func loadedFrom(
    data: Data,
    filename: String,
    dispatchQueue: DispatchQueue = .global(),
    handleResult: @escaping (Result<DotLottieFile, Error>) -> Void)
  {
    dispatchQueue.async {
      do {
        let dotLottie = try DotLottieFile(data: data, filename: filename)