Firestore对象具有复杂类型,枚举和数组的Swift结构和返回对象

Within our app written in Swift and using SwiftUI, we needed a way to read our data from Google Firestore into the app, and then write some data back into Firestore.

在用Swift编写并使用SwiftUI的应用程序中,我们需要一种方法将数据从Google Firestore读取到应用程序中,然后再将一些数据写回到Firestore中。

Initially, I followed this guide from the Firestore documentation, although it involved a lot of mapping dictionary key/values to the type I was after. Writing data was much the same. For each property in my Swift class I had to specify the name of the corresponding Firestore field, and then assign the appropriate value to the field. I knew there must be a better way.

最初,我遵循了Firestore文档中的本指南 ,尽管该指南涉及将字典键/值映射到我后来使用的类型上。 写入数据几乎相同。 对于我的Swift类中的每个属性,我必须指定相应的Firestore字段的名称,然后将适当的值分配给该字段。 我知道一定有更好的方法。

Fortunately, the Firestore documentation recognises this problem and offers a way to bring your document into a Swift class, and allow you to use it as you would any other piece of data. This is through the use of the Pod FirebaseFirestoreSwift , which adds an extension method to the document.data() call so you can specify what type the data is coming in as. There is also a similar method on the document.setData() method for converting your Swift object back to a Firestore object.

幸运的是,Firestore文档认识到了此问题,并提供了一种将文档带入Swift类的方法 ,并允许您像处理其他任何数据一样使用它。 这是通过使用Pod FirebaseFirestoreSwift ,该类将扩展方法添加到document.data()调用中,以便您可以指定输入数据的类型。 document.setData() 方法上还有一个类似的方法 ,用于将您的Swift对象转换回Firestore对象。

However, I wasn’t sure how this would work where the data types weren’t simple String to String or Number to Int mappings. Specifically, there were five situations I wasn’t sure about (in order of least to most complicated):

但是,我不确定在数据类型不是简单的String到String或Number到Int映射的情况下这将如何工作。 具体来说,有五种我不确定的情况(从最小到最复杂):

  1. Arrays/lists

    数组/列表
  2. Complex/custom objects

    复杂/自定义对象
  3. Where the names of the fields didn’t match

    字段名称不匹配的地方
  4. Enums

    枚举
  5. Other data transformations

    其他数据转换

如何将Swift对象转换为Firestore,反之亦然? (How do I convert my Swift object to Firestore and vice versa?)

The most basic example is when your object only has simple data types.

最基本的示例是对象仅具有简单数据类型时。

A simple Firestore object with a string and a number
一个带有字符串和数字的简单Firestore对象

The corresponding Swift struct could look like this

相应的Swift结构可能看起来像这样

A simple Swift struct with a string and an integer
一个带有字符串和整数的简单Swift结构

To get your Firestore object into Swift, you need to use the .data(as: <type>) method. In the scenario below, I am getting all the documents in the Firestore collection websites and putting them into a property called self.websites

要将您的Firestore对象放入Swift,您需要使用.data(as: <type>)方法。 在以下情况下,我将在Firestore集合websites获取所有文档,并将它们放入一个名为self.websites的属性中。

Bringing Firestore data into Swift
将Firestore数据带入Swift

A few things to note:

注意事项:

  • Line 12 is where I call the document.data(as: <type>) method. I pass the type I want to turn the document into here.

    第12行是我调用document.data(as: <type>)方法的地方。 我输入了我想把文档变成这里的类型。

  • On line 11, I am using a flatMap . This is like a map in that it transforms every object in the array to another object. In this case I am transforming each document (which is essentially a dictionary or key/value pairs) to my Website struct. Except, if a result returns nil, which happens when it is unable to convert it into the specified struct, it will remove it from the array.

    在第11行,我正在使用flatMap 。 就像映射一样,它将数组中的每个对象都转换为另一个对象。 在这种情况下,我要将每个文档(本质上是字典或键/值对)转换为我的网站结构。 除非结果返回nil(在无法将其转换为指定的结构时发生),否则它将从数组中将其删除。

    An equivalent way of doing this would be to first apply a filter on

    一种等效的方法是首先在

    documents to remove any items which weren’t able to be converted into the Website struct. Because Firestore itself doesn’t require each document in a collection to look the same, it is possible to have documents which can’t be converted to the struct you have specified.

    documents以删除所有无法转换为“网站”结构的项目。 由于Firestore本身并不需要集合中的每个文档看起来都一样,因此可能存在无法转换为指定结构的文档。

There is also the opposite method, which works much the same way but for saving data to Firestore. Note I am setting just the one document here.

还有另一种方法,其工作方式大致相同,但用于将数据保存到Firestore。 注意,我在这里仅设置一个文档。

All the code we’ve looked at won’t work yet. We are missing one small ingredient.

我们查看的所有代码尚无法使用。 我们缺少一种小成分。

We need to let Swift know how to convert the document, at this point just a dictionary, into the struct we would like. For this simple example, this is really easy. We only need to do three things to our struct.

我们需要让Swift知道如何将文档(目前只是字典)转换为我们想要的结构。 对于这个简单的例子,这确实很容易。 我们只需要对我们的结构做三件事。

The Website struct, now codable and identifiable
网站结构,现在可编码和可识别
  1. Conform our struct to Identifiable . This requires it to have an id property, which we will add…

    使我们的结构符合可Identifiable 。 这要求它具有一个id属性,我们将添加它。。。

  2. Add an id property and annotate it with @DocumentID . This is where Firestore will store the id of the document itself. This is required and is useful for saving the right document back to Firestore.

    添加一个id属性,并使用@DocumentID对其进行注释。 Firestore将在此处存储文档本身的ID。 这是必需的,对于将正确的文档保存回Firestore非常有用。

  3. Conform our struct to Codable . This is the essential step that allows Swift to convert to and from our struct to Firestore.

    使我们的结构符合Codable 。 这是允许Swift在我们的结构与Firestore之间进行转换的关键步骤。

可编码 (Codable)

Codable is the awesome protocol (made up of two protocols: Decodable and Encodable) that adds two key functions:

Codable是令人敬畏的协议(由两个协议组成:Decodable和Encodable),它增加了两个关键功能:

  1. An init() method, which allows us to create the struct from the dictionary that Firestore returns. This is the decoder.

    一个init()方法,它使我们能够从Firestore返回的字典中创建结构。 这是解码器。
  2. A method which allows us to convert our struct back to the dictionary format that Firestore is expecting. This is the encoder.

    一种允许我们将结构转换回Firestore期望的字典格式的方法。 这是编码器。

Each of these methods separately comes from the Decodable and Encodable protocols respectively. If you have a struct that you only ever need to retrieve from Firestore, but never save back to Firestore, you could simply conform to the Decodable protocol.

这些方法分别分别来自“可解码”和“可编码”协议。 如果您有一个仅需要从Firestore检索但从未保存回Firestore的结构,则可以简单地遵循Decodable协议。

Fortunately Codable is quite smart, and if you have a struct like ours where there are only simple types like String, Int or Bool, that is all you need to do. Our code will now work.

幸运的是,Codable非常聪明,如果您拥有像我们这样的结构,而其中只有简单类型(如String,Int或Bool),那么您所需要做的就是。 我们的代码现在可以使用了。

1.如果我的对象有数组怎么办? (1. What if my object has an array?)

Fortunately, arrays are supported just as well as a standard type. See line 5 below.

幸运的是,数组和标准类型一样受支持。 请参阅下面的第5行。

Swift struct with an array of strings
带有字符串数组的Swift结构

This will convert an array of strings from Firestore straight into an array of strings in Swift. No extra code required.

这会将Firestore中的字符串数组直接转换为Swift中的字符串数组。 无需额外的代码。

2.如果我的对象具有自定义数据类型怎么办? (2. What if my object has custom data types?)

A common scenario is supporting custom data types you’ve written yourself. For example, my website could have an author attached to it, which is its own data type. Let’s view how this would be represented in Firestore.

一种常见的情况是支持您自己编写的自定义数据类型。 例如,我的网站可能附有作者,这是它自己的数据类型。 让我们看看如何在Firestore中表示它。

Luckily implementing this in Swift is also trivial. We just need to define another struct containing the fields we need in our custom data type. Importantly, this also needs to conform to Codable. In fact, you can nest custom objects as deep as you like, so long as every struct conforms to Codable. Let’s see this in action:

幸运的是,在Swift中实现这一点也是微不足道的。 我们只需要定义另一个结构,其中包含我们自定义数据类型中所需的字段。 重要的是 ,这需要符合Codable。 实际上,只要每个结构都符合Codable,您就可以嵌套任意深度的自定义对象。 让我们来看看实际情况:

As you can see, I’m using the custom Author type in our Website struct. You’ll notice Author doesn’t need to conform to Identifiable , as we are only keeping track of one single author, and author isn’t a Firestore document — it’s simply a field within our website.

如您所见,我在Website结构中使用自定义Author类型。 您会注意到Author不需要遵循Identifiable ,因为我们只跟踪一个作者,而且author不是Firestore文档,它只是我们网站中的一个字段。

You can also make arrays of custom objects in the same way as you’d make an array of strings.

您还可以按照与创建字符串数组相同的方式来创建自定义对象数组。

3.如果我的Firestore对象与Swift结构具有不同的字段名称怎么办? (3. What if my Firestore object has different field names compared to my Swift struct?)

This is a fairly common scenario and can come about particularly if it is challenging to update your Firestore database because it already contains data, is being consumed by other clients that you can’t update, or simply because you don’t have access to update Firestore.

这是一个相当普遍的情况,尤其是在由于要更新Firestore数据库而遇到挑战的情况下,尤其是因为该数据库已经包含数据,正被其他无法更新的客户端使用或者仅仅是因为您无权更新而导致的情况消防站。

In the first instance, I’d try to keep the field names the same, if only for your own sanity. If in your Firestore you refer to something as “author” but in your Swift code you refer to the same data as “person”, you can and will get confused.

在第一种情况下,如果只是出于您的理智考虑,我将尝试使字段名称保持相同。 如果在Firestore中您将某些内容称为“作者”,而在Swift代码中您将相同的数据称为“人员”,那么您将会并且会感到困惑。

But, if you’re unable to keep them in sync, fortunately the Codable protocol allows you to tell Swift that for a particular property, the key in the dictionary from Firebase will be called something else. This is done by defining an enum called CodingKeys .

但是,如果您无法使它们保持同步,那么幸运的是,可编码协议允许您告诉Swift,对于特定属性,Firebase字典中的键将被称为其他键。 这是通过定义一个称为CodingKeys的枚举来完成的。

For our website example, in Firestore we may have a field called websiteDescription , but in Swift we want the property to be called description as we know it is going to be describing the website.

对于我们的网站示例,在Firestore中,我们可能有一个名为websiteDescription的字段,但是在Swift中,我们希望将该属性称为description因为我们知道它将描述网站。

Our website struct where the field name in Swift doesn’t match to Firestore
我们的网站结构中,Swift中的字段名称与Firestore不匹配

Here you can see I am defining the enum called CodingKeys . It conforms to the protocol String as the keys/field names in our Firestore are all strings. If for some reason our field names were numbers in Firestore, we’d conform to the Int protocol. Importantly we also conform to the CodingKey protocol, which lets Swift know that this enum is to be used within this Codable struct to define the names of the fields.

在这里,您可以看到我正在定义名为CodingKeys的枚举。 它符合协议String因为我们的Firestore中的键/字段名称都是字符串。 如果由于某种原因,我们的字段名称是Firestore中的数字,则表明我们符合Int协议。 重要的是,我们还遵循CodingKey协议,该协议使Swift知道该枚举将在此Codable结构中用于定义字段名称。

