防弹包装纸 [英] Debounced Property Wrapper

查看:146
本文介绍了防弹包装纸的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

花了一些时间创建@Debounced属性包装器后,我对代码的可读性不满意.要了解发生了什么,您确实需要了解Property包装器是如何工作的,以及wrappedvalue和projectedvalue的概念.这是属性包装器:

After spending some time creating a @Debounced property wrapper I'm not happy with the readability of the code. To understand what's going on you really need to understand how a Property wrapper works and the concept of the wrappedvalue and projectedvalue. This is the Property Wrapper:

    @propertyWrapper
    class Debounced<Input: Hashable> {

    private var delay: Double
    private var _value: Input
    private var function: ((Input) -> Void)?
    private weak var timer: Timer?

    public init(wrappedValue: Input, delay: Double) {
        self.delay = delay
        self._value = wrappedValue
    }

    public var wrappedValue: Input {
        get {
            return _value
        }
        set(newValue) {
            timer?.invalidate()
            timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] _ in
                self?._value = newValue
                self?.timer?.invalidate()
                self?.function?(newValue)
            })
        }
    }

    public var projectedValue: ((Input) -> Void)? {
        get {
            return function
        }
        set(newValue) {
            function = newValue
        }
    }
}

像这样使用属性包装器:

The property wrapper is being used like this:

@Debounced(delay: 0.4) var text: String? = nil

override func viewDidLoad() {
    super.viewDidLoad()

    self.$text = { text in
        print(text)
    }
}

它可以正常工作.每次设置text属性时,都会调用打印功能.如果在0.4秒内多次更新该值,则该函数将仅被调用一次.

It works as it should. Every time the text property is being set, the print function is being called. And if the value is updated more than once within 0.4 seconds then the function will only be called once.

但就简单性和可读性而言,我认为最好创建这样的Debouncer类: https ://github.com/webadnan/swift-debouncer .

BUT in terms of simplicity and readability, I think its better just creating a Debouncer class like this: https://github.com/webadnan/swift-debouncer.

您怎么看?有没有更好的方法来创建此属性包装器?

What do you think? Is there a better way to create this property wrapper?

推荐答案

它应能正常工作...在这种情况下,只需使用它即可!

It works as it should ... In that case, just use it!

嗯...但是如何使用呢?实际上,它不是非常灵活,尤其是在编译器声明不支持多个属性包装器"之前:-)

Hm ... but how to use it? In reality, it is not very flexible, especially till compiler claims "Multiple property wrappers are not supported" :-)

如果您的目标是在UIKit或SwiftUI应用中使用它,我建议您使用其他方法.

If your goal is to use it in UIKit or SwiftUI app, I suggest you different approach.

让我们尝试一些简约但可以正常工作的SwiftUI示例

Lets try some minimalistic, but fully working SwiftUI example

//
//  ContentView.swift
//  tmp031
//
//  Created by Ivo Vacek on 26/01/2020.
//  Copyright © 2020 Ivo Vacek. NO rights reserved.
//

import SwiftUI
import Combine

class S: ObservableObject {
    @Published var text: String = ""
    @Published var debouncedText: String = ""

    private var store = Set<AnyCancellable>()
    init(delay: Double) {
        $text
            .debounce(for: .seconds(delay), scheduler: RunLoop.main)
            .sink { [weak self] (s) in
            self?.debouncedText = s
        }.store(in: &store)
    }
}

