你好👋🏼我花了10多个小时来实现Combine
框架,但不能清楚地理解如何链接Publisher
和Subscriber
。在例子中,我只想从Theme
类调用setTheme
funk,并自动更新Game
类中的game
变量。我知道如何用didSet
实现它,但主要目标是用Combine
实现它。谢谢你的帮助。
import SwiftUI
import Combine
import Foundation
class Theme: ObservableObject {
@Published private(set) var choosenTheme: Color? // Publisher right?
func setTheme(with color: Color?) {
if let unwrappedColor = color {
self.choosenTheme = unwrappedColor
} else {
self.choosenTheme = nil
}
}
}
class Game: ObservableObject {
@Published var game: String? // Subscriber right?
private let gameTheme = Theme()
private var cancellables = Set<AnyCancellable>()
init() {
addSubscribers()
}
private func addSubscribers() { // I think something wrong here
gameTheme.$choosenTheme
.map(createGame)
.sink { [weak self] (returnedString) in
print("Value from sink (String(describing: returnedString))")
self?.game = returnedString
}
.store(in: &cancellables)
}
private func createGame(for theme: Color?) -> String? {
if let unwrappedTheme = theme {
print(unwrappedTheme)
return String("(unwrappedTheme)")
} else {
return nil
}
}
}
// Test:
var testTheme = Theme()
testTheme.setTheme(with: .orange)
var testGame = Game()
print(testGame.game) // Should be Orange
testTheme.setTheme(with: .blue)
print(testGame.game) // Should be Blue
testTheme.setTheme(with: nil)
print(testGame.game) // Should be nil
您遇到的问题是,有两个实例的Theme
您在这里创建的一个:
// Test:
var testTheme = Theme()
和Game
:
private let gameTheme = Theme()
那么你正在改变第一个主题,并期望第二个主题改变。要解决这个问题,你必须将Theme
注入到Game
中。
首先,您需要对Game
进行一些更改,以允许Theme
被注入:
class Game: ObservableObject {
@Published var game: String?
private let gameTheme: Theme
private var cancellables = Set<AnyCancellable>()
init(gameTheme: Theme) {
self.gameTheme = gameTheme
addSubscribers()
}
接下来,你必须在测试时执行该注入:
var testTheme = Theme()
testTheme.setTheme(with: .orange)
var testGame = Game(gameTheme: testTheme)
如果你这样做,你会看到正确的变化反映在打印。
如果你愿意,你可以大大简化这两个类:
class Theme: ObservableObject {
@Published private(set) var chosenTheme: Color?
func setTheme(with color: Color?) {
chosenTheme = color
}
}
class Game: ObservableObject {
@Published var game: String?
private let gameTheme: Theme
init(gameTheme: Theme) {
self.gameTheme = gameTheme
addBindings()
}
private func addBindings() {
gameTheme
.$chosenTheme
.map(createGame)
.assign(to: &$game)
}
private func createGame(for theme: Color?) -> String? {
theme.map { $0.description }
}
}
根本不应该改变。您正在做的是设置Theme
实例的主题。然而,在你的Game
课上,你听的是另一个。
你现在看到问题了吗?要么用你要更新的主题初始化你的Game
,要么让Theme
成为singleton
。
试试这个:
import SwiftUI
import Combine
import Foundation
class Theme: ObservableObject {
static var shared = Theme()
@Published private(set) var choosenTheme: Color? // Publisher right?
func setTheme(with color: Color?) {
if let unwrappedColor = color {
self.choosenTheme = unwrappedColor
} else {
self.choosenTheme = nil
}
}
}
class Game: ObservableObject {
@Published var game: String? // Subscriber right?
private let gameTheme = Theme.shared
private var cancellables = Set<AnyCancellable>()
init() {
addSubscribers()
}
private func addSubscribers() { // I think something wrong here
gameTheme.$choosenTheme
.map(createGame)
.sink { [weak self] (returnedString) in
print("Value from sink (String(describing: returnedString))")
self?.game = returnedString
}
.store(in: &cancellables)
}
private func createGame(for theme: Color?) -> String? {
if let unwrappedTheme = theme {
print(unwrappedTheme)
return String("(unwrappedTheme)")
} else {
return nil
}
}
}
// Test:
var testGame = Game() //Game initialization should precede Theme because now the listener starts listening for the orange change
var testTheme = Theme.shared
testTheme.setTheme(with: .orange)
print(testGame.game) // Should be Orange
testTheme.setTheme(with: .blue)
print(testGame.game) // Should be Blue
testTheme.setTheme(with: nil)
print(testGame.game) // Should be nil