合并@Published属性:在更新过程中从其他位置获取当前值

时间:2019-12-09 04:17:39

标签: ios swift combine

我的主要问题是,我正在尝试解决SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); String datetime = sdf.format(new Date(*timestamp*)) 属性在通知订户有关更改之前不会更新属性值的事实(未记录)。我似乎无法解决这个问题。

请考虑以下@PublishedSubject属性的组合。首先,一个简单的类:

@Published

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

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

要调试,让我们订阅string属性并打印出来:

let stringSubject = PassthroughSubject<String, Never>()

到目前为止,一切都很好。接下来,让我们订阅主题并根据其值更改寻呼机:

pager.$string.sink { print($0) }

希望,只要我们不在首页上,此逻辑将使我们能够将寻呼机字符串大写。

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

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

如果我们正确理解了此逻辑,则小写将始终为小写,而大写将始终为大写。不幸的是,这根本不会发生。这是一个示例输出:

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.pagezip和主题一起来解决此问题:

pager.$page

以及stringSubject.zip(pager.$page).eraseToAnyPublisher().sink { ...same code... } 对其进行

combineLatest

但这会导致观察到的行为完全相同(在前一种情况下),或者除了其他行为(在后一种情况下)之外,同样具有不想要的行为。

如何在主题stringSubject.combineLatest(pager.$page).eraseToAnyPublisher().sink { ...same code... } 内的中获得当前页面

2 个答案:

答案 0 :(得分:1)

根据您的意图,我了解到,我认为您希望控制页码的小写大写。但是您在某种程度上将自己的逻辑视为 this.contactsService.getContacts().subscribe( res=>{ console.log(res); }, err=>{ console.log(err); }); 框架的预期工作。正如@user1046037对您的问题的评论之一提到:

  

Combine与突变无关。相反,您应该使用它来随时间转换您的值。

因此,不应触发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
    }
}

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

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

直到您有意再次设置页面:

pager.string = "new value" // new value

答案 1 :(得分:0)

问题是您实际上并没有使用Combine框架的功能。您不应该在任何值内使用sink闭包来 look 。这颠覆了合并的全部观点!相反,您应该让该值流入{em {1}} 作为构建的Combine管道的一部分。

让我们从您的StringPager开始吧:

sink

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

class StringPager {
    @Published var page = 1
    @Published var string = "this is a test"
}

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

我们现在的目标是创建一个合并管道,该管道将在每次字符串或页面更改时根据寻呼机的页面是否为1来吐出寻呼机字符串的大写或非大写版本。我将在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 } } 中进行操作。监视多个发布者中的任何一个更改并在其其中一个更改为viewDidLoad时报告其当前值的Combine操作符,这就是我要使用的:

combineLatest

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

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

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