struct ContentView: View {
    @ObservedObject var model = S(delay: 2)
    var body: some View {
        List {
            Color.clear
            Section(header: Text("Direct")) {
                Text(model.text).font(.title)
            }
            Section(header: Text("Debounced")) {
                Text(model.debouncedText).font(.title)
            }
            Section(header: Text("Source")) {
                TextField("type here", text: $model.text).font(.title)
            }

        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

您仍然可以根据需要多次订阅发布者model.$debouncedText.而且,如果您希望使用自己的动作来执行操作,也没问题!

You still can subscribe to model.$debouncedText which is Publisher as many times, as you need. And if you like to use your own action to be performed, no problem as well!

model.$debouncedText
    .sink { (s) in
        doSomethingWithDebouncedValue(s)
    }

示例应用用法

更新:如果您无法使用Combine,但是喜欢类似的语法... 首先定义协议

UPDATE: if you not able to use Combine, but you like similar syntax ... First define the protokol

protocol Debounce: class {
    associatedtype Value: Hashable
    var _value: Value { get set }
    var _completions: [(Value)->Void] { get set}
    var _delay: TimeInterval { get set }
    var _dw: DispatchWorkItem! { get set }
    func debounce(completion: @escaping (Value)->Void)
}

和防抖功能的默认实现.这个想法是,与在Combine上使用.publisher.sink()相同的方式来使用防抖动. _debounce是反跳功能的内部"实现.它比较当前和延迟"的旧值,如果它们相等,则执行此操作.

and default implementation of debounce function. The idea is, to use debounce the same way, as .publisher.sink() on Combine. _debounce is "internal" implementation of debouncing functionality. It compare current and "delay" old value and if they are equal, do the job.

extension Debounce {
    func debounce(completion: @escaping (Value)->Void) {
        _completions.append(completion)
    }
    func _debounce(newValue: Value, delay: TimeInterval, completions:  [(Value)->Void]) {
        if _dw != nil {
            _dw.cancel()
        }
        var dw: DispatchWorkItem!
        dw = DispatchWorkItem(block: { [weak self, newValue, completions] in
            if let s = self, s._value == newValue {
                for completion in completions {
                    completion(s._value)
                }
            }
            dw = nil
        })
        _dw = dw
        DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: dw)
    }
}

现在,我们有了属性包装器的所有组件.

Now we have all componets of our property wrapper.

@propertyWrapper class Debounced<T: Hashable> {

    final class Debouncer: Debounce {
        typealias Value = T

        var _completions: [(T) -> Void] = []
        var _delay: TimeInterval
        var _value: T {
            willSet {
                _debounce(newValue: newValue, delay: _delay, completions: _completions)
            }
        }
        var _dw: DispatchWorkItem!
        init(_value: T, _delay: TimeInterval) {
            self._value = _value
            self._delay = _delay
        }
    }

    var wrappedValue: T {
        get { projectedValue._value }
        set { projectedValue._value = newValue }
    }

    var projectedValue: Debouncer

    init(wrappedValue: T, delay: TimeInterval) {
        projectedValue = Debouncer(_value: wrappedValue, _delay: delay)
    }
    deinit {
        print("deinit")
    }
}

让我们尝试一下

do {
    struct S {
        @Debounced(delay: 0.2) var value: Int = 0
    }

    let s = S()
    print(Date(), s.value, "initial")

    s.$value.debounce { (i) in
        print(Date(), i, "debounced A")
    }

    s.$value.debounce { (i) in
        print(Date(), i, "debounced B")
    }

    var t = 0.0
    (0 ... 8).forEach { (i) in
        let dt = Double.random(in: 0.0 ... 0.6)
        t += dt
        DispatchQueue.main.asyncAfter(deadline: .now() + t) { [t] in
            s.value = i
            print(s.value, t)
        }
    }
}

打印类似

2020-02-04 09:53:11 +0000 0 initial
0 0.46608517831539165
2020-02-04 09:53:12 +0000 0 debounced A
2020-02-04 09:53:12 +0000 0 debounced B
1 0.97078412234771
2 1.1756938500918692
3 1.236562020385944
4 1.4076127046937024
2020-02-04 09:53:13 +0000 4 debounced A
2020-02-04 09:53:13 +0000 4 debounced B
5 1.9313412744029004
6 2.1617775513150366
2020-02-04 09:53:14 +0000 6 debounced A
2020-02-04 09:53:14 +0000 6 debounced B
7 2.6665465865810205
8 2.9287734023206418
deinit

这篇关于防弹包装纸的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