somu
(somu)
November 19, 2019, 2:03pm
#1
Aim:
I would like to know what is the preferred way to decode using Combine to parse only valid records and not provide default values for failures.
Questions:
I get the desired output, but is this a reasonable approach or is there a better way to do it ?
I couldn't find something like tryDecode
(like tryMap
), or could other more suitable operator be used to achieve this ?
Based on the WWDC example, I have modified it slightly.
Code:
I am returning nil
when parsing fails and then use compactMap
not sure if there is a better way to do it.
import Foundation
import Combine
//Model
public struct CarRecord : Codable {
var name : String
var doors : Int
}
//Notification
extension Notification.Name {
public static let didPost = Notification.Name("didPost")
}
func postNotifications() {
let d1 = #"{"name":"aaa","doors":2}"#.data(using: .utf8)! //Good record
let d2 = #"{"name":"ccc"}"#.data(using: .utf8)! //Faulty record
let d3 = #"{"name":"ddd","doors":5}"#.data(using: .utf8)! //Good record
let datas = [d1, d2, d3]
for data in datas {
NotificationCenter.default.post(name: .didPost,
object: nil,
userInfo: ["Car" : data])
}
}
//Combine
let cancellable = NotificationCenter.default.publisher(for: .didPost)
.compactMap { $0.userInfo?["Car"] as? Data }
.flatMap { data in
Just(data)
.decode(type: CarRecord.self, decoder: JSONDecoder())
.map { Optional($0) } //added
.catch { _ in
Just(nil) //modified
}
}
.compactMap { $0 } //added
.sink {
print("attempt: \($0)")
}
postNotifications()
Output:
attempt: CarRecord(name: "aaa", doors: 2)
attempt: CarRecord(name: "ddd", doors: 5)
Lantua
November 20, 2019, 3:19pm
#2
You can also run compactMap
directly,
let cancellable = NotificationCenter.default.publisher(for: .didPost)
.compactMap { $0.userInfo?["Car"] as? Data }
.compactMap { try? JSONDecoder().decode(CarRecord.self, from: $0) }
.sink {
print("attempt: \($0)")
}
1 Like
somu
(somu)
November 20, 2019, 3:56pm
#3
@Lantua Thanks a ton !!, this is so much cleaner.
Even if decoder throws an exception we are still safe because of try?
and wouldn't be unsubscribed to the publisher. Really like this solution.
I guess I only thinking about the operators and completely missed the fact that we could decode directly with the operator.
somu
(somu)
November 20, 2019, 4:08pm
#4
@Lantua I am not sure if I am missing something but once I add .receive(on: RunLoop.main)
the sink
subscriber is not receiving values.
let cancellable6 = NotificationCenter.default.publisher(for: .didPost)
.compactMap { $0.userInfo?["Car"] as? Data }
.compactMap { try? JSONDecoder().decode(CarRecord.self, from: $0) }
.receive(on: RunLoop.main)
.sink {
print("attempt6: \($0)")
}
I am not sure if the subscriber was cancelled
mayoff
(Rob Mayoff)
November 20, 2019, 5:11pm
#5
It's possible that you're running into the problem described in this thread:
My message gets lost if I make subject to receive on a scheduler.
Here's my code to reproduce.
import Foundation
import Combine
let control = PassthroughSubject<Int,Never>()
let pipe = control.receive(on: RunLoop.main).sink(receiveValue: { print($0) })
control.send(233)
RunLoop.main.run()
// No print!
I expected to receive 233, but it won't.
This works if I remove .receive(on: RunLoop.main).
let control = PassthroughSubject<Int,Never>()
let pipe = control.sink(receiveValue: { print($0) })
…
1 Like
somu
(somu)
November 20, 2019, 5:19pm
#6
Thanks a lot @mayoff for pointing me to the relevant thread, I think I will reply to that thread with my example
somu
(somu)
November 20, 2019, 7:21pm
#7
Many Thanks to @Lantua @mayoff , the issue of .receive(on: RunLoop.main)
not sending through values to the subscribers seems to be fixed in iOS 13.3 Beta.