如何使用 AppSync S3ObjectManager 更新视图的进度条?

How can I update a view's progress bar using the AppSync S3ObjectManager?

我正在使用 AWSAppSyncClient 上传文件,但我很难将上传进度挂钩与视图连接起来。

AWSAppSyncClient 是使用 S3ObjectManager 初始化的应用程序委托的 属性。对象管理器方法 upload 可以通过 AWSTransferUtilityUplaodExpression 访问上传进度:

  expression.progressBlock = {(task, progress) in
    DispatchQueue.main.async(execute: {
      // Can we update the controller's progress bar here?
      print("Progress: \(Float(progress.fractionCompleted))")
    })
  }

我的控制器通过调用 perform 调用上传:

var appSyncClient: AWSAppSyncClient? // retrieved from the app delegate singleton

appSyncClient?.perform(mutation: CreatePostMutation(input: input)) { (result, error) in ... 

我正在努力解决的问题: 我如何向 S3ObjectManager 提供对控制器的引用?我想在每个控制器中实例化 AWSAppSyncClient,也许使用某种委托模式?

在每个视图控制器上实例化一个新客户端可能有点矫枉过正。安装和拆卸需要一些时间和系统资源来执行,您可能更愿意在任何情况下将这些活动与视图控制器分开,只是为了职责分离。

没有真正注册每个对象侦听器的好方法,因为突变会排队等待最终的异步传递。你的代表想法似乎是目前最好的方法。

注意:以下代码未经测试,不是线程安全的。

例如,您可以声明一个单例委托来管理需要报告进度的各个视图的观察者:

class AppSyncS3ObjectManagerProgressWatcher {
    typealias ProgressSubscription = UUID
    static let shared = AppSyncS3ObjectManagerProgressWatcher()
    private var watchers = [UUID: AppSyncS3ObjectManagerProgressDelegate?]()

    func add(_ watcher: AppSyncS3ObjectManagerProgressDelegate) -> ProgressSubscription {
        let subscription = UUID()
        weak var weakWatcher = watcher
        watchers[subscription] = weakWatcher
        return subscription
    }

    func remove(_ subscription: ProgressSubscription?) {
        guard let subscription = subscription else {
            return
        }
        watchers[subscription] = nil
    }
}

extension AppSyncS3ObjectManagerProgressWatcher: AppSyncS3ObjectManagerProgressDelegate {
    func progressReportingExpression(forDownloadingObject object: AWSS3ObjectProtocol) -> AWSS3TransferUtilityDownloadExpression {
        let expression = AWSS3TransferUtilityDownloadExpression()
        expression.progressBlock = { _, progress in
            self.didReportProgress(forDownloadingObject: object, progress: progress)
        }
        return expression
    }

    func progressReportingExpression(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol) -> AWSS3TransferUtilityUploadExpression {
        let expression = AWSS3TransferUtilityUploadExpression()
        expression.progressBlock = { _, progress in
            self.didReportProgress(forUploadingObject: object, progress: progress)
        }
        return expression
    }

    func didReportProgress(forDownloadingObject object: AWSS3ObjectProtocol, progress: Progress) {
        for watcher in watchers.values {
            watcher?.didReportProgress(forDownloadingObject: object, progress: progress)
        }
    }

    func didReportProgress(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, progress: Progress) {
        for watcher in watchers.values {
            watcher?.didReportProgress(forUploadingObject: object, progress: progress)
        }
    }
}

无论在何处使 S3TransferUtility 符合 S3ObjectManager,您都可以执行如下操作:

extension AWSS3TransferUtility: AWSS3ObjectManager {

    public func download(s3Object: AWSS3ObjectProtocol, toURL: URL, completion: @escaping ((Bool, Error?) -> Void)) {

        let completionBlock: AWSS3TransferUtilityDownloadCompletionHandlerBlock = { task, url, data, error -> Void in
            if let _ = error {
                completion(false, error)
            } else {
                completion(true, nil)
            }
        }

        let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
            .shared
            .progressReportingExpression(forDownloadingObject: s3Object)

        let _ = self.download(
            to: toURL,
            bucket: s3Object.getBucketName(),
            key: s3Object.getKeyName(),
            expression: progressReportingExpression,
            completionHandler: completionBlock)
    }

    public func upload(s3Object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, completion: @escaping ((_ success: Bool, _ error: Error?) -> Void)) {
        let completionBlock : AWSS3TransferUtilityUploadCompletionHandlerBlock = { task, error -> Void in
            if let _ = error {
                completion(false, error)
            } else {
                completion(true, nil)
            }
        }

        let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
            .shared
            .progressReportingExpression(forUploadingObject: s3Object)

        let _ = self.uploadFile(
            s3Object.getLocalSourceFileURL()!,
            bucket: s3Object.getBucketName(),
            key: s3Object.getKeyName(),
            contentType: s3Object.getMimeType(),
            expression: progressReportingExpression,
            completionHandler: completionBlock
            ).continueWith { (task) -> Any? in
            if let err = task.error {
                completion(false, err)
            }
            return nil
        }
    }
}

然后在进度报告视图中:

override func awakeFromNib() {
    super.awakeFromNib()
    progressSubscription = AppSyncS3ObjectManagerProgressWatcher.shared.add(self)
}

func didReportProgress(forUploadingObject object: AWSS3InputObjectProtocol & AWSS3ObjectProtocol, progress: Progress) {
    // TODO: Filter by object local URI/key/etc to ensure we're updating the correct progress
    print("Progress received for \(object.getKeyName()): \(progress.fractionCompleted)")
    self.progress = progress
}

正如我所指出的,此代码未经测试,但它应该概述了一个通用方法供您开始使用。欢迎您提供反馈,并想听听您最终选择了哪种方法。

最后,请随时在我们的问题页面上提出功能请求:https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues