繁体   English   中英

Firebase 存储图像缓存未更新 SWIFT

[英]Firebase Storage Image Cache not updating SWIFT

我在我的应用程序上设置了图像以使用 firebase UI,以便我可以通过遵循文档https://firebase.google.com/docs/storage/ios/download-files轻松缓存它们

我的代码看起来像这样

    guard let profileImageUrl = user?.url else { return }
    profileImageView.sd_setImage(with: profileImageUrl)

我正在覆盖图像位置以更新这样的图像

let storageRef = Storage.storage().reference(withPath: "/users/\(uid)/profilePhoto.jpg")
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, err) in

如果我在没有缓存的新设备上访问照片,它会显示新图像。 但是,如果我在以前查看过的设备上访问该图像,则会看到旧图像。

编辑:所以我了解缓存是通过 SDWebImage 工作的。 我正在根据用户 uid 生成与 SDWebImage 一起使用的存储引用。 因此,存储引用不会改变,这会导致问题。

当您使用 SDWebImage 加载图像时,它会永久缓存图像并且下次不会从网络请求它。 当您从另一台设备上传新图像时,图像的 url 保持不变,即之前设备上的 SDWebImage 不知道图像已更新。 因此,您需要想出如何将更改通知先前设备的想法。

以下是我认为应该如何完成:

上传图片时,请使用动态图片名称。 以便更改 url,因此 SDWebImage 将加载新的个人资料图片。


在评论中,您问我将如何引用此动态 url。 你有两种可能的选择(我能想到的):

1) 使用 firebase 数据库:在这里,我们将在用户/uid 等数据库上创建一个节点/引用。 在这里,我们将存储带有关键图像的动态 imageurl。 在任何设备上,当您想在设备上加载图像时,您将从数据库中读取 url。 它总是指向新的图像 url 关于如何做到这一点的一个很好的例子是在这个SO POST

Breif:用户-> 上传图像-> 将 URL 保存到数据库

从 Firebase 读取图片 URL -> 通过 SDWebImage UIImageView 加载

2) 使用文本文件:在此我们将不使用数据库。 我们将在同一文件夹 users/uid/log.txt 文件下创建一个日志文件。 每当您将图像上传到存储时,我们都会将 url 保存在此文本文件中并将其上传到存储中。 当您要加载图像时,首先读取文本文件以获取 imageUrl,然后使用 SDWebImage 加载图像。 这种方式与上传和下载图像文件非常相似,只需一个额外的步骤,您必须读取文本文件以获取存储空间。

简介:用户->上传图片->将url放入文本文件并上传

下载文本文件 -> 从文件中读取图像 url -> 通过 SDWebImage 将图像加载到 UIImageVIew


我正在阅读文档并找到了另一种方法。 这样您就不需要使用动态网址。 您可以使用您的静态网址。 在此您需要查询图像的元数据:

// Create reference to the file whose metadata we want to retrieve
let forestRef =  Storage.storage().reference(withPath: "/users/\(uid)/profilePhoto.jpg")

// Get metadata properties
forestRef.getMetadata { metadata, error in
  if let error = error {
    // Uh-oh, an error occurred!
  } else {
    // Metadata now contains the metadata for 'profilePhoto.jpg'  
  }
}

除了一些不太常见的属性(如 contentDisposition 和 timeCreated)之外,文件元数据还包含常见的属性,例如名称、大小和 contentType(通常称为 MIME 类型)。

取自

每当您查询元数据时,将 timeCreated 保存在 UserDefaults 中,下次将 timeCreated 与保存的 timeCreated 进行比较,如果存在差异,则从 SDWebImage Cache 中删除图像,然后要求 SDWebImage 再次加载图像。

您的缓存不刷新的原因是因为您总是写入相同的路径,您只是上传不同的图像。 我看到您一直在询问创建一个动态 URL,理论上,它会创建一个未缓存的图像路径,因此可以正确重新加载。

但你不需要这样做。 我最近在开发使用 Firebase 的应用程序时遇到了同样的情况和问题。 我有一个播放器模型,其中包含对个人资料图片的引用。 图片是使用 Firebase Storage 存储的,文件夹路径为/players/\\(uid)/profilePhoto.jpg 可以通过购买头像和使用相机拍摄照片来更改个人资料图片。 而且由于我每次都上传到相同的存储路径,因此我的图像也没有重新加载。

现在,SDWebImage 缓存是自动的 - 除非您指定不同,每当您调用someImageView.sd_setImage(with: imageUrl) ,URL 都会通过 SDImageCache 缓存。

所以我所做的就是在获得新参考时从缓存中删除图像 - 在个人资料图片的didSet属性观察者中。

    /// property to store the profile picture url
    internal var profilePictureUrl: String = "" {
      didSet {
        SDImageCache.shared().removeImage(forKey: Storage.storage().reference().child(profilePicture).fullPath, withCompletion: nil)
      }
    }

