swift 依赖请求_在Swift中使用属性包装器轻松进行依赖注入

swift 依赖请求

iOS App开发 (iOS App Development)

If you’re a developer, you probably know that Dependency Injection is required in any decent project. You can’t live without it, since it helps you make your project testable, helps to loosen tightening between your classes and brings many other advantages.

如果您是一名开发人员,您可能知道在任何体面的项目中都需要进行依赖注入。 您不能没有它,因为它可以帮助您使项目可测试,有助于放松类之间的联系并带来许多其他优点。

But also, you might know DI forces you to write an extra code and init methods take a ridiculously huge number of parameters. Imagine you need to create ImageDownloader that has dependencies on NetworkManager (to download an image), FileManager (to cache the image), NotificationCenter (to post a notification when the image is downloaded) and AnalyticsManager (to log if there is any error). In this case, you’re going to have something like this:

但是,您可能也知道DI会迫使您编写额外的代码,而init方法采用了大量的参数。 想象一下,您需要创建依赖于NetworkManager (用于下载图像), FileManager (用于缓存图像), NotificationCenter (用于在下载图像时发布通知)和AnalyticsManager (用于记录是否有错误)的依赖的ImageDownloader 。 在这种情况下,您将具有以下内容:

Conventional way to DI
DI的常规方式

Your custom classes, such as networkManager, FileManager and AnalyticsManager are hidden behind protocols, so you could mock them in your unit tests.

