Extension for default empty value for optional publisher? - swift

I'm trying to pass in an optional publisher to my view's .onReceive. I don't want to force wrap it like this:
let refreshPublisher: AnyPublisher<Void, Never>?
var body: some View {
Group {
...
}
.onReceive(refreshPublisher!) {...}
}
So I'm passing an empty default value like this:
var body: some View {
Group {
...
}
.onReceive(refreshPublisher ?? Empty<Void, Never>().eraseToAnyPublisher()) {...}
}
This is really verbose and want to add an extension on Optional publishers something like this:
extension Optional where Wrapped == Publisher { // Incorrect syntax, doesn't compile
func orEmpty() -> AnyPublisher<Void, Never> { ... }
}
This way, I can end or doing something like this:
var body: some View {
Group {
...
}
.onReceive(refreshPublisher.orEmpty()) {...}
}
Is something like this possible or a better way to handle optional publishers in non-optional chains?

To your specific question, it's certainly possible. I just wouldn't do it this way.
Note the addition of Combine. before Publisher. There's a type called Optional.Publisher that shadows Combine.Publisher and that's what is causing your error.
extension Optional where Wrapped: Combine.Publisher {
func orEmpty() -> AnyPublisher<Wrapped.Output, Wrapped.Failure> {
self?.eraseToAnyPublisher() ?? Empty().eraseToAnyPublisher()
}
}
Instead of doing it this way, I'd make the default value Empty and not use Optional at all:
let refreshPublisher: AnyPublisher<Void, Never>
init(refreshPublisher: AnyPublisher<Void, Never> = Empty().eraseToAnyPublisher()) {
self.refreshPublisher = refreshPublisher
}
I'd also rework this so that the caller can pass any publisher they want rather than forcing AnyPublisher on them:
let refreshPublisher: AnyPublisher<Void, Never>
init<Refresh>(refreshPublisher: Refresh) where Refresh: Publisher,
Refresh.Output == Void, Refresh.Failure == Never {
self.refreshPublisher = refreshPublisher.eraseToAnyPublisher()
}
init() {
self.init(refreshPublisher: Empty())
}

Related

How can I map to a type in Combine?

I have numerous pages where users may input information. They may input the fields with dates, numbers, or text.
I am trying to receive all changes in Combine and get their outputs as Encodable so I can easily upload the results to the network.
A String is Encodable, so I thought this would be easy but I cannot get this to work in Combine. I get a compiler error:
Cannot convert return expression of type 'Publishers.Map<Published.Publisher, Encodable>' to return type 'Published.Publisher'
There is a workaround where I add another property in SampleTextHandler that is #Published var userTextEncodable: Encodable but that's not what I want to do.
import Combine
protocol FieldResponseModifying {
var id: String { get }
var output: Published<Encodable>.Publisher { get }
}
struct SampleTextWrapper {
var output: Published<Encodable>.Publisher {
// Cannot convert return expression of type 'Publishers.Map<Published<String>.Publisher, Encodable>' to return type 'Published<Encodable>.Publisher'
handler.$userTextOutput.map { $0 as Encodable}
}
let id = UUID().uuidString
let handler = SampleTextHandler()
}
class SampleTextHandler {
#Published var userTextOutput = ""
init () { }
}
Combine uses generics heavily. For example, the type returned by your use of map is Publishers.Map<Published<Value>.Publisher, Encodable>. So you could declare your property like this:
var output: Publishers.Map<Published<Encodable>.Publisher, Encodable> {
handler.$userTextOutput.map { $0 as Encodable}
}
But now your property's type depends closely on how it is implemented. If you change the implementation, you'll have to change the type.
Instead, you should almost certainly use the “type eraser” AnyPublisher, like this:
var output: AnyPublisher<Encodable, Never> {
handler.$userTextOutput
.map { $0 as Encodable }
.eraseToAnyPublisher()
}
You're probably going to run into another issue down the line, due to your use of the Encodable existential. When you hit that, you'll want to post another question.

Swift Generics and Protocols

