我想使用Combine 的@Published属性来响应属性的变化,但它似乎在属性发生变化之前发出信号,就像willSet观察者一样。以下代码:
import Combine
class A {
@Published var foo = false
}
let a = A()
let fooSink = a.$foo.dropFirst().sink { _ in // `dropFirst()` is to ignore the initial value
print("foo is now \(a.foo)")
}
a.foo = true
Run Code Online (Sandbox Code Playgroud)
输出:
foo 现在是假的
我希望接收器像didSet观察者一样在属性发生变化后运行,这样foo就可以了。有没有其他出版商发出信号,或者有一种制作这样的@Published作品的方式?
鉴于以下代码:
enum MyError: Error {
case someError
}
myButton.publisher(for: .touchUpInside).tryMap({ _ in
if Bool.random() {
throw MyError.someError
} else {
return "we're in the else case"
}
})
.replaceError(with: "replaced Error")
.sink(receiveCompletion: { (completed) in
print(completed)
}, receiveValue: { (sadf) in
print(sadf)
}).store(in: &cancellables)
Run Code Online (Sandbox Code Playgroud)
每当我点击按钮时,我都会得到we're in the else case直到Bool.random()为真 - 现在抛出一个错误。我尝试了不同的方法,但我无法捕获/替换/忽略错误并在点击按钮后继续。
在代码示例中,我希望有以下输出
we're in the else case
we're in the else case
replaced Error
we're in the else case
...
Run Code Online (Sandbox Code Playgroud)
相反,我得到finished了replaced error并且没有发出任何事件。 …
我希望所有发布者都执行,除非明确取消。我不介意AnyCancellable基于文档,它会自动调用的范围走出去,但是cancel在deinit这是不希望。
我曾尝试使用可取消的袋子,但AnyCancelable即使在出版商解雇了一个完成后仍然堆积如山。
我应该手动管理行李吗?我的印象store(in: inout Set)是为了方便管理可取消的实例,但它所做的只是推AnyCancellable入一个集合。
var cancelableSet = Set<AnyCancellable>()
func work(value: Int) -> AnyCancellable {
return Just(value)
.delay(for: .seconds(1), scheduler: DispatchQueue.global(qos: .default))
.map { $0 + 1 }
.sink(receiveValue: { (value) in
print("Got value: \(value)")
})
}
work(value: 1337).store(in: &cancelableSet)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
print("\(cancelableSet)")
}
Run Code Online (Sandbox Code Playgroud)
到目前为止我想出了什么,它工作正常,但让我想知道Combine框架中是否缺少某些东西,或者它不应该以这种方式使用:
class DisposeBag {
private let lock = NSLock()
private var cancellableSet = Set<AnyCancellable>()
func store(_ cancellable: AnyCancellable) {
print("Store cancellable: …Run Code Online (Sandbox Code Playgroud) 我创建了一个函数,该函数使用以下代码在 Swift Combine 中返回一个自定义发布者:
func customPubliher() -> AnyPublisher<Bool, Never> {
return Future<Bool, Never> { promise in
promise(.success(true))
}.eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
然后我使用以下代码订阅了这个发布者:
customPublisher()
.subscribe(on: DispatchQueue.global())
.map { _ in
print(Thread.isMainThread)
}
.sink(receiveCompletion: { _ in }, receiveValue: { value in
// Do something with the value received
}).store(in: &disposables)
Run Code Online (Sandbox Code Playgroud)
但是,即使我说行.subscribe(on: DispatchQueue.global()),当我做了申购,代码不是在不同的队列(已执行print的.map产出如此)。
但是,例如,如果我将自定义发布者替换为一个内置的 Combine 发布者Just()(见下文),则相同的代码将在不同的队列上正常执行:
Just(true)
.subscribe(on: DispatchQueue.global())
.map { _ in
print(Thread.isMainThread)
}
.sink(receiveCompletion: { _ in }, receiveValue: { value in …Run Code Online (Sandbox Code Playgroud) 我的主要问题是我正在尝试解决(未记录的)事实,即@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 …Run Code Online (Sandbox Code Playgroud) 我在一个项目中成功地使用了 PromiseKit,直到 Xcode 11 beta 破坏了 PK v7。为了减少外部依赖,我决定废弃 PromiseKit。处理链式异步代码的最佳替代品似乎是使用新组合框架的 Futures。
我正在努力使用 Combine 复制简单的 PK 语法
前任。简单的 PromiseKit 链式异步调用语法
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}
Run Code Online (Sandbox Code Playgroud)
我明白:
async/await 的 Swift 标准库实现将解决这个问题(async/await 尚不存在,尽管Chris Latter 本人有很多喋喋不休和参与)
我可以使用信号量进行复制(容易出错?)
flatMap 可用于链接 Futures
我想要的异步代码应该能够按需调用,因为它涉及确保用户登录。我正在努力解决两个概念性问题。
如果我将 Futures 包装在一个方法中,sink以处理结果,则该方法似乎在订阅者被调用之前超出了范围sink。
由于 Futures 只执行一次,我担心如果我多次调用该方法,我只会从第一次调用中得到旧的、陈旧的结果。要解决这个问题,也许我会使用 PassthroughSubject?这允许按需调用发布者。
问题:
//how is this done using Combine?
func startSync() {
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
Run Code Online (Sandbox Code Playgroud) 我只是在学习如何使用Combine。我有使用 Rx(RxSwift 和 RxJava)的经验,我注意到它非常相似。
然而,完全不同(有点烦人)的一件事是Publisher协议不对其Output和Failure类型使用泛型;它使用关联类型代替。
这意味着我无法指定多态Publisher类型(例如Publisher<Int, Error>)并简单地返回符合Publisher这些类型的任何类型。我需要AnyPublisher<Int, Error>改用,我被迫在eraseToAnyPublisher()所有地方都包括在内。
如果这是唯一的选择,那么我会忍受它。但是,我最近还了解了 Swift 中的不透明类型,我想知道是否可以使用它们来解决这个问题。
有没有办法,让我有,比方说,一个函数,返回some Publisher和使用的具体类型Output和Failure?
这似乎是不透明类型的完美案例,但我不知道是否有办法既使用不透明类型又指定关联类型。
我正在想象这样的事情:
func createPublisher() -> some Publisher where Output = Int, Failure = Error {
return Just(1)
}
Run Code Online (Sandbox Code Playgroud) 我有两个类嵌套在另一个类中,它是 SwiftUI 视图中的一个可观察对象。即使嵌套类中的属性声明为@Published,它们的值(当它们更改时)也不会在主视图中更新。
这里有人问过一个类似的问题,我可以用它来让它为两个子类之一工作,但不能同时为两个子类工作。
如何告诉 SwiftUI 视图绑定到嵌套的 ObservableObjects
这是模型:
class Submodel1: ObservableObject {
@Published var count = 0
}
class Submodel2: ObservableObject {
@Published var count = 0
}
class Model: ObservableObject {
@Published var submodel1: Submodel1 = Submodel1()
@Published var submodel2: Submodel2 = Submodel2()
}
Run Code Online (Sandbox Code Playgroud)
这是主要观点:
struct ContentView: View {
@ObservedObject var model: Model = Model()
var body: some View {
VStack {
Text("Count: \(model.submodel1.count)")
.onTapGesture {
self.model.submodel1.count += 1
}
Text("Count: \(model.submodel2.count)")
.onTapGesture {
self.model.submodel2.count += 1
} …Run Code Online (Sandbox Code Playgroud) 基本上我试图弄清楚我的 viewModel 何时更新,它会通知视图并刷新整个身体。如何避免这种情况。例如,如果我的视图 GoLiveView 已经呈现另一个视图 BroadcasterView,然后我的 goLiveViewModel 得到更新,GoLiveView 将被刷新,它会再次创建 BroadcasterView,因为 showBroadcasterView = true。正因为如此,它会在未来引起很多问题。
struct GoLiveView: View {
@ObservedObject var goLiveViewModel = GoLiveViewModel()
@EnvironmentObject var sessionStore: SessionStore
@State private var showBroadcasterView = false
@State private var showLiveView = false
init() {
goLiveViewModel.refresh()
}
var body: some View {
NavigationView {
List(goLiveViewModel.rooms) { room in // when goLiveViewModed get updated
NavigationLink(destination: LiveView(clientRole: .audience, room: room, showLiveView: $showLiveView))) {
LiveCell(room: room)
}
}.background(Color.white)
.navigationBarTitle("Live", displayMode: .inline)
.navigationBarItems(leading:
Button(action: {
self.showBroadcasterView = true
}, …Run Code Online (Sandbox Code Playgroud) 我正在尝试向模态呈现的视图导航栏添加一个关闭按钮。但是,在解雇后,我的视图模型deinit方法从未被调用。我发现问题在于它在navigationBarItem 中捕获self的位置。我不能只在navigationBarItem的动作中传递 a ,因为 View 是一个结构,而不是一个类。这是一个有效的问题还是只是缺乏知识?weak self
struct ModalView: View {
@Environment(\.presentationMode) private var presentation: Binding<PresentationMode>
@ObservedObject var viewModel: ViewModel
var body: some View {
NavigationView {
Text("Modal is presented")
.navigationBarItems(leading:
Button(action: {
// works after commenting this line
self.presentation.wrappedValue.dismiss()
}) {
Text("close")
}
)
}
}
}
Run Code Online (Sandbox Code Playgroud) combine ×10
swift ×10
ios ×5
swiftui ×3
asynchronous ×1
memory-leaks ×1
opaque-types ×1
xcode ×1