现在,每次设置属性时,都会从缓存中删除 url,SDWebImage 必须再次获取它。 当您调用sd_setImage(with: imageUrl)并再次缓存它时,它sd_setImage(with: imageUrl)

    /// public property to set the profile image on the button
    internal var profileImagePath: String? {
      didSet {
        if let path = profileImagePath {
          profileButton.imageView?.sd_setImage(with: Storage.storage().reference().child(path), placeholderImage: nil, completion: { [weak self] (image, error, _, _) in
            self?.profileButton.setImage(image ?? #imageLiteral(resourceName: "default-profile-pic"), for: .normal)
          })
        }
      }
    }

我有一个按钮,可以在其图像视图中显示我的个人资料图片。 当我获取新图像并调用刷新按钮视图的方法时,将获取图像,并且由于缓存不再引用图像路径,它将获取新上传的图像并重新创建缓存路径。

这就是我为我完成这项工作的方式。 我希望它也能解决您的问题。 请随时提出任何其他说明,我会尽力回答。

编辑: Sahil Manchanda向我指出我误解了问题的全部范围。

我的答案在单个设备上效果最佳。

如果问题是多设备同步,您仍然可以使用提供的解决方案,但需要更频繁的 UI 更新 - 由于每次从 Firebase 获取用户信息时都会更新profilePictureUrl属性,因此更新可能会延迟到下一次用户信息时已读取或应用程序已重新启动。

您还可以在删除缓存之前添加连接检查,以避免因带宽滞后造成的任何损失。

另一种解决方案可能是使用静默通知(用于快速刷新操作),它仅作为设备“提醒”该清除缓存的时间。 如果您对服务器或 OneSignal 没有任何经验,则可以使用 Firebase 的云消息传递

一段时间后,我找到了另一种更好的方法,因为它比我以前的解决方案只使用一个网络请求。 现在我正在使用直接 url 在存储桶中进行图像处理。 然后我使用 Nuke(图像加载和缓存库)加载它,这尊重原生 HTTP 缓存机制。


extension StorageReference {
    var url: URL {
        let path = fullPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
        return URL(string: "https://firebasestorage.googleapis.com/v0/b/\(bucket)/o/\(path)?alt=media")!
    }
}

Nuke.loadImage(with: imgRef.url, into: imageView)

我的旧答案:

它从存储检索元数据,并将服务器上的创建日期与缓存图像的创建日期进行比较。 如果缓存过时,它将从服务器重新缓存图像。

extension UIImageView {

    func setImage(with reference: StorageReference, placeholder: UIImage? = nil) {
        sd_setImage(with: reference, placeholderImage: placeholder) { [weak self] image, _, _, _ in
            reference.getMetadata { metadata, _ in
                if let url = NSURL.sd_URL(with: reference)?.absoluteString,
                    let cachePath = SDImageCache.shared.cachePath(forKey: url),
                    let attributes = try? FileManager.default.attributesOfItem(atPath: cachePath),
                    let cacheDate = attributes[.creationDate] as? Date,
                    let serverDate = metadata?.timeCreated,
                    serverDate > cacheDate {

                    SDImageCache.shared.removeImage(forKey: url) {
                        self?.sd_setImage(with: reference, placeholderImage: image, completion: nil)
                    }
                }
            }
        }
    }

}

我遇到了同样的问题并用最新版本解决了它,所以想在这里添加我的答案以帮助其他人。

SDWEBImage 更新了这些图像的新功能,这些图像将在相同的 URL 或路径上更新。

功能细节总结:

即使图像被缓存,也要遵守 HTTP 响应缓存控制,并在需要时从远程位置刷新图像。 磁盘缓存将由 NSURLCache 而不是 SDWebImage 处理,导致性能略有下降。 此选项有助于处理在同一请求 URL 后面更改的图像,例如 Facebook 图形 API 配置文件图片。 如果刷新缓存图像,则使用缓存图像和最终图像再次调用完成块。 宣言

SDWebImageRefreshCached = 1 << 3

仅当您无法使用嵌入式缓存破坏参数使 URL 成为静态时,才使用此标志。

声明于:SDWebImageDefine.h

所以简而言之,如果我们想更新缓存的图像,该图像将来会在同一 URL 上更新,我们可以使用以下方法。

带有完成块的图像 URL:

imageView.sd_setImage(with: <URL>,
                      placeholderImage: placeholderImage,
                      options: .refreshCached)
                { (image, error, type, url) in
                    <CODE>
                }

没有完成块的图像 URL:

imageView.sd_setImage(with: <URL>,
                      placeholderImage: placeholderImage,
                      options: .refreshCached)

带有完成块的 Firebse 参考:

imageView.sd_setImage(with: reference,
                      maxImageSize: <MaxSize>,
                      placeholderImage: placeholderImage,
                      options: .refreshCached)
                { (image, error, type, ref) in
                    <#code#>
                }

没有完成块的 Firebse 参考:

imageView.sd_setImage(with: reference,
                      maxImageSize: <MaxSize>,
                      placeholderImage: placeholderImage,
                      options: .refreshCached)

感谢 SDWebImage 团队将这个惊人的功能添加到 lib 中,让我们的生活变得轻松。

希望这个答案可以帮助其他人。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM