在命令式 Swift 中,通常使用计算属性来提供对数据的便捷访问,而无需复制状态。
假设我有这个类用于命令式 MVC 使用:
class ImperativeUserManager {
private(set) var currentUser: User? {
didSet {
if oldValue != currentUser {
NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
// Observers that receive this notification might then check either currentUser or userIsLoggedIn for the latest state
}
}
}
var userIsLoggedIn: Bool {
currentUser != nil
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
如果我想使用Combine 创建一个反应式等效项,例如用于SwiftUI,我可以轻松添加@Published
到存储的属性以生成Publisher
s,但不能用于计算属性。
@Published var userIsLoggedIn: Bool { // Error: Property wrapper cannot be applied to a computed property
currentUser != …
Run Code Online (Sandbox Code Playgroud) 所以我正在尝试学习 SwiftUI 和 Combine。我通常通过制作一个简单的小费计算器来开始新技术。
我似乎收到了一个随机的“通话中的额外参数”。编码时出错这是我的 SwiftUI 文件
import SwiftUI
internal enum ReceiptRowType {
case subtotal
case tax
case total
case tip
case grandTotal
}
struct TipView: View {
@ObservedObject internal var adBannerView: BannerAdView = BannerAdView()
@ObservedObject internal var receiptViewModel: ReceiptViewModel
private let percentageFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .percent
return f
}()
var body: some View {
ZStack {
Color.white
.scaledToFit()
VStack {
if adBannerView.adHasLoaded {
adBannerView
.frame(maxHeight: adBannerView.adHeight)
.animation(.easeInOut(duration: 2.0))
}
BorderView()
Text(ARCHLocalizedStrings.receipt)
.foregroundColor(Color.gray)
BorderView() …
Run Code Online (Sandbox Code Playgroud) 在 SwiftUI 中,View
我有一个List
基于@FetchRequest
显示Primary
实体和通过关系连接Secondary
实体的数据。当我添加一个带有新的相关次要实体的新实体时,View
和它List
的更新正确Primary
。
问题是,当我Secondary
在详细视图中更新连接的项目时,数据库会更新,但更改未反映在Primary
列表中。显然,@FetchRequest
不会被另一个视图中的更改触发。
当我此后在主视图中添加新项目时,先前更改的项目最终会得到更新。
作为一种解决方法,我还更新Primary
了详细信息视图中实体的属性,并将更改正确传播到Primary
视图。
我的问题是:如何强制更新@FetchRequests
SwiftUI Core Data 中的所有相关内容?特别是,当我无法直接访问相关实体时/ @Fetchrequests
?
import SwiftUI
extension Primary: Identifiable {}
// Primary View
struct PrimaryListView: View {
@Environment(\.managedObjectContext) var context
@FetchRequest(
entity: Primary.entity(),
sortDescriptors: [NSSortDescriptor(key: "primaryName", ascending: true)]
)
var fetchedResults: FetchedResults<Primary>
var body: some View {
List {
ForEach(fetchedResults) { primary in
NavigationLink(destination: …
Run Code Online (Sandbox Code Playgroud) 我有一个模型,它是一个ObservableObject
. 它有一个Bool
属性,我想用这个Bool
属性来初始化一个@Binding
变量。
@ObservableObject
a转换为 a @Binding
?@State
是初始化 a 的唯一方法@Binding
吗?@ObservedObject
/ @EnvironmentObject
,而且我看到它很有用,但我不确定一个简单的按钮是否需要访问整个模型。import SwiftUI
import Combine
import SwiftUI
import PlaygroundSupport
class Car : ObservableObject {
@Published var isReadyForSale = true
}
struct SaleButton : View {
@Binding var isOn : Bool
var body: some View {
Button(action: {
self.isOn.toggle()
}) {
Text(isOn ? "On" : "Off")
}
} …
Run Code Online (Sandbox Code Playgroud) 我的应用程序包含一个资源密集型操作,该操作根据从 XML 提要中提取的数据填充数组。我不希望此操作锁定主线程(以及为数组提供新数据时的 UI),因此它是在后台完成的。
let dispatchQueue = DispatchQueue(label: "concurrent.queue", qos: .utility, attributes: .concurrent)
class XMLHandler: ObservableObject {
let context: NSManagedObjectContext
@Published var myArray: [CustomObject] = []
init(context: NSManagedObjectContext) {
self.context = context
}
...some code...
func populateArray {
dispatchQueue.async {
...xml parsing happens...
(xmlOutputObject) in
for x in xmlOutputObject {
self.myArray.append(x)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在其他地方,我的 SwiftUI 视图使用 myArray 来填充它的列表:
struct MyView: View {
@EnvironmentObject var handler: XMLHandler
var body: some View {
List{
ForEach(handler.myArray) { CustomObject in
... generate …
Run Code Online (Sandbox Code Playgroud) 我有这样的代码
func request(request: URLRequest) -> AnyPublisher<Data, Error> {
return Just(request)
.flatMap { request in
RequestManager.request(request) // returns AnyPublisher<Data, Error>
}
.eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
我收到编译错误:
实例方法 flatMap(maxPublishers:_:) 要求类型 Just.Failure(又名 Never)和 Error 是等价的
很明显,因为Just
has Never
asFailure
和.flatMap
requires Error
as Failure
,所以Never
!=Error
我看到两种方法:
使用正确的Publisher 而不是Just
,但我没有找到合适的人选。
使用诸如.mapError
, 之类的运算符.mapError { $0 as Error }
,但我不确定这是个好主意。
任何想法如何处理它?
更新:
使用更有意义
.setFailureType(to: Error.self)
Run Code Online (Sandbox Code Playgroud)
或者
.mapError { $0 as Error }
Run Code Online (Sandbox Code Playgroud) 如何让构成Combine框架的异步管道同步(串行)排队?
假设我有 50 个 URL,我想从中下载相应的资源,假设我想一次下载一个。我知道如何使用 Operation/OperationQueue 来做到这一点,例如使用一个 Operation 子类,该子类在下载完成之前不会声明自己已完成。我将如何使用Combine 做同样的事情?
目前我想到的只是保留一个剩余 URL 的全局列表并弹出一个,为一次下载设置一个管道,进行下载,然后在sink
管道中重复。这似乎不太像结合。
我确实尝试制作一组 URL 并将其映射到一组发布者。我知道我可以“生产”一个发布者,并使用flatMap
. 但后来我仍然在同时进行所有下载。没有任何组合方式以受控方式遍历阵列——或者有吗?
(我也想象过用 Future 做点什么,但我变得绝望了。我不习惯这种思维方式。)
如果我已将可取消集存储到 ViewController 中:
private var bag = Set<AnyCancellable>()
Run Code Online (Sandbox Code Playgroud)
其中包含多个订阅。
1 - 我应该在 deinit 中取消订阅吗?或者它会自动完成工作?
2 - 如果是这样,我如何取消所有存储的订阅?
bag.removeAll() is enough?
Run Code Online (Sandbox Code Playgroud)
还是我应该遍历集合并一一取消所有订阅?
for sub in bag {
sub.cancel()
}
Run Code Online (Sandbox Code Playgroud)
Apple 表示订阅一直有效,直到存储的 AnyCancellable 在内存中。所以我想解除分配可取消的bag.removeAll()
应该足够了,不是吗?
我不太明白如何在类中正确存储订阅者,以便它们持续存在但不会阻止对象被取消初始化。这是对象不会取消初始化的示例:
import UIKit
import Combine
class Test {
public var name: String = ""
private var disposeBag: Set<AnyCancellable> = Set()
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
test = nil
Run Code Online (Sandbox Code Playgroud)
当我assign
用 a替换sink
(在其中我正确声明[weak self]
)时,它实际上确实正确地进行了 deinit(可能是因为assign
访问self
的方式会导致问题)。
例如,如何在使用时防止强引用循环.assign
?
谢谢
我碰巧研究了 Apple 的新组合框架,在那里我看到了两件事
PassthroughSubject<String, Failure>
CurrentValueSubject<String, Failure>
有人可以向我解释它们的含义和用途吗?
reactive-programming declarative-programming ios swiftui combine