Given the below, this will throw a compile time error about the protocol not being able to adhere to itself and only struct/enum can adhere to the protocol. This seems to defeat the purpose of being able to use protocols in generics. I'm trying to understand why this doesn't work, but if I remove the generic and just put the protocol where 'Z' is everything is fine. It seems antithetical to what protocols and generics should be allowed for.
**Edit for question clarity: I need to take a type of Any that can be cast to a dictionary of [String:MyProtocol] and pass it into the method printEm. printEm must use the generic as it will be instantiating instances of the class Z.
protocol MyProtocol {
init()
var whoAmI:String { get }
}
func genericPassing(unknownThing:Any) {
let knownThing = unknownThing as? [String:MyProtocol]
if(knownThing != nil){
self.printEm(knownThing)
}
}
func printEm<Z:MyProtocol>(theThings:[String:Z]) {
let zCollection:[Z] = []
for thing in theThings {
print(thing.whoAmI)
zCollection.append(Z())
}
}
**Edited printEm to illustrate why generic is needed.
** Edit for more complex code. The two primary requirements are the use of a generic to call Z() and the ability to take an Any and somehow type check it and/or cast it so that it can be used in a genericized method.
private func mergeUpdates<Z:RemoteDataSyncable>(source:inout Z, updates:[WritableKeyPath<Z, Any>:Any]) throws {
for key in updates.keys {
let value = updates[key]!
let valueDict = value as? [String:[WritableKeyPath<RemoteDataSyncable, Any>:Any]]
if(valueDict != nil) {
var currentValueArray = source[keyPath: key] as? [RemoteDataSyncable]
if(currentValueArray != nil) {
self.mergeUpdates(source: &currentValueArray!, updates: valueDict!)
}
else {
throw SyncError.TypeError
}
}
else {
source[keyPath: key] = value
}
}
}
private func mergeUpdates<Z:RemoteDataSyncable>(source:inout [Z], updates:[String:[WritableKeyPath<Z,Any>:Any]]) {
for key in updates.keys {
var currentObject = source.first { syncable -> Bool in
return syncable.identifier == key
}
if(currentObject != nil) {
try! self.mergeUpdates(source: &currentObject!, updates: updates[key]!)
}
else {
var newSyncable = Z()
try! self.mergeUpdates(source: &newSyncable, updates: updates[key]!)
source.append(newSyncable)
}
}
}
This is a perfect example of why protocols do not conform to themselves. In your code Z is MyProtocol, so Z() is MyProtocol(). How would that work? What type is it? How big is it? You then can't put them into a [Z], since they might be different types.
You mean to pass arbitrary MyProtocols and call init on the type of each element:
func printEm(theThings:[String: MyProtocol]) {
var zCollection:[MyProtocol] = []
for thing in theThings.values {
print(thing.whoAmI)
zCollection.append(type(of: thing).init())
}
}
When I suggested using closures, this is the kind of thing I mean. This Updater can accept arbitrary ReferenceWritableKeyPaths, and when you pass it Any update value, it'll assign it to every key path that can accept it. That's kind of useless, but shows the technique. (Keep in mind that the updater is retaining the object, so that may be a problem that you need to address.)
class Updater {
private(set) var updaters: [(Any) -> ()] = []
func add<Root, Value>(keyPath: ReferenceWritableKeyPath<Root, Value>, on root: Root) {
updaters.append { value in
if let value = value as? Value {
root[keyPath: keyPath] = value
}
}
}
func updateAll(with value: Any) {
for updater in updaters {
updater(value)
}
}
}
class Client {
var updateMe: Int = 0
}
let client = Client()
let updater = Updater()
updater.add(keyPath: \.updateMe, on: client)
updater.updateAll(with: 3)
client.updateMe // 3
The key lesson in this code is that the generic types on the add are erased (hidden) by the (Any) -> () closure at compile-time. And the runtime as? check is done inside that closure, where the types are all known.

How to convert a publisher to a CurrentValueSubject?

I have a publisher that my view's onReceive is subscribed to. Instead of duplicating logic in onAppear as well, I'd like the publisher in the onReceive to fire on first subscribe.
Is there a way to convert a publisher to a CurrentValueSubject? Here's what I'm trying to do:
var myPublisher: CurrentValueSubject<Void, Never> {
Just(()) // Error: Cannot convert return expression of type 'Just<()>' to return type 'CurrentValueSubject<Void, Never>'
}
Is this possible or a better way to do this?
You can't convert publishers to CurrentValueSubject at will because it's a very specific type of publisher. Ask yourself whether you really need the type of myPublisher to be CurrentValueSubject<Void, Never> or if an AnyPublisher<Void, Never> would do.
var myPublisher: AnyPublisher <Void, Never> {
Just(()).eraseToAnyPublisher()
}
Alternatively, if all you're trying to do is create an instance of CurrentValueSubject that has () as its initial value you could use this:
var myPublisher: CurrentValueSubject<Void, Never> {
CurrentValueSubject<Void, Never>(())
}
You can assign a publisher to a CurrentValueSubject which you can then use in your code. Without duplicating the logic.
var oldPublisher: AnyPublisher <Void, Never>
private var cancellables = Set<AnyCancellable>()
var myCurrentValueSubject: CurrentValueSubject<Void, Never> {
let subject = CurrentValueSubject<Void, Never>(())
oldPublisher
.assign(to: \.value, on: subject)
.store(in: &cancellables)
return subject
}

RxSwift protocol and concrete types

I'm having trouble with a BehaviorRelay that has a protocol type and using it on concrete types. Here's my code:
protocol Item {
var title: { get }
}
struct Can: Item {
let title = "Can"
}
let canRelay = BehaviorRelay<Can?>(value: nil)
func handle(item: BehaviorRelay<Item?>) {
// do something with item here
}
handle(item: canRelay) // can't do this?
I assumed I would be able to call handle(item:) but it's not the case because the arguments don't match. I get that they don't match, but Can is a type of Item so shouldn't this be possible?
Can may be a subtype of Item, but BehaviorRelay<Can> is not a subtype of BehaviorRelay<Item>.
Also, you should not be passing BehaviorRelays around in code. Pass Observables instead.
Once you know these two rules, you end up with:
func handle(item: Observable<Item?>) {
// do something with item here
}
handle(item: canRelay.map { $0 })

ReSwiftRecorder Add Action with property

Recently I have used ReSwift API, And I want to add ReSwiftRecorder too!
The sample of ReSwiftRecorder in Github is very simple app
I need to to something more complicated. I have an object which get data from server and I need to It reloads its data when app is not connected to net. Here is my code:
AppState:
struct AppState: StateType {
var menus: Result<[Menu]>?
}
MenuReducer:
func menusReducer(state: Result<[Menu]>?, action: Action) -> Result<[Menu]>? {
switch action {
case let action as SetMenusAction:
return action.menus
default:
return state
}
}
AppReducer:
struct AppReducer: Reducer {
func handleAction(action: Action, state: AppState?) -> AppState {
return AppState(
menus: menusReducer(state: state?.menus, action: action),
)
}
}
MenuActions:
struct SetMenus: Action {
let menus: Result<[Menu]>
}
I know I need to change MenuAction to Something like this:
let SetMenusActionTypeMap: TypeMap = [SetMenusAction.type: SetMenusAction.self]
struct SetMenusAction: StandardActionConvertible {
static let type = "SET_MENU_ACTION"
let menus: Result<[Menu]>
init() {}
init(_ standardAction: StandardAction) {}
func toStandardAction() -> StandardAction {
return StandardAction(type: SetMenusAction.type, payload: [:], isTypedAction: true)
}
}
but I got error on init functions
Return from initializer without initializing all stored properties
when I set a initializer code the error disappear but app does not restore saved data! How can I fix it?
You will want to add serialization/deserialization code. The menus property needs to be set. Also, you will want to serialize that property as payload:
let SetMenusActionTypeMap: TypeMap = [SetMenusAction.type: SetMenusAction.self]
struct SetMenusAction: StandardActionConvertible {
static let type = "SET_MENU_ACTION"
let menus: Result<[Menu]>
init() {
self.menus = // however you initialize that
}
init(_ standardAction: StandardAction) {
let maybeMenus = standardAction.payload["menus"] as? [Menu]?
self.menus = // create Result from Optional<[Menu]>
}
func toStandardAction() -> StandardAction {
let maybeMenus = self.menus.asOptional // Cannot serialize Result itself
return StandardAction(type: SetMenusAction.type, payload: ["menus" : maybeMenus], isTypedAction: true)
}
}
So problems I see here: JSON serialization depends on Dictionary representation of your payload data, i.e. the properties of your object. Can Result be serialized directly? I guess not, so you need to convert it, probably easiest to nil.
All in all, the payload is the key you missed and now you have to figure out how to use it with the data you have at hand. Also, it makes me a bit suspicious that the Result type itself is part of the AppState. I expected it to be reduced away or handled before dispatching an action, like SettingMenusFailedAction instead of ChangeMenusAction(result:) or similar. Just as a sidenote: actions should be more than typed property setters.