So, within the CodingKeys enum, we need to create a case for every single field we are using. Unfortunately we can’t just define the case for the one field where the names don’t match — we need to define every single field.

因此,在CodingKeys枚举中,我们需要为正在使用的每个字段创建一个案例。 不幸的是,我们不能只为名称不匹配的一个字段定义大小写,而是需要定义每个字段。

When we get to the one where it doesn’t match, all we need to do is give a string value to the case where the name doesn’t match. As you can see in the code example, we want the Swift property to be called description , but in Firestore it is called websiteDescription . So, I’ve set the string value of the case description to be websiteDescription , and that’s it! Done.

当我们找到一个不匹配的名称时,我们所要做的就是为名称不匹配的情况提供一个字符串值。 如代码示例所示,我们希望将Swift属性称为description ,但在Firestore中将其称为websiteDescription 。 因此,我将案例description的字符串值设置为websiteDescription ,就是这样! 做完了

4.如果我的对象有枚举怎么办? (4. What if my object has enums?)

Ah, enums. This was the type I was dreading and the reason why I originally did my own mapping within the Firestore methods instead of relying on Codable. But, I actually found it wasn’t too complicated to get going.

嗯,枚举。 这就是我所恐惧的类型,也是我最初在Firestore方法中进行自己的映射而不是依赖于Codable的原因。 但是,我实际上发现开始并不太复杂。

Let’s go back to the website example. I want an enum called typeOfWebsite , with the values blog , app and forum . Firestore actually supports quite a number of different data types, but none of them are enums, so I’m going to have to store them as a string in Firestore.

让我们回到网站示例。 我想要一个名为typeOfWebsite的枚举,其值包括blogappforum 。 Firestore实际上支持许多不同的数据类型 ,但是它们都不是枚举,因此我将不得不将它们作为字符串存储在Firestore中。

What I want to happen is to convert my string to an enum when I decode the Firestore object, so I can use the typesafe enum across my Swift app instead of having to rely on magic strings. But, when I encode the object to save back in Firestore, I need it to go back to a string so it can be saved.

我想要发生的是在解码Firestore对象时将字符串转换为枚举,因此我可以在Swift应用程序中使用类型安全枚举,而不必依赖魔术字符串。 但是,当我编码对象以将其保存回Firestore时,我需要将其返回到字符串以进行保存。

So naturally, like I said before, any types within your struct need to also be Codable. So, I need to conform my enum to Codable.

就像我之前说的那样,自然地,您结构中的任何类型也都必须是可编码的。 因此,我需要使我的枚举符合Codable。

If your enum uses either String or Int for its raw values, it can actually conform to Codable with no extra work. Here’s an example:

如果您的枚举将StringInt用作其原始值,则它实际上可以符合Codable,而无需进行额外的工作。 这是一个例子:

Because these examples use Int and String raw values under the hood, Swift is able to encode and decode these. For the Int example, Firestore would save the number 0 for blog, 1 for app and 2 for forum within the document. For the String example, it would actually save blog for blog, app for app and forum for forum. For 90% of cases, this will work fine.

由于这些示例在后台使用了Int和String原始值,因此Swift能够对它们进行编码和解码。 对于Int示例,Firestore将在博客中保存数字0(博客),1(应用程序)和2(论坛)。 对于字符串例如,它实际上保存blog的博客, app的应用程序和forum为论坛。 对于90%的情况,这可以正常工作。

However, I had a slightly more complicated example, where the enum values within Firestore didn’t exactly match to the raw values of my enum. So, what I needed to do was implement my own encoder and decoder.

但是,我有一个稍微复杂的示例,其中Firestore中的枚举值与我的枚举的原始值不完全匹配。 因此,我需要做的是实现自己的编码器和解码器。

