协议里面可以定义属性吗_如何使用已发布的属性包装器类型定义协议

协议里面可以定义属性吗

This article is originally published at https://swiftsenpai.com on May 17, 2020.

本文最初于 2020年5月17日 发布在 https://swiftsenpai.com 上。

The Combine framework was introduced in WWDC 2019 and it is mainly used alongside SwiftUI. However, this does not limit us to use the Combine framework on our UIKit apps.

Combine框架是在WWDC 2019中引入的,主要与SwiftUI一起使用。 但是,这并不限制我们在UIKit应用程序上使用Combine框架。

In fact, the @Published property wrapper introduced in Combine is the perfect fit for any UIKit apps with MVVM architecture. We can use @Published to elegantly link up the view controller with its view model so that the view controller can be notified automatically by any changes made on the view model.

实际上,在Combine中引入的@Published 属性包装器非常适合任何具有MVVM体系结构的UIKit应用程序。 我们可以使用@Published优雅地将视图控制器与其视图模型链接起来,以便可以通过对视图模型进行的任何更改自动通知视图控制器。

Everything works nicely until the day where I wanted to define a protocol for my view model in order to achieve polymorphism using protocol-oriented programming. The problem arises because the current Swift version (5.2) does not support property wrapper definition in a protocol.

一切顺利,直到我想为我的视图模型定义协议以使用面向协议的编程实现多态的那天。 出现问题是因为当前的Swift版本(5.2)不支持协议中的属性包装器定义。

How should we go about defining @Published property wrapper type in a protocol? Read on to find out more.

我们应该如何在协议中定义@Published属性包装器类型? 请继续阅读以了解更多信息。

问题 (The Problem)

Let’s say we have a view controller and a view model with a @Published variable called name. Whenever name is set or updated, the name publisher will notify MyViewController to print out a hello message.

比方说,我们有一个视图控制器和一个视图模型@Published变量叫name 。 只要name被设置或更新,该name出版商将通知MyViewController打印出hello消息。

class MyViewModel {


    @Published var name: String


    init(name: String) {
        self.name = name
    }
}


class MyViewController: UIViewController {
    
    var viewModel: MyViewModel!
    
    private var cancellables: Set<AnyCancellable> = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Subscribe to the view model's name publisher
        // and get notify when name is set or updated
        viewModel.$name
            .receive(on: RunLoop.main)
            .sink { (name) in
                
                // Print hello message when name is set or updated
                print("Hello \(name)")
                
        }.store(in: &cancellables)
    }
}

To see the above code in action, let’s try to run it using Xcode Playground.

要查看上面的代码,请尝试使用Xcode Playground运行它。

let viewModel = MyViewModel(name: "Swift Senpai 1")
let viewController = MyViewController()
viewController.viewModel = viewModel


PlaygroundPage.current.liveView = viewController


viewModel.name = "Swift Senpai 2"
viewModel.name = "Swift Senpai 3"


// Output:
// Hello Swift Senpai 1
// Hello Swift Senpai 2
// Hello Swift Senpai 3

As you can see from the output, the name publisher is working according to our expectation.

从输出中可以看到, name发布者正在按照我们的期望工作。

Now, let’s try to improve the reusability of MyViewController by applying polymorphism on MyViewController 's view model type.

现在,让我们尝试通过对MyViewController的视图模型类型应用多态来提高MyViewController的可重用性。

What we can do here is to create a polymorphic interface using protocol so that MyViewController can accept any view models that conform to this protocol.

我们在这里可以做的是使用协议创建一个多态接口,以便MyViewController可以接受任何符合该协议的视图模型。

// Define a polymorphic interface
protocol ViewModelProtocol {


    @Published var name: String { get }
}


// Conform to ViewModelProtocol
class MyViewModel: ViewModelProtocol {


    @Published var name: String


    init(name: String) {
        self.name = name
    }
}


class MyViewController: UIViewController {


    // Change viewModel type to ViewModelProtocol
    var viewModel: ViewModelProtocol!


    private var cancellables: Set<AnyCancellable> = []


    override func viewDidLoad() {
        super.viewDidLoad()


        viewModel.$name
            .receive(on: RunLoop.main)
            .sink { (name) in
                print("Hello \(name)")
        }.store(in: &cancellables)
    }
}

Unfortunately, when the code above is executed, the compiler will start complaining about “Property ‘name’ declared inside a protocol cannot have a wrapper”.

不幸的是,执行上述代码时,编译器将开始抱怨“ 协议内声明的属性'name'不能具有包装器 ”。