您的自定义类(例如networkManagerFileManagerAnalyticsManager隐藏在协议后面,因此您可以在单元测试中模拟它们。

Even though we all are probably used to writing that code, it’s a little bit too much.

即使我们所有人都可能习惯于编写该代码,但还是有些过分。

Long story short, there is an approach that may help you make your life easier! Thanks Property wrappers, a great feature in Swift (5.1+) that comes in handy here.

长话短说,有一种方法可以帮助您简化生活! 感谢Property wrapper,它是Swift(5.1+)中的一个很棒的功能,在这里派上用场。

If you’re interested in that, here is how your code can look like:

如果您对此感兴趣,则代码如下所示:

DI with a property wrapper
带属性包装的DI

You don’t need to put all these dependencies into the init method of your class. And you don’t even need to specify the type for each variable. And yet, your code is still testable.

您无需将所有这些依赖项放入类的init方法中。 而且,您甚至不需要为每个变量指定类型。 但是,您的代码仍然可以测试。

So, how do we do that? Let’s write some code to implement that approach.

那么,我们该怎么做呢? 让我们编写一些代码来实现该方法。

注入解析器 (InjectionResolver)

Firstly, we need a resolver which responsibility will be to provide a proper dependency to a property. Here is how it’s going to look like:

首先,我们需要一个解决者,负责为财产提供适当的依存关系。 这是它的样子:

InjectionResolver
注入解析器

If you want to check how it works, you need to create and implement these classes and protocols. Or you can simply use my code:

如果要检查其工作方式,则需要创建和实现这些类和协议。 或者您可以简单地使用我的代码:

Simple classes for DI
DI的简单类

We will keep these classes very simple. Now, let’s implement a property wrapper.

我们将使这些类非常简单。 现在,让我们实现一个属性包装器。

物业包装 (Property Wrapper)

Create a new Swift file called Injected.swift. Here is the code:

创建一个名为Injected.swift的新Swift文件。 这是代码:

Injected.swift
注入式

If you’re not familiar with Property Wrappers in Swift 5.1, it’s not a big deal:

如果您不熟悉Swift 5.1中的Property Wrappers,那没什么大不了的:

  • We created a struct;

    我们创建了一个struct ;

  • Added @propertyWrapper before its declaration;

    在声明之前添加@propertyWrapper

  • Every Property Wrapper has to have wrappedValue. In our case, it has a generic type T;

    每个属性包装器都必须具有wrappedValue 。 在我们的例子中,它具有泛型T

  • init gets one parameter: keyPath to the variable in InjectionResolver;

    init获得一个参数: keyPath于变量InjectionResolver ;

  • wrappedValue gets a value from the InjectionResolver's variable.

    wrappedValueInjectionResolver的变量中获取一个值。

图片下载器 (ImageDownloader)

Now, we’ll add a method downloadImage to our ImageDownloader:

现在,我们将方法downloadImage添加到ImageDownloader

ImageDownloader with downloadImage()
具有downloadImage()的ImageDownloader

It’s time to check if it works. Add this code somewhere in your project:

现在该检查它是否有效了。 将此代码添加到项目中的某个位置:

When you run this code, you’ll get the following console output:

运行此代码时,将获得以下控制台输出:

Will init ImageDownloader()NetworkManager initFileManager initAnalyticsManager initWill call downloadImage()real fetchwrite filelogged: Image is downloadedDid call downloadImage()

As you can see, the instances of NetworkManager, FileManager and AnalyticsManager are not initialized until you create an instance of ImageDownloader(). This happens because your properties in InjectionResolver are lazily loaded. As a result, they’re initiated only when they are used for the first time.

如您所见,在您创建ImageDownloader()实例之前,不会初始化NetworkManagerFileManagerAnalyticsManager的实例。 发生这种情况是因为您在InjectionResolver中的属性是延迟加载的。 因此,只有在首次使用它们时才会启动它们。

Once you’ve called downloadImage(), you got three messages from your dependencies: real fetch, write file, logged: Image is downloaded. But what does happen if you want to test this class? You don’t really want to rely, for example, on your network connection. So, instead of NetworkManager, you’d use NetworkManagerMock that mimics the NetworkManager's behavior.

调用downloadImage() ,从依赖项中获得了三则消息: real fetchwrite filelogged: Image is downloaded 。 但是,如果您想测试此类,会发生什么? 例如,您实际上并不想依靠网络连接。 所以,相反的NetworkManager ,你会使用NetworkManagerMock模仿的NetworkManager的行为。

Add InjectionResolver.shared.networkManager = NetworkManagerMock() before the initialization of ImageDownloader:

在初始化ImageDownloader之前添加InjectionResolver.shared.networkManager = NetworkManagerMock()

If you run this code again, you’ll get the following output:

如果再次运行此代码,将得到以下输出:

NetworkManagerMock initWill init ImageDownloader()FileManager initAnalyticsManager initWill call downloadImage()mock fetchwrite filelogged: Image is downloadedDid call downloadImage()

As you can see, now ImageDownloader injects NetworkManagerMock. And instead of real fetch, we see mock fetch.

如您所见,现在ImageDownloader注入NetworkManagerMock 。 除了real fetch ,我们还看到了mock fetch

You can replace all the necessary dependencies with mocks before testing a class (or struct, or anything else) in your app.

您可以在测试应用程序中的类(或结构或其他任何东西)之前,用模拟代替所有必要的依赖项。

Now, you can test your app in the same way as you’re used to, but you don’t need huge init methods for your classes.

现在,您可以使用与以前相同的方式来测试您的应用程序,但是您的类不需要大量的init方法。

It might be concerning that one class (InjectionResolver) is linked to all the systems of your app. On the other hand, it initializes only necessary objects, and this behavior is basically the same as with singletons. But in this implementation, your singletons can be easily replaced with mock classes and all your code will remain testable.

可能与一个类( InjectionResolver )链接到应用程序的所有系统有关。 另一方面,它仅初始化必要的对象,并且此行为与单例基本相同。 但是在此实现中,您的单例可以轻松地用模拟类替换,并且所有代码将保持可测试性。

There is another good implementation of DI with Property Wrappers written by Ilario Salatino that might be interesting to you.

还有一个由Ilario Salatino编写的带有Property Wrappers的DI的良好实现,这可能对您来说很有趣。

The idea with keyPaths is taken from the SwiftUI’s Property Wrapper @Environment that allow you to inject Environmental values in SwiftUI views:

使用keyPaths的想法来自SwiftUI的Property Wrapper @Environment ,它允许您在SwiftUI视图中注入环境值:

@Environment(\.managedObjectContext) private var managedObjectContext

If you want to learn more about SwiftUI, check my series of tutorials:

如果您想了解有关SwiftUI的更多信息,请查看我的系列教程:

翻译自: https://medium.com/swlh/easy-dependency-injection-with-property-wrappers-in-swift-886a93c28ed4

swift 依赖请求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值