仅当其他变量已更改并且需要更新时,才更改变量

时间:2020-01-20 18:58:49

标签: ios swift combine

我遇到以下问题:

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)

每次更改items-Array时,都会设置var avg的值。我了解这是预期的行为。

但是,只有在items-Array已更改并且调用了诸如“ Request”之类的方法时,才有任何方法可以更新变量avg。 如果仅更改了项目,则不应该更新变量avg,也可以仅调用“请求”,但没有更改项目,则不应该更新变量。

我不知道该怎么做。

您是否有任何想法要使用Combine框架或其他解决方案?

编辑-2020年1月23日:

我可以做这样的事情:

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)'
 */

但是,我仍在寻找一种仅使用Combine的解决方案(没有带有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 :(得分:0)

使事情变得简单!

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)

结果值将仅在请求时发布(没有初始值可用)

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

更新:

使用.removeDuplicates()根本无法减少计算量。它充当发布者的“筛选器”。

您想拥有的很简单,您必须存储每个“操作”的结果并将其发布在单独的“操作”上

更改的部分

    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)

它只会发布最后的结果,而无需任何计算

并且在尝试:-)之前不要忘记tu更新

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