结合@Published 属性:在更新期间从别处获取当前值

Ell*_*ock 14 ios swift combine

我的主要问题是我正在尝试解决(未记录的)事实,即@Published在订阅者收到更改通知后,属性不会更新属性的值。我似乎无法找到解决它的好方法。

考虑以下 aSubject@Published属性的人为组合。首先是一个简单的类:

class StringPager {
    @Published var page = 1
    @Published var string = ""
}
let pager = StringPager()
Run Code Online (Sandbox Code Playgroud)

然后是一个简单的传递主题:

let stringSubject = PassthroughSubject<String, Never>()
Run Code Online (Sandbox Code Playgroud)

为了调试,让我们订阅字符串属性并打印出来:

pager.$string.sink { print($0) }
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好。接下来,让我们订阅主题并根据其值更改寻呼机:

stringSubject.sink { string in
  if pager.page == 1 {
    pager.string = string
  } else {
    pager.string = string.uppercased()
  }
}
Run Code Online (Sandbox Code Playgroud)

希望这个逻辑可以让我们在不在第一页时将寻呼机字符串设为大写。

现在让我们在页面更新时通过 stringSubject 发送值:

pager.$page.sink { 
  $0 == 1 ? stringSubject.send("lowercase") : stringSubject.send("uppercase") 
}
Run Code Online (Sandbox Code Playgroud)

如果我们正确地理解了这个逻辑,那么小写将始终为小写,而大写将始终为大写。不幸的是,事实并非如此。这是一个示例输出:

pager.page = 1 // lowercase
pager.page = 2 // uppercase
pager.page = 3 // UPPERCASE
pager.page = 4 // UPPERCASE
pager.page = 1 // LOWERCASE
pager.page = 1 // lowercase
Run Code Online (Sandbox Code Playgroud)

这样做的原因是当我们订阅主题时,我们检查了pager.page...的值,但是更新pager.page是触发主题关闭的原因,因此pager.page还没有更新的值,因此主题执行了错误的分支。

我已经尝试通过在下沉之前与主题zippager.$page起来解决这个问题:

stringSubject.zip(pager.$page).eraseToAnyPublisher().sink { ...same code... }
Run Code Online (Sandbox Code Playgroud)

以及combineLatesting它:

stringSubject.combineLatest(pager.$page).eraseToAnyPublisher().sink { ...same code... }
Run Code Online (Sandbox Code Playgroud)

但这会导致完全相同的观察行为(在前一种情况下)或同样不受欢迎的行为,除了更多(在后一种情况下)。

我怎样才能在当前页面的主题sink关闭?

mat*_*att 6

问题是您实际上并没有使用Combine 框架的强大功能。您不应该使用sink闭包来查看任何值。这颠覆了Combine的全部意义!相反,您应该让该值作为您构建的组合管道的一部分流入您的sink

让我们从您的 StringPager 开始:

class StringPager {
    @Published var page = 1
    @Published var string = "this is a test"
}
Run Code Online (Sandbox Code Playgroud)

让我们有一个具有 StringPager 的视图控制器类,以及一些设置其字符串或页码的测试按钮:

class ViewController: UIViewController {
    let pager = StringPager()
    var storage = Set<AnyCancellable>()
    override func viewDidLoad() {
        // ...
    }
    @IBAction func doButton(_ sender: Any) {
        let i = Int.random(in: 1...4)
        print("setting page to \(i)")
        self.pager.page = i
    }
    @IBAction func doButton2(_ sender: Any) {
        let s = ["Manny", "Moe", "Jack"].randomElement()!
        print("setting string to \(s)")
        self.pager.string = s
    }
}
Run Code Online (Sandbox Code Playgroud)

你会看到这个测试台的想法是什么。我们单击第一个按钮或第二个按钮,然后将寻呼机的页面或字符串随机设置为一个新值,并在控制台中报告它是什么。

我们现在的目标是创建一个组合管道,它会在每次字符串或页面更改时根据寻呼机的页面是否为 1 来吐出大写或非大写版本的寻呼机字符串。我会在viewDidLoad. 监视多个发布者中的任何一个更改并在其中任何一个更改时报告其当前值的 Combine 运算符是combineLatest,所以这就是我将使用的:

    pager.$page.combineLatest(pager.$string)
        .map {p,s in p > 1 ? s.uppercased() : s}
        .sink {print($0)}.store(in:&storage)
Run Code Online (Sandbox Code Playgroud)

就是这样!现在我将点击按钮几次,让我们看看我们得到了什么:

this is a test
setting page to 3
THIS IS A TEST
setting page to 3
THIS IS A TEST
setting page to 1
this is a test
setting string to Jack
Jack
setting string to Manny
Manny
setting page to 1
Manny
setting page to 1
Manny
setting page to 4
MANNY
setting string to Manny
MANNY
setting string to Moe
MOE
setting string to Jack
JACK
Run Code Online (Sandbox Code Playgroud)

如您所见,每次我们更改页面或字符串时,我们都会从管道的末端得到一个新值,而且它是正确的值!如果页面大于 1,我们得到字符串的大写版本;如果页面为 1,我们将按原样获取字符串。任务完成!


nay*_*yem 1

我从你的意图中了解到的是,我认为你想控制页码的小写大写。但是您的逻辑达到了框架预期工作之外的程度Combine正如@user1046037在你的问题中的评论之一提到的:

Combine与突变无关。相反,你应该用它来随着时间的推移改变你的价值观。

所以不应该是page订阅者触发了发布者的价值突变string。相反,您会故意更改 的值string。然后您可以将该值转换为绑定到page. 这些逻辑应该进入对象本身。让我们看看我的意思:

class StringPager {
    @Published var page = 0
    @Published var string = "lorem ipsum"

    private var cancellableBag = Set<AnyCancellable>()

    init() {

        let publisher = $page
            .map { [unowned self] in
                return $0 == 1 ? self.string.lowercased() : self.string.uppercased()
        }

        publisher
            .eraseToAnyPublisher()
            .assign(to: \.string, on: self)
            .store(in: &cancellableBag) // must store the subscriber to get the events
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,当您更改页面值时,您将获得当时字符串将保存的字符串值的预期大小写版本。

let pager = StringPager()
pager.$string.sink { print($0) }
pager.page = 1 // lorem ipsum
pager.page = 2 // LOREM IPSUM
pager.page = 3 // LOREM IPSUM
pager.page = 4 // LOREM IPSUM
pager.page = 1 // lorem ipsum
pager.page = 1 // lorem ipsum
Run Code Online (Sandbox Code Playgroud)

当您需要将字符串更新为先前设置值以外的任何值时,它将独立于页面转换。这是什么意思?

pager.string = "new value" // new value
Run Code Online (Sandbox Code Playgroud)

直到你故意再次设置你的页面:

pager.page = 3 // NEW VALUE
Run Code Online (Sandbox Code Playgroud)