Swift和Combine中的简单JSON解码器

介绍 (Intro)

Pretty much every app nowadays requires you to connect to the internet to access some content. The majority of those apps use JSON to communicate that data from the backend systems.

如今,几乎每个应用程序都需要您连接到Internet才能访问某些内容。 这些应用程序中的大多数使用JSON从后端系统传递数据。

There is high chance you will have some code in your app to download, parse and return objects for your app to use from an endpoint (unless you are using a network library such as Alamofire)

您的应用程序中很有可能有一些代码可以下载,解析和返回对象,以供您的应用程序从端点使用(除非您使用的是Alamofire之类的网络库)

In this post we are going to demonstrate how we can use Generics and Codable to help us build a simple reusable JSON decoder to download and parse responses from our endpoints.

在本文中,我们将演示如何使用GenericsCodable帮助我们构建一个简单的可重用JSON解码器,以下载和解析来自端点的响应。

建立我们的可编码对象 (Building our codable objects)

The first thing we need to do is build our codable objects. Objects that implement the Codable protocol allow Encoders and Decoders to encode or decode them to and from an external representation such as JSON.

我们需要做的第一件事是构建我们的可编码对象。 实现Codable协议的对象允许编码器解码器在外部表示形式(例如JSON)之间进行编码或解码。

Let’s take the response from the sample endpoint below as an example:

让我们以下面的示例端点的响应为例:

https://jsonplaceholder.typicode.com/users

https://jsonplaceholder.typicode.com/users

You can create codable classes yourself by hand. In simple examples this can be fairly straight forward, however if you have a response that has a more complex structure, doing so can be time consuming and error prone.

您可以手动创建可编码的类。 在简单的示例中,这可能很简单,但是,如果您的响应具有更复杂的结构,则这样做会很耗时且容易出错。

To create our codable objects we can use a generator, my weapon of choice is QuickType. We just paste in the JSON that is returned from the posts endpoint and it automatically generates the Codable structs for us. Easy!

要创建可编码对象,可以使用生成器,我选择的武器是QuickType 。 我们只需粘贴从posts端点返回的JSON,它会自动为我们生成Codable结构。 简单!

If we paste in our post response, we should end up with some code looking like this:

如果粘贴后回复,则应该以如下代码结束:

How easy was that?! Obviously we will still need to check the structs, in the example above none of the fields are optional which means data must be passed in otherwise our decoding will fail. We don’t need to worry about that here, but worth remembering when checking the generated code in your examples.

那有多容易? 显然,我们仍然需要检查结构,在上面的示例中,没有一个字段是可选的,这意味着必须传递数据,否则我们的解码将失败。 我们在这里不必担心,但是在检查示例中生成的代码时值得记住。

URLSession扩展和泛型 (URLSession extension and Generics)

To solve our problem we are going to wrap the existing URLSession dataTask method. I’m sure if you have done any kind of request work in pure swift you will have used this method in some form so we aren’t going to go into the details of how it works.

为了解决我们的问题,我们将包装现有的URLSession dataTask方法。 我敢肯定,如果您以任何快速的方式完成了任何形式的请求工作,您都会以某种形式使用此方法,因此我们不再赘述其工作原理。

So let’s step through this code sample step by step:

因此,让我们逐步完成此代码示例:

  1. First of we have defined a custom error for this extension, this is returned when no data has been returned from the request, covered in point 6. We also have an error case if we get an HTTPURLResponse with an incorrect status code, covered in point 5.

    首先,我们为此扩展定义了一个自定义错误,当没有从请求中返回任何数据时,将返回该错误(在第6点中进行了说明)。如果我们收到的HTTPURLResponse带有错误的状态码(在第6点中进行了说明),也会发生错误情况5,
  2. Here we are making use of Generics to allow any type T being returned from this function as long as type T implements the Decodable protocol (which we need it to inorder to use the JSONDecoder)

    在这里,我们利用泛型来允许从该函数返回的任何类型T,只要类型T实现了Decodable协议(我们需要它才能使用JSONDecoder )

  3. As discussed, here we are calling the existing dataTask method to run our request.

    如前所述,这里我们调用现有的dataTask方法来运行请求。

  4. First thing we do once the request has returned is check to see if there was a request error, if so we call the completion handler with the response and the error.

    一旦请求返回,我们要做的第一件事就是检查是否存在请求错误,如果是的话,我们使用响应和错误来调用完成处理程序。
  5. The second check we perform is to check the status code if we have received an HTTPURLResponse. Note we aren’t stopping the code here if we don’t get a HTTPURLResponse as you could use this function to load a local JSON file for example, not just a remote URL. Any status code in the 200–299 range is considered a successful request, if we receive a status code outside this range we return an error along with the response for further processing by whoever passed the completion handler.

    我们执行的第二项检查是检查状态代码,是否已收到HTTPURLResponse。 请注意,如果没有得到HTTPURLResponse,我们不会在这里停止代码,因为您可以使用此功能来加载本地JSON文件,例如,而不仅仅是远程URL。 任何200-299范围内的状态码均视为成功请求,如果我们收到此范围外的状态码,则返回错误以及响应,以供通过完成处理程序的任何人进一步处理。
  6. The third check we perform is to unwrap data ready for decoding. If this fails (as in it’s nil) then we call the completionHandler with the response and our custom error defined in step 1.

    我们执行的第三项检查是解包准备解码的数据。 如果失败(如nil),则调用带有响应和在步骤1中定义的自定义错误的完成处理程序。
  7. The final piece of the puzzle is to attempt to decode the data into type T we defined in the method signature as part of step 2. If this succeeds we can call our completion handler with our decoded type and response. If it throws an error we capture the error and return it using the catch block below.

    最后一个难题是尝试将数据解码为我们在方法签名中定义的类型T(作为步骤2的一部分)。如果成功,我们可以使用解码后的类型和响应调用完成处理程序。 如果抛出错误,我们将捕获错误并使用下面的catch块将其返回。