Compiler error in Xcode when define protocol with property wrapper
Compiler error when define protocol with property wrapper
使用属性包装器定义协议时发生编译器错误

This is because the current version of Swift (5.2) does not support property wrapper definition in a protocol. 🙁

这是因为当前版本的Swift(5.2)不支持协议中的属性包装器定义。 🙁

解决方法 (The Workaround)

In order to work around this limitation, let’s take a step back and understand how we are using the @Published name property wrapper in the view controller.

为了解决此限制,让我们退后一步,了解我们如何在视图控制器中使用@Published name属性包装器。

// Subscribe to the view model's name publisher
// and get notify when name is set or updated
viewModel.$name
    .receive(on: RunLoop.main)
    .sink { (name) in
        print("Hello \(name)")
}.store(in: &cancellables)

As seen in the code snippet above, we are assessing the name publisher via the projected value of @Published name property wrapper. In order word, what we actually need in the view controller is the name publisher but not the @Published name property wrapper.

如上面的代码片段所示,我们正在通过@Published name属性包装器的预计值评估name发布者。 换句话说,视图控制器中实际上需要的是name发布者,而不是@Published name属性包装器。

With that in mind, we are now able to work around the limitation by defining a protocol with name publisher and manually expose the name publisher in the view model implementation.

考虑到这一点,我们现在可以通过使用name发布者定义协议来解决限制,并在视图模型实现中手动公开name发布者。

protocol ViewModelProtocol {


    // Define name publisher
    var namePublisher: Published<String>.Publisher { get }
}


class MyViewModel: ViewModelProtocol {


    @Published var name: String
    
    // Manually expose name publisher in view model implementation
    var namePublisher: Published<String>.Publisher { $name }


    init(name: String) {
        self.name = name
    }
}


class MyViewController: UIViewController {


    var viewModel: ViewModelProtocol!


    private var cancellables: Set<AnyCancellable> = []


    override func viewDidLoad() {
        super.viewDidLoad()


        // Subscribe to the view model's name publisher
        // and get notify when name is set or updated
        viewModel.namePublisher
            .receive(on: RunLoop.main)
            .sink { (name) in
                print("Hello \(name)")
        }.store(in: &cancellables)
    }
}


// Execute code
let viewModel = MyViewModel(name: "Swift Senpai 1")
let viewController = MyViewController()
viewController.viewModel = viewModel


PlaygroundPage.current.liveView = viewController


viewModel.name = "Swift Senpai 2"
viewModel.name = "Swift Senpai 3"


// Output:
// Hello Swift Senpai 1
// Hello Swift Senpai 2
// Hello Swift Senpai 3

With that, we have successfully created a polymorphic interface while retaining the behavior of the view model and view controller. 🥳

这样,我们就成功创建了一个多态接口,同时保留了视图模型和视图控制器的行为。 🥳

更进一步 (Taking One Step Further)

The suggested workaround is not only limited to exposing the publisher, we can also use the same concept to expose the @Published property wrapper itself as well as the wrapped value.

建议的解决方法不仅限于公开发布者,我们还可以使用相同的概念公开@Published属性包装器本身以及包装的值。

protocol ViewModelProtocol {


    // Define name (wrapped value)
    var name: String { get }
    
    // Define name Published property wrapper
    var namePublished: Published<String> { get }
    
    // Define name publisher
    var namePublisher: Published<String>.Publisher { get }
}


class MyViewModel: ViewModelProtocol {


    @Published var name: String
    var namePublished: Published<String> { _name }
    var namePublisher: Published<String>.Publisher { $name }


    // ... ...
    // ... ...
    // ... ...
}

结语 (Wrapping Up)

As you can see from the above example, the limitation that we are facing is more of a property wrapper limitation rather than the @Published type limitation.

从上面的示例中可以看到,我们面临的限制更多是属性包装器限制,而不是@Published类型限制。

Therefore, with the same idea, you can definitely apply the workaround to any kind of property wrapper. I’ll leave that as an exercise for you!

因此,以相同的想法,您绝对可以将变通方法应用于任何类型的属性包装器。 我将其留给您练习!

If you have any questions, feel free to leave it in the comment section below or you can reach out to me on Twitter.

如有任何疑问,请随时在下面的评论部分中保留,或者可以在Twitter上与我联系。

Thanks for reading. 🧑🏻‍💻

谢谢阅读。 ‍💻

翻译自: https://medium.com/swlh/how-to-define-a-protocol-with-published-property-wrapper-type-1b6349097064

协议里面可以定义属性吗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值