storage-swift

警告

此仓库已弃用,并且已迁移至 monorepo。仓库将保持可用,以支持旧版本的库,但任何新的更新必须在 monorepo 上进行。

用于与 Supabase Storage 交互的 Swift 客户端库。

前言:RLS

很多关于 Supabase Storage 库的错误报告都被提交,因为对于新手来说,RLS 很容易出错。

在使用任何客户端服务之前,您应该遵循 Supabase 网站上的 RLS 指南。您需要为每个存储桶单独创建策略,或者在 storage/objectstorage/bucket 部分使用全局策略。

对于来自用户的图像/视频上传,这里有一个关于如何为所有 CRUD 操作设置基本 RLS 的关键示例。特别注意 POSTGRES SQL 中 USINGWITH CHECK 关键字之间的区别。

一旦您为所有 CRUD 操作设置了 RLS,您应该能够将存储桶与客户端一起使用。当存储桶将其权限级别从公共更改为私有,反之亦然时,上传或下载可能会出现一些问题,因此,如果您正在进行实验,请在尝试这些设置时谨慎操作。如果您在上传和下载时遇到大量 404 错误,请尝试删除存储桶并重新初始化它。

实例化客户端

如果您正在使用完整的 supabase-swift 体验,您可以通过访问以下内容来创建对存储客户端的引用

import Supabase
import SupabaseStorage

lazy var supabaseClient: SupabaseClient = {
    return SupabaseClient(supabaseURL: supabaseUrl, supabaseKey: apiKey)
}()

lazy var supabaseStorageClient: SupabaseStorageClient = {
    return supabaseClient.storage
}()

如果您在使用此实例时遇到错误,或者只是想构建自己的实例,您可以这样做:

func storageClient(bucketName: String = "photos") async -> StorageFileApi? {
    guard let jwt = try? await supabaseClient.auth.session.accessToken else { return nil}
    return SupabaseStorageClient(
        url: "\(supabaseUrl)/storage/v1",
        headers: [
            "Authorization": "Bearer \(jwt)",
            "apikey": apiKey,
        ]
    ).from(id: bucketName)
}

在架构上,您可以遵循的模式是在提供程序类中使用简单的单例模式来保存这些值和函数,或者使用像 Resolver 这样的东西来注入它。

class SupabaseProvider {
    
    private let apiDictionaryKey = "supabase-key"
    private let supabaseUrlKey = "supabase-url"
    
    private init() {}
    
    static let shared = SupabaseProvider()
    
    lazy var supabaseClient: SupabaseClient = {
        return SupabaseClient(supabaseURL: supabaseUrl, supabaseKey: apiKey)
    }()
    
    func loggedInUserId() async -> String? {
        return try? await SupabaseProvider.shared.supabaseClient.auth.session.user.id.uuidString
    }
    
    private var keysPlist: NSDictionary {
        if
            let path = Bundle.main.path(forResource: "Keys", ofType: "plist"),
            let dictionary = NSDictionary(contentsOfFile: path) {
            return dictionary
        }
        fatalError("You must have a Keys.plist file in your application codebase.")
    }
    
    private var apiKey: String {
        guard let apiKey = keysPlist[apiDictionaryKey] as? String else {
            fatalError("Your Keys.plist must have a key of: \(apiDictionaryKey) and a corresponding value of type String.")
        }
        return apiKey
    }
    
    var supabaseUrl: URL {
        guard let url = keysPlist[supabaseUrlKey] as? String else {
            fatalError("Your Keys.plist must have a key of: \(supabaseUrlKey) and a corresponding value of type String.")
        }
        return URL(string: url)!
    }
    
    // Storage
    
    func storageClient(bucketName: String = "photos") async -> StorageFileApi? {
        guard let jwt = try? await supabaseClient.auth.session.accessToken else { return nil}
        return SupabaseStorageClient(
            url: "\(supabaseUrl)/storage/v1",
            headers: [
                "Authorization": "Bearer \(jwt)",
                "apikey": apiKey,
            ]
        ).from(id: bucketName)
    }
}

上传和下载

通过您选择的客户端,您可以按照上面说明的 RLS 和命名约定进行下载。

.png 图像下载转换为 UIImageViewUIImage 示例

if let data = try? await SupabaseProvider.shared.storageClient()?.download(
    path: profilePhotoUrl
) {
    imageView.image = UIImage(data: data)
}

通过 UIImageView 当前的 UIImage 上传图像并将其保存到用户的存储桶文件夹的示例

func loggedInUserId() async -> String? {
    return try? await SupabaseProvider.shared.client.auth.session.user.id.uuidString
}

guard let image = imageView.image?.pngData() else { return }

// Note that Swift has UUID's all capitalized, but Supabase will always lowercase
// them.

guard let userId = loggedInUserId().lowercased() else { return }

let fileId = UUID().uuidString
let filename = "\(fileId).png"
let storageClient = await SupabaseProvider.shared.storageClient()
guard let uploadResponseData = try? await storageClient?.upload(
        path: "\(userId)/\(filename)", 
        file: File(
            name: filename, 
            data: image, 
            fileName: filename, 
            contentType: "image/png"), 
            fileOptions: FileOptions(cacheControl: "3600")
        )
    ) else { return }

URL 创建

签名 URL 创建非常简单,是获取存储设备 URL 的推荐方法。对于较大的文件,您应该整合 CoreData 以将它们保存在设备上,可能还需要借助 LRU 缓存。

let storageClient = await SupabaseProvider.shared.storageClient(bucketName: "bucket_name")
// URL will expire in 1 hour (3600 seconds)
guard let url = try? await storageClient?.createSignedURL(path: imageUrl, expiresIn: 3600) else {
    return
}
DispatchQueue.main.async {
    guard let data = try? Data(contentsOf: url) else { return }
    self.imageView.image = UIImage(data: data)
}

赞助商

我们正在使用企业级开源产品构建 Firebase 的功能。我们尽可能支持现有的社区,如果产品不存在,我们会自己构建它们并开源。感谢这些赞助商,他们使 OSS 生态系统对每个人都更好。

New Sponsor