Bear in mind, you can do this for absolutely any data type — it doesn’t necessarily need to be on an enum. If you need to do some kind of transformation on data that isn’t supported out of the box with the features I’ve already described, this is the way to do it.

请记住,您绝对可以对任何数据类型执行此操作-不一定需要使用枚举。 如果您需要使用我已经描述的功能对开箱即用的数据不进行某种转换,这就是这样做的方法。

So, my exact problem was a third party API was putting enum values into my Firestore which didn’t exactly match the names of my enums. Back to our website example, even though blog is the enum value I have defined in Swift, this third party API was putting it in as Blog . Even though that is only a one letter case change, this breaks the encoding and decoding, so we need to write our own methods.

因此,我的确切问题是第三方API将枚举值放入我的Firestore中,该值与我的枚举名称不完全匹配。 回到我们的网站示例,即使blog是我在Swift中定义的枚举值,该第三方API仍将其作为Blog放入。 即使只是一个字母的大小写更改,这也会破坏编码和解码,因此我们需要编写自己的方法。

There is quite a bit to unpack here.

这里有很多要解压的东西。

  1. First, I’ve created two methods within the enum itself to convert to and from our string representation.

    首先,我在枚举本身中创建了两个方法来与我们的字符串表示形式进行相互转换。
  2. I’ve created an extension to WebsiteType which conforms to Codable . This isn’t necessary, and you could move all the code from within this extension into the enum — I just thought it provided a nice separation of the enum to the Codable implementation.

    我创建了一个扩展WebsiteType这符合Codable 。 这不是必需的,您可以将所有代码从此扩展名内移到枚举中,我只是认为它为Endable与Codable实现提供了很好的分离。

  3. As explained earlier, Codable is actually two protocols, Encodable and Decodable . Each of them has a method for doing which you can override with your own, as I’ve done here. So…

    如前所述,Codable实际上是两个协议, EncodableDecodable 。 每个人都有一个执行方法,您可以使用自己的方法来覆盖,就像我在这里所做的那样。 所以…

  4. On line 40 we have the init method. This creates the WebsiteType enum given a value. So, on lines 41 and 42 we get the value, as a string, and then on line 43 we take that string and pass it into a static function (because the enum hasn’t yet been initialised as that’s what we’re doing now), which gives us the enum corresponding to our string value. This is the decodable part.

    第40行,我们有init方法。 这将创建给定值的WebsiteType枚举。 因此,在第41和42行上,我们将值作为字符串获取,然后在第43行上 ,将该字符串并传递给静态函数(因为尚未初始化枚举,因为这就是我们现在正在做的事情) ),这将为我们提供与字符串值相对应的枚举。 这是可解码的部分。

  5. On line 46 we have a function for converting the enum back to a string. On line 48, we are using the self.description method to get the string representation of the enum, and then we are encoding it. Notice on line 47 we are using singleValueContainer() . This ensures that when we are encoding our enum it outputs as a single value, such as app .

    46 行,我们有一个将枚举转换回字符串的函数。 在第48行 ,我们使用self.description方法获取枚举的字符串表示形式,然后对其进行编码。 注意,在第47行,我们正在使用singleValueContainer() 。 这样可以确保在对枚举进行编码时,它会将输出作为单个值输出,例如app

    A lot of other tutorials I read online had this as a

    我在网上阅读的许多其他教程都将其作为

    container(keyedBy: CodingKey.self) , and then they encoded the value like container.encode(self.description, keyBy: .rawValue) . This worked, but outputted the enum within an object, like: {rawValue: "app"} .

    container(keyedBy: CodingKey.self) ,然后他们将值编码为container.encode(self.description, keyBy: .rawValue) 。 这{rawValue: "app"} ,但将对象内的枚举输出,如: {rawValue: "app"}

That’s it!

而已!

5.如果我需要进行其他类型的数据转换怎么办? (5. What if I need to do some other kind of data transformation?)

Good question. Well, you actually have all the tools you need to go ahead and do this. In the same way that I created a custom encoder and decoder for the WebsiteType enum, you can actually create your own encode and decode functions for any object conforming to Codable.