实际观看 (See it in action)

Now that we have put our function together, let’s take it for a test drive.

现在我们已经将我们的功能放在一起,让我们对其进行测试驱动。

This shouldn’t look too scarey, infact if you have used the standard dataTask functions in your code previously this should look very familiar. The only different here being that our completion handler now returns our Codable User objects rather than just a blob of Data like before.

如果您以前在代码中使用过标准的dataTask函数,这看上去应该不会太吓人,这看起来应该非常熟悉。 唯一不同的是,我们的完成处理程序现在返回我们的Codable User对象,而不是像以前那样仅返回Data Blob。

Hopefully that example makes sense and gives you a nice simple way to perform a request and have it decode some JSON into a struct / class. Now let’s have a look at some reactive programming using Combine.

希望该示例有意义,并为您提供了一种执行请求并将其解码一些JSON到struct /类的简单方法。 现在让我们来看一些使用Combine的React式编程。

结合 (Combine)

Hopefully you have at least heard of Combine even if you haven’t had chance to use it yet in a production app. It is Apple’s own version of a reactive framework. Those of you who have already been using RxSwift will be right at home. We aren’t going to go into too much detail about what Combine is but here is a definition of what reactive programming is:

希望您至少听说过Combine,即使您还没有机会在生产应用中使用它。 它是Apple自己的React框架版本。 那些已经在使用RxSwift的人会在家中。 我们不会对“组合”是什么进行详细介绍,但以下是对“React式编程”的定义:

In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change

在计算中,React式编程是一种声明性编程范例,涉及数据流和变化的传播

In more simplistic terms, reactive programming uses a observer pattern to allow classes to monitor different streams of data or state. When this state changes it emits an event with the new value which can trigger other streams to perform work or update such as UI code. If you are familier with KVO you will understand the basic concept. However reactive programming is far less painful and a lot more powerful than KVO.

用更简单的术语来说,React式编程使用观察者模式来允许类监视不同的数据或状态流。 当此状态更改时,它将发出具有新值的事件,该事件可以触发其他流来执行工作或更新,例如UI代码。 如果您熟悉KVO,您将了解基本概念。 但是,与KVO相比,React式编程的痛苦要小得多,功能要强大得多。

Now let’s take the previous pure Swift example and see how we can use it in Combine. The Combine framework adds new reactive functionality to the URLSession in the form of the dataTaskPublisher function.

现在,让我们以前面的纯Swift示例为例,看看如何在Combine中使用它。 Combine框架以dataTaskPublisher函数的形式向URLSession添加了新的响应式功能。

Similar to our previous example we have extended URLSession to provide this functionality. Let’s step through it:

与前面的示例类似,我们扩展了URLSession以提供此功能。 让我们逐步完成它:

  1. As with the pure Swift example we are defining a custom error here to handle when we receive a status code that is not a success. The difference being here we are attaching the response to the error as we don’t have a completionHandler in Combine. That way whoever is handling the error can inspect the response and see why it failed.

    与纯Swift示例一样,我们在此处定义一个自定义错误,以在收到不成功的状态代码时处理该错误。 区别在于我们将响应附加到错误,因为在Combine中没有完成处理程序。 这样,无论是谁处理错误,都可以检查响应并查看失败原因。
  2. Here we are defining the method, again using generics to only accept a type T that has implemented the Decodable protocol. The function returns a publisher who returns our decoded object.

    在这里,我们定义了方法,再次使用泛型仅接受已实现Decodable协议的类型T。 该函数返回一个发布者,该发布者返回我们的解码对象。
  3. As discussed previously, we are simply wrapping the existing dataTaskPublisher method.

    如前所述,我们只是包装现有的dataTaskPublisher方法。
  4. Now here is where things start to become reactive. The tryMap function is similar to the standard map function in that it attempts to convert / transfrom elements from one type to another. However the difference here being that it is almost wrapped in a try. In this case you can include code in the closure which throws errors and they will be pushed downstream and handled later instead of needing a do block. Similar to our pure Swift example, we are checking we have a valid status code, if not we throw our custom error. If not we map our data ready to be decoded.

    现在,这里开始变得被动起来。 tryMap函数与标准映射函数相似,它尝试将元素从一种类型转换为另一种类型。 但是,这里的区别在于它几乎要尝试一下。 在这种情况下,您可以在闭包中包含引发错误的代码,这些错误将被推送到下游并在以后处理,而不需要do块。 与纯粹的Swift示例类似,我们正在检查是否有有效的状态码,如果没有,则抛出自定义错误。 如果没有,我们将映射我们的数据准备好进行解码。

  5. Here we are using the built in decode method to attempt to decode our custom type using the JSONDecoder. Similar to the tryMap function above, any errors are pushed downstream to be handler later.

    在这里,我们使用内置的解码方法来尝试使用JSONDecoder解码自定义类型。 与上面的tryMap函数类似,所有错误都将向下游推送以供稍后处理。

  6. The final piece of the puzzle is to use type erasure. This removes the publisher class type and makes it AnyPublisher. For more info on type erasure see my previous post

    难题的最后一部分是使用类型擦除 。 这将删除发布者类类型,并使其成为AnyPublisher。 有关类型擦除的更多信息,请参阅我以前的文章

结合行动 (Combine in action)

Now that we have built our wrapper class let’s take a look at this in action:

现在,我们已经建立了包装器类,让我们来看一下实际的情况:

  1. Here we have called our newly created dataTaskPublisher method which has returned our publisher. This is where reactive programming comes in. All of the code inside the dataTaskPublisher has not executed yet. We have simply returned a publisher who is waiting for a subscriber to come along and listen. A publisher will not execute unless a subscription has not been fulfilled. To subscribe to a stream we use the sink method. If you think of the chain of reactive methods flowing into a sink at the bottom, that is the best analogy here.

    在这里,我们调用了新创建的dataTaskPublisher方法,该方法已返回我们的发布者。 这就是React式编程的用武之地。dataTaskPublisher内部的所有代码尚未执行。 我们只是返回了一个正在等待订户来听的发布者。 除非尚未完成订阅,否则发布者将不会执行。 要订阅流,我们使用接收器方法。 如果您想到React性方法的链条流入底部的汇,那是最好的类比。
  2. The sink method has 2 parts. The first closure defines what happens once the stream is completed. Now this can come in the form of a finished state, which means the stream has completed what is doing and will no longer emit any more events. Or failure, which means some item further up the stream has raised an error which flows down into this sink where it can be handled.

    下沉法分为两部分。 第一个闭包定义流完成后将发生的情况。 现在这可以以完成状态的形式出现,这意味着流已完成正在执行的操作,将不再发出任何其他事件。 或失败,这意味着上游的某些物品引发了一个错误,该错误向下流入该水槽,在那里可以对其进行处理。
  3. The second closure defines what we would like to do each time the event stream emits a change. In this case the publisher will send a users array once it has finished loading, here we are just printing out the user names.

    第二个闭包定义每次事件流发出更改时我们要做什么。 在这种情况下,发布者将在完成加载后发送一个用户数组,这里我们只是打印出用户名。

最后 (Finally)

What have we learnt:

我们学到了什么:

  • We have used QuickType to convert our JSON into codable structs for decoding.

    我们已经使用QuickType将JSON转换为可编码的结构以进行解码。

  • Wrapped the existing URLSession dataTask method with our own using Generics so we can using any Codable type to decode the response.

    使用泛型将现有的URLSession dataTask方法与我们自己的方法包装在一起 ,因此我们可以使用任何Codable类型来解码响应。

  • Similarly, using reactive programming and Apple’s new Combine framework have created our own Generic wrapper for the existing dataTaskPublisher function.

    同样,使用React式编程和Apple的新Combine框架为现有dataTaskPublisher函数创建了自己的通用包装。

Feel free to download the playground and play around with the examples yourself

可以免费下载游乐场并自己玩示例

Originally published at https://pyartez.github.io on June 11, 2020.

最初于 2020年6月11日 发布在 https://pyartez.github.io

翻译自: https://medium.com/dev-genius/simple-json-decoder-in-swift-and-combine-35fc99a499b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值