开发者社区 问答 正文

Combine@Published属性:在更新期间从其他地方获取当前值

我的主要问题是,我试图解决(无证)的事实@Published属性在通知订阅方更改后才会更新该属性的值。我似乎找不到一个很好的方法来绕过它。

考虑以下人为组合Subject和@Published财产。首先,一个简单的类:

class StringPager {
    @Published var page = 1
    @Published var string = ""
}
let pager = StringPager()

然后是一个简单的通过主题:

let stringSubject = PassthroughSubject<String, Never>()

为了进行调试,让我们订阅String属性并打印出来:

pager.$string.sink { print($0) } 到目前一切尚好。接下来,让我们订阅主题并根据其值更改寻呼机:

stringSubject.sink { string in
  if pager.page == 1 {
    pager.string = string
  } else {
    pager.string = string.uppercased()
  }
}

希望,这个逻辑将允许我们使寻呼机字符串当我们不在第一页时就会出现。

现在,当页面更新时,让我们通过string Subject发送值:

pager.$page.sink { 
  $0 == 1 ? stringSubject.send("lowercase") : stringSubject.send("uppercase") 
}

如果我们已经正确地理解了这个逻辑,那么小写将总是被小写,而大写则总是被超感知。不幸的是,事情根本不是这样的。下面是一个示例输出:

pager.page = 1 // lowercase
pager.page = 2 // uppercase
pager.page = 3 // UPPERCASE
pager.page = 4 // UPPERCASE
pager.page = 1 // LOWERCASE
pager.page = 1 // lowercase

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

我两个人都试过了zip使.pager.$page在下沉之前:

stringSubject.zip(pager.$page).eraseToAnyPublisher().sink { ...same code... }

以及combineLatest指的是:

stringSubject.combineLatest(pager.$page).eraseToAnyPublisher().sink { ...same code... }

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

我怎么才能拿到电流页内主题sink结束?

展开
收起
游客5akardh5cojhg 2019-12-09 13:45:30 546 分享 版权
阿里云 AI 助理回答

你的问题涉及到Swift的Combine框架中如何正确处理多个异步更新和依赖属性的问题。你想要在stringSubject发送值时,根据pager.page的当前状态来决定如何更新pager.string。但是,由于异步和闭包捕获变量的机制,导致了你观察到的非预期行为。

解决这个问题的一个方法是确保你在处理主题发送的值时,能够访问到pager.page的最新状态。你可以通过使用.receive(on: RunLoop.main)来确保sink操作在主线程执行,并且利用.share()来确保所有订阅者看到的是同一个最新的状态。同时,可以考虑使用.debounce(for:)来避免因快速连续更改而产生的不必要的多次处理。

下面是一个修改后的示例代码,尝试解决你的问题:

import Combine

class StringPager {
    @Published var page = 1
    @Published var string = ""
}

let pager = StringPager()

let stringSubject = PassthroughSubject<String, Never>()

// 使用share保证所有订阅者共享同一状态
let sharedStringSubject = stringSubject.share()

// 订阅pager.$page的变化,但这里我们主要关注如何处理stringSubject
pager.$page
    .receive(on: RunLoop.main) // 确保在主线程处理
    .share() // 确保共享最新的page状态
    .sink { [weak pager] currentPage in
        guard let strongPager = pager else { return }
        sharedStringSubject
            .receive(on: RunLoop.main)
            .debounce(for: .milliseconds(100)) // 防止快速连续触发
            .sink { string in
                if currentPage == 1 {
                    strongPager.string = string
                } else {
                    strongPager.string = string.uppercased()
                }
            }
            .store(in: &cancellables)
    }
    .store(in: &cancellables)

// 发送值到stringSubject
pager.$page
    .receive(on: RunLoop.main)
    .sink { $0 == 1 ? stringSubject.send("lowercase") : stringSubject.send("uppercase") }
    .store(in: &cancellables)

// 订阅string以验证输出
pager.$string
    .sink { print($0) }
    .store(in: &cancellables)

// 示例更新
pager.page = 1 // 应输出 lowercase
pager.page = 2 // 应输出 UPPERCASE

这个解决方案的关键在于: - 使用.share()确保所有对stringSubject的订阅都能看到相同的状态。 - 在处理stringSubject的sink之前,通过.receive(on: RunLoop.main)确保在主线程上执行并等待可能的UI更新完成。 - 添加.debounce(for:)来防止短时间内多次触发导致的不必要处理。 - 注意捕获弱引用[weak pager]以避免循环引用。

请尝试上述代码看是否解决了你的问题。如果还有其他疑问或需要进一步的帮助,请随时告诉我。

有帮助
无帮助
AI 助理回答生成答案可能存在不准确,仅供参考
0 条回答
写回答
取消 提交回答
问答地址: