Изменяйте переменную только в том случае, если изменились другие переменные и запрашивается обновление - PullRequest
1 голос
/ 20 января 2020

У меня есть следующая проблема:

struct Item {
    var foo: Int

    init(_ iFoo: Int = 0){
        self.foo = iFoo
    }
}

class TestObject {
    @Published var items = [Item(1), Item(2), Item(3), Item(4)]

    private var avg:Double = 0.0{
        didSet{
            print("didSet: avg: '\(self.avg)'")
        }
    }

    private var cancellableSet: Set<AnyCancellable> = []
    private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
        self.$items
            .eraseToAnyPublisher()
    }

    init(){
        self.isItemChangedPublisher
            .map{ items in
                var sum = 0
                for item in items{
                    sum += item.foo
                }
                return Double(sum)/Double(items.count)}
            .assign(to: \.avg, on: self)
            .store(in: &cancellableSet)
    }

    func changeItem(at index: Int, to value: Int){
        if self.items.count < index{
            self.items.append(Item(value))
        }else{
            self.items[index].foo = value
        }
    }

    func getAvg() -> Double{
        //Request: Items changed --> Change Value of avg here
        //Set Value of avg only if items has changed AND "Request" is called
        //  - don't set the new Value if Items has not changed and "Request" is called
        //  - don't set the new Value if Items has changed, but "Request" is not called
        return self.avg
    }
}

var bar = TestObject()

bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 3)

print("1. avg: '\(bar.getAvg())'")

bar.changeItem(at: 2, to: 20)

print("2. avg: '\(bar.getAvg())'")

bar.changeItem(at: 2, to: 30)

print("3. avg: '\(bar.getAvg())'")

bar.changeItem(at: 2, to: 20)

Значение var avg устанавливается каждый раз, когда я изменяю массив элементов. Я понимаю, что это предполагаемое поведение.

Но есть ли способ обновить переменную avg только в том случае, если изменился Массив элементов И вызвано что-то вроде «Запрос». Если были изменены только элементы, Переменная avg не должна обновляться, также, если вызывается только «Запрос», но элементы не были изменены, Переменная не должна обновляться.

Надеюсь Понятия не имею, как это сделать.

Есть ли у вас идеи сделать это с помощью инфраструктуры комбайна или другого решения?

Редактировать - 23.Jan.2020:

Я мог бы сделать что-то подобное:

import Combine

struct Item: Equatable {
    var foo: Int

    init(_ iFoo: Int = 0){
        self.foo = iFoo
    }
}

class TestObject {
    @Published var items = [Item(1), Item(2), Item(3), Item(4)]

    private var newAverage: Double? {
        didSet{
            print("didSet: items changed --> newAverage: '\(String(describing: self.newAverage))'")
        }
    }

    private var average:Double = 0.0{
        didSet{
            print("didSet: average: '\(self.average)'")
        }
    }

    private var cancellable: AnyCancellable?
    private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
        self.$items
            .eraseToAnyPublisher()
    }

    init(){
        cancellable = self.isItemChangedPublisher
            .removeDuplicates()
            .map{Double($0.map{$0.foo}.reduce(0, +))/Double($0.count)}
            .sink{self.newAverage = $0}
    }

    func changeItem(at index: Int, to value: Int){
        if self.items.count < index{
            self.items.append(Item(value))
        }else{
            self.items[index].foo = value
        }
    }

    func getAverage() -> Double{
        if self.newAverage != nil{
            self.average = self.newAverage!
            self.newAverage = nil
        }
        return self.average
    }
}

var bar = TestObject()

bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 20)
print("1. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("2. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("3. avg: '\(bar.getAverage())'")
bar.changeItem(at: 3, to: 20)

/*
 prints:
 didSet: items changed --> newAverage: 'Optional(2.5)'
 didSet: items changed --> newAverage: 'Optional(6.75)'
 didSet: items changed --> newAverage: 'Optional(11.5)'
 didSet: average: '11.5'
 didSet: items changed --> newAverage: 'nil'
 1. avg: '11.5'
 didSet: items changed --> newAverage: 'Optional(16.0)'
 didSet: average: '16.0'
 didSet: items changed --> newAverage: 'nil'
 2. avg: '16.0'
 3. avg: '16.0'
 didSet: items changed --> newAverage: 'Optional(20.0)'
 */

Но я все еще ищу решение только с комбайном (без грязного решения с переменной newAverage).

Я также попробовал решение с пользовательским DispatchQueue (это просто попытка, а не хорошее решение или идея):

import Combine
import SwiftUI

struct Item: Equatable {
    var foo: Int

    init(_ iFoo: Int = 0){
        self.foo = iFoo
    }
}

struct MyQueue {
//    let queue = DispatchQueue(label: "myQueue", attributes: .concurrent, target: .global())
    let queue = DispatchQueue(label: "myQueue")

    init(){
        self.queue.suspend()
    }

    func releaseData(){
        self.queue.resume()
        self.queue.suspend()
    }
}

class TestObject {
    @Published var items = [Item(1), Item(2), Item(3), Item(4)]

    private var average:Double = 0.0{
        didSet{
            print("didSet: average: '\(self.average)'")
        }
    }

    private var cancellable: AnyCancellable?
    let myQueue = MyQueue()
    private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
        self.$items
            .eraseToAnyPublisher()
    }

    init(){
        cancellable = self.isItemChangedPublisher
            .removeDuplicates()
            .map{ items in
                Double(items.map{ $0.foo }.reduce(0, +))/Double(items.count)}
            .buffer(size: 1, prefetch: .keepFull, whenFull: .dropOldest) //The Buffer changes nothing
            .receive(on: self.myQueue.queue)
            .assign(to: \.average, on: self)
    }

    func changeItem(at index: Int, to value: Int){
        if self.items.count < index{
            self.items.append(Item(value))
        }else{
            self.items[index].foo = value
        }
    }

    func getAverage() -> Double{
        self.myQueue.releaseData()
        return self.average
    }
}
var bar = TestObject()

bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 20)
print("1. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("2. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("3. avg: '\(bar.getAverage())'")
bar.changeItem(at: 3, to: 20)

/*
 Prints:

 didSet: average: '2.5'
 1. avg: '2.5'
 didSet: average: '6.75'
 didSet: average: '11.5'
 2. avg: '11.5'
 didSet: average: '16.0'
 3. avg: '16.0'


 But im looking for:

 didSet: average: '11.5' (because 2.5 and 6.5 are dropped)
 1. avg: '11.5'
 didSet: average: '16.0'
 2. avg: '16.0'
 3. avg: '16.0'
 */

, но это тоже не работает ...

1 Ответ

0 голосов
/ 29 января 2020

Сделайте все просто!

import Combine
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true


enum Operator {
    case count
    case sum
    case average
}

class TestObject {
    @Published var items = [1, 2, 3, 4]
    @Published var result: Double = .nan

    private var count: Double {
        Double(items.count)
    }

    private var average: Double {
        guard items.count > 0 else {
            return .nan
        }
        return sum / count
    }

    private var sum: Double {
        return Double(items.reduce(0, +))
    }


    func request( _ operation: Operator) {
        switch operation {
        case .count:
            result = count
        case .sum:
            result = sum
        case .average:
            result = average
        }
    }
}

let test = TestObject()

let pub = test.$result.sink { (res) in
    print("Result:", res)
}

test.items[0] = 10
test.items[1] = 20
test.items[2] = 30

test.request(.sum)
test.request(.average)
test.request(.count)

test.items.append(contentsOf: [1, 2, 3])

test.request(.average)

печатает только начальное значение и по запросу ...

Result: nan
Result: 64.0
Result: 16.0
Result: 4.0
Result: 10.0

Если у вас возникли проблемы с отображением начального значения результата, вы можете принять код, но его будет сложнее использовать с SwiftUI

import Combine
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true


enum Operator {
    case count
    case sum
    case average
}

class TestObject {
    @Published var items = [1, 2, 3, 4]
    //@Published var result: Double = .nan

    let result = PassthroughSubject<Double,Never>()

    private var count: Double {
        Double(items.count)
    }

    private var average: Double {
        guard items.count > 0 else {
            return .nan
        }
        return sum / count
    }

    private var sum: Double {
        return Double(items.reduce(0, +))
    }


    func request( _ operation: Operator) {
        switch operation {
        case .count:
            //result = count
            result.send(count)
        case .sum:
            //result = sum
            result.send(sum)
        case .average:
            //result = average
            result.send(average)
        }
    }
}

let test = TestObject()

/*
let pub = test.$result.sink { (res) in
    print("Result:", res)
}*/

let pub = test.result.sink { (res) in
    print("Result:", res)
}

test.items[0] = 10
test.items[1] = 20
test.items[2] = 30

test.request(.sum)
test.request(.average)
test.request(.count)

test.items.append(contentsOf: [1, 2, 3])

test.request(.average)

значение результата будет опубликовано sh только по запросу (начальное значение недоступно)

Result: 64.0
Result: 16.0
Result: 4.0
Result: 10.0

ОБНОВЛЕНИЕ:

с использованием .removeDuplicates() вовсе не помогает сократить вычисления. Он работает как «фильтр» на издателе.

то, что вы хотели бы получить, просто, вы должны сохранить результат каждой «операции» и опубликовать sh его на отдельной «операции»

измененная часть

    private var _last: Double = .nan


    private var count: Double {
        Double(items.count)
    }

    private var average: Double {
        guard items.count > 0 else {
            return .nan
        }
        _last = sum / count
        return _last

    }

    private var sum: Double {
        _last = Double(items.reduce(0, +))
        return _last
    }


    func request( _ operation: Operator) {
        switch operation {
        case .count:
            //result = count
            result.send(count)
        case .sum:
            //result = sum
            result.send(sum)
        case .average:
            //result = average
            result.send(average)
        case .last:
            result.send(_last)
        }
    }

теперь у вас есть еще один «запрос операции»

test.request(.last)

, он будет публиковать только sh последний результат, без каких-либо вычислений

и не забудьте обновить его перед попыткой: -)

enum Operator {
    case count
    case sum
    case average
    case last
}
...