好问题。 好吧,您实际上拥有继续进行此操作所需的所有工具。 与我为WebsiteType枚举创建自定义编码器和解码器的方式WebsiteType ,您实际上可以为符合Codable的任何对象创建自己的编码和解码功能。

Some examples of things you could do:

您可以做的一些事例:

  • Get only the first value of an array

    仅获取数组的第一个值
  • Remove extra properties you don’t need (in either direction)

    删除多余的属性(双向)
  • Change the casing of string values

    更改字符串值的大小写
  • Convert Ints into Floats

    将整数转换为浮点数
  • Change date time zones

    更改日期时区

Because you can write your own functions, the opportunities are limitless.

因为您可以编写自己的函数,所以机会是无限的。

Hopefully this article was useful in your Swift journey. Within our app, we are essentially using Firestore and Firebase Functions as our logic layer, and despite having both native Android and iOS apps, it really feels like a cross platform app because they both share the same data API.

希望本文对您的Swift旅程很有用。 在我们的应用程序中,我们实际上是使用Firestore和Firebase函数作为我们的逻辑层,尽管同时具有本机Android和iOS应用程序,但由于它们都共享相同的数据API,因此它确实感觉像是跨平台应用程序。

Reading and saving data is a breeze once you’ve conformed your data types to Codable, and I’d recommend not doing any manual mapping at all. Now, all you need to do is read in the object, update a value, and save it again. This could potentially take all of about 3 lines of code, something that would take much much more if you had to hit an API or go through an ORM. Codable can also be used for JSON decoding/encoding, so the lessons in this story are not limited to usage in Firestore.

将数据类型与Codable一致后,读取和保存数据变得轻而易举,我建议您完全不要进行任何手动映射。 现在,您需要做的就是读入对象,更新值,然后再次保存。 这可能需要大约3行代码,如果您必须使用API​​或通过ORM,则需要花费更多。 Codable也可以用于JSON解码/编码,因此本故事中的课程不仅限于Firestore中的用法。

翻译自: https://levelup.gitconnected.com/firestore-to-swift-models-with-complex-types-enums-and-arrays-282893affb15

Android Studio 中的数据存储通常分为以下几个部分:文件存储、SQLite数据库、ContentProviders以及各种内置的数据持久化API。这些工具帮助开发者在Android应用中管理数据。 1. **文件存储(File Storage)**: - 使用`File`或`java.io`包可以直接操作设备的内部存储(Internal Storage)和外部存储(External Storage, 如SD卡)。 - `getExternalFilesDir()` 和 `getCacheDir()` 方法获取专用的文件路径用于存储应用缓存和数据。 2. **SQLite数据库**: - SQLite是一个轻量级的关系型数据库,使用它可以将数据持久化到应用内部,通过`SQLiteOpenHelper`和`Cursor` API进行CRUD(创建、读取、更新、删除)操作。 3. **ContentProviders**: - 为应用间共享数据提供机制,通过ContentResolver API可以查询、修改、删除其他应用的数据。 4. **SharedPreferences**: - 一种简单的键值对存储,适合存储少量、不常改动的数据,如应用设置。 5. **Room Persistence Library**(安卓X架构组件): - 一个ORM(对象关系映射)框架,提供了SQLite数据库的高级抽象,简化了数据存储和事务处理。 6. **Firebase Realtime Database/Cloud Firestore**: - Google提供的云数据库服务,支持实时同步和离线访问。 7. **Kotlin Coroutines/ViewModel**: - 在数据流库(LiveData、Flow)的帮助下,简化了数据获取和更新操作的异步管理。 相关问题: 1. Android Studio中如何选择合适的存储方式? 2. 如何在Android Studio中使用SQLite数据库创建和管理表? 3. ContentProviders的主要应用场景是什么? 4. SharedPreferences和SQLite在数据持久化中的优缺点分别是什么?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值