RxSwift Two way binding with a BehaviorRelay<Any?> - swift

I create a generic variable. This variable can be connected to a textField or an other interface element like a switch, for example. So the type of my variable will be Any so that I can store a bool, a string or something else.
var test: BehaviorRelay<Any?> = BehaviorRelay<Any?>(value: nil)
Then I try to connect my interface elements to this variable
(self.switch.rx.value <-> viewModel.test).disposed(by: self.disposeBag)
Or
(self.textfield.rx.text <-> viewModel.test).disposed(by: self.disposeBag)
with this error Cannot convert value of type 'BehaviorRelay<Any?>' to expected argument type 'BehaviorRelay<Bool>' or Cannot convert value of type 'BehaviorRelay<Any?>' to expected argument type 'BehaviorRelay<String?>'
How can I do this with RxSwift?
You can find here my <-> func:
infix operator <->
func <-> <T>(property: ControlProperty<T>, variable: BehaviorRelay<T>) -> Disposable {
let bindToUIDisposable = variable.asObservable()
.bind(to: property)
let bindToVariable = property
.subscribe(onNext: { n in
variable.accept(n)
}, onCompleted: {
bindToUIDisposable.dispose()
})
return Disposables.create(bindToUIDisposable, bindToVariable)
}

(The two-way binding doesn't cause an infinite loop in this case because UI elements don't emit values when modified programmatically, they only do it when the user changes the value. If the user flips the switch, it gets pushed to the Subject [BehaviorRelay] which then pushes the value back to the switch, but it ends there.)
You would have to cast your Element to an Any? with map and map the Any? back into the proper type when going from the BehaviorRelay to the UI element. It's very clunky and unsafe to do that.
Better would be to make your BehaviorRelay's element the correct type. You're going to have to know the type in order to do anything useful with it anyway.

Related

SwiftUI: why does ModifiedContent conform to `some View` function return type?

// it compiles without any problems, regardless ModifiedContent is not a View
func modify(with viewModifier: some ViewModifier) -> some View {
let content: ModifiedContent<Self, some ViewModifier> = modifier(viewModifier)
return content
}
but if we write same in generic way, we got expected error message
func modify<V>(with viewModifier: some ViewModifier) -> V where V : View {
let content: ModifiedContent<Self, some ViewModifier> = modifier(viewModifier)
return content // error: Cannot convert return expression of type
// 'ModifiedContent<Self, some ViewModifier>' to return type 'V'
}
Here's your first, working declaration:
func modify(with viewModifier: some ViewModifier) -> some View
You used the some keyword twice in that function declaration. It is important to understand that some means different things in those two uses.
In the first use (viewModifier: some ViewModifier), the some keyword is “syntactic sugar” for a implicit, unnamed generic parameter. We can de-sugar the use of some there by replacing it with an explicit, named generic parameter, like this:
// Replace `some` in parameter position with an explicit generic parameter:
func modify<VM: ViewModifier>(with viewModifier: VM) -> some View
When you call that modify method, you as the caller choose a real type to replace the VM parameter. So you can call modify many times, with a different type to replace VM each time. The only constraint is the replacement type must always conform to the ViewModifier protocol.
The second use of some in your first function declaration (in -> some View) has a different meaning. It means the function will return a value whose type conforms to View, but the specific type is chosen by the body of the function. You as the function caller do not choose the type.
To summarize the differences:
The some keyword in parameter position
represents an implicit, unnamed generic type parameter;
can be de-sugared to an explicit, named generic parameter without changing the function body;
is replaced by a type chosen by the caller of the function.
The some keyword in return type position
represents a type known to conform to a protocol;
is a type chosen by the body of the function.
Now let's look at your second, broken function declaration:
func modify<V>(with viewModifier: some ViewModifier) -> V where V : View
This is like your first function declaration, except that you have replaced the some in return type position with the generic parameter V.
The problem is that these don't mean the same things. As I explained above, a return type of some View means the function body chooses the actual type, but a generic parameter is always chosen by the caller of the function.
That's why you get the error when your function tries to return content. There's no guarantee that content has type V. Your function knows so little about type V (you only know it conforms to View) that there's no way for the function to even create a value of type V (because the View protocol doesn't have any init requirements).
You also have this comment on your first, working function declaration:
// it compiles without any problems, regardless ModifiedContent is not a View
I assume you declared the modify(withViewModifier:) method in an extension View. That means Self conforms to View, so ModifiedContent<Self, some ViewModifier> conforms to View.
ModifiedContent is a generic View so you can return it as opaque result type some View.
when returning -> V where V : View ModifiedContent has to match any V type where it is called from, which is not possible.
see as well: https://forums.swift.org/t/how-to-use-a-generic-type-as-the-functions-return-type/37666/2
Thanks to #robmayoff and #jrturton i finally figured out.
ModifiedContent conforms to some View because ModifiedContent conditionally conforms to View. We can make sure of this by watching definition of ModifiedContent. If we scroll down, we can see:
extension ModifiedContent : View where Content : View, Modifier : ViewModifier { ... }

Swift method needs to sort a polymorphic collection of UInt32

Confused (and a little frustrated) with Swift5 at the moment.
I have a method:
func oidsMany(_ oids:Array<UInt32>) -> MCCommandBuilder {
let sorted_oids:[UInt32] = oids.sorted()
...
}
Discovered I have a case where I want to pass a Set to this method just as well. Either way, I'm going to sort an Array or a Set to an Array right away.
Waded through the many many many protocols that both Set and Array conform to, noticed that they both conform to [Sequence][1] and that Sequence responds to sorted. Perfect.
But when I change the above to:
func oidsMany(_ Sequence<UInt32>) -> MCCommandBuilder {
let sorted_oids:[UInt32] = oids.sorted()
...
}
I get the following error hints:
Cannot specialize non-generic type 'Sequence'
Member 'sorted' cannot be used on value of protocol type 'Sequence'; use a generic constraint instead
What's the right way to approach this? I could just add a second oidsMany(_ Set...) that casts its arg as an array and recalls. But I feel like I'm missing something fundamental here. My experience from other languages is not mapping over well here.
You can as the error message suggest use it as a generic constraint instead
func oidsMany2<Sortable: Sequence>(_ oids: Sortable) -> MCCommandBuilder where Sortable.Element: Comparable {
let sorted_oids:[Sortable.Element] = oids.sorted()
//...
}
if you only want to accept collections where the element is UInt32 you can change the where condition to
where Sortable.Element == UInt32

Swift 5 storing and passing KeyPaths

Let's say I have the following class:
class User: NSObject {
var name = "Fred"
var age = 24
var email = "fred#freddy.com"
var married = false
}
I want to be able to write a generic function that takes in a list of KeyPaths for a known class type, read the values and print to screen. The problem is, the I can't get the following code to compile as the type of the KeyPath's Value is not known, and will be different for each time. What do I have to do to make this work generically?
Consider the following:
struct KeyPathProperties<T> {
var name: String
var relatedKeyPaths: [KeyPath<T, Any>]
}
extension KeyPath where Root == User {
var properties: KeyPathProperties<Root> {
switch self {
case \Root.name:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
default:
fatalError("Unknown key path")
}
}
}
This line fails to compile:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
with this error:
Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'
This is what I wish to be able to do, for instance:
let myUser = User()
var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])
for keyPath in props.relatedKeyPaths {
print("Value: \(myUser[keyPath: keyPath])")
}
The above won't compile of course. Essentially I want to store keyPaths in an array at runtime, so I can generically at some point in time get values out of the User. I need to know if I can re-write the above in some way where the compiler can safely and correctly determine the type of the keyPath's value at runtime.
This is a conceptual use case for a much more complex architectural issue I'm trying to solve with hopefully less code.
MORE INFORMATION:
At runtime I wish to keep track of the properties that get modified - these properties are held in a modifiedProps array in each object / instance. At some point at runtime, I wish to be able to enumerate over this array of KeyPaths and print their values like so:
for modifiedKeyPath in self.modifiedProps {
print ("\(self[keyPath: modifiedKeyPath])"
}
In short - I need to be able to capture the generic type of the KeyPath within KeyPathProperties. How do I achieve this?
SIDE NOTE: I can already easily achieve this by using Swift 3 style string based KeyPaths (by adding #objc to the class properties). I can store an array of keyPaths as strings and later do:
let someKeyPath = #keyPath(User.email)
...
myUser.value(forKeyPath: someKeyPath)
I just cannot do this with Swift 4 KeyPaths generically.
The error tells you what your misconception is:
Cannot convert value of type 'KeyPath<User, Int>'
to expected element type 'KeyPath<User, Any>'
You seem to think that you can use a KeyPath<User, Int> where a KeyPath<User, Any> is expected, ostensibly on the grounds that an Int is an Any. But that's not true. These are generic types, and generic types are not covariant — that is, there is no substitution principle for generics based on their parameterized types. The two types are effectively unrelated.
If you need an array of key paths regardless of their parameterized types, you would need an array of PartialKeyPath or AnyKeyPath. It seems that in your use case the root object is the same throughout, so presumably you want PartialKeyPath.

Binder in RxSwift which accepts optional Any

I would like to create a Reactive extension to the UIButton which will take any object (value and reference type) and will enable the button based on the value. If it is nil I want to disable the button and enable if it there is value.
here is the code:
extension Reactive where Base: UIButton {
var isEnabledForModel: Binder<Any?> {
return Binder(base, binding: {
$0.isEnabled = $1 != nil
})
}
}
When I try to bind the observable which contains optional struct I get an error: Ambiguous reference to member 'bind(to: ). Is there any way to pass Any to the Binder or achieve it in different way? Maybe I want to make it too generic.
Yes, you are trying to make something that is too generic. For example, if you want the button disabled for a missing string, you probably also want it disabled for an empty string, but there is no way to express that in your binder.
That said, your code compiles fine for me. I suggest you clean your build folder and rebuild. Despite the fact that it compiles, the point of use becomes more complex than I think you would like.
let foo = Observable<Int?>.just(nil)
foo
.map { $0 as Any? }
.bind(to: button.rx.isEnabledForModel)
.disposed(by: bag)
Note that you have to manually map the Int? into a Any?. If you remove the map you will get an error "Generic parameter 'Self' could not be inferred". Because it's a generic parameter, the system won't look for possible matches through the inheritance tree and therefore won't accept the isEnabledForModel.

Can a condition be used to determine the type of a generic?

I will first explain what I'm trying to do and how I got to where I got stuck before getting to the question.
As a learning exercise for myself, I took some problems that I had already solved in Objective-C to see how I can solve them differently with Swift. The specific case that I got stuck on is a small piece that captures a value before and after it changes and interpolates between the two to create keyframes for an animation.
For this I had an object Capture with properties for the object, the key path and two id properties for the values before and after. Later, when interpolating the captured values I made sure that they could be interpolated by wrapping each of them in a Value class that used a class cluster to return an appropriate class depending on the type of value it wrapped, or nil for types that wasn't supported.
This works, and I am able to make it work in Swift as well following the same pattern, but it doesn't feel Swift like.
What worked
Instead of wrapping the captured values as a way of enabling interpolation, I created a Mixable protocol that the types could conform to and used a protocol extension for when the type supported the necessary basic arithmetic:
protocol SimpleArithmeticType {
func +(lhs: Self, right: Self) -> Self
func *(lhs: Self, amount: Double) -> Self
}
protocol Mixable {
func mix(with other: Self, by amount: Double) -> Self
}
extension Mixable where Self: SimpleArithmeticType {
func mix(with other: Self, by amount: Double) -> Self {
return self * (1.0 - amount) + other * amount
}
}
This part worked really well and enforced homogeneous mixing (that a type could only be mixed with its own type), which wasn't enforced in the Objective-C implementation.
Where I got stuck
The next logical step, and this is where I got stuck, seemed to be to make each Capture instance (now a struct) hold two variables of the same mixable type instead of two AnyObject. I also changed the initializer argument from being an object and a key path to being a closure that returns an object ()->T
struct Capture<T: Mixable> {
typealias Evaluation = () -> T
let eval: Evaluation
let before: T
var after: T {
return eval()
}
init(eval: Evaluation) {
self.eval = eval
self.before = eval()
}
}
This works when the type can be inferred, for example:
let captureInt = Capture {
return 3.0
}
// > Capture<Double>
but not with key value coding, which return AnyObject:\
let captureAnyObject = Capture {
return myObject.valueForKeyPath("opacity")!
}
error: cannot invoke initializer for type 'Capture' with an argument list of type '(() -> _)'
AnyObject does not conform to the Mixable protocol, so I can understand why this doesn't work. But I can check what type the object really is, and since I'm only covering a handful of mixable types, I though I could cover all the cases and return the correct type of Capture. Too see if this could even work I made an even simpler example
A simpler example
struct Foo<T> {
let x: T
init(eval: ()->T) {
x = eval()
}
}
which works when type inference is guaranteed:
let fooInt = Foo {
return 3
}
// > Foo<Int>
let fooDouble = Foo {
return 3.0
}
// > Foo<Double>
But not when the closure can return different types
let condition = true
let foo = Foo {
if condition {
return 3
} else {
return 3.0
}
}
error: cannot invoke initializer for type 'Foo' with an argument list of type '(() -> _)'
I'm not even able to define such a closure on its own.
let condition = true // as simple as it could be
let evaluation = {
if condition {
return 3
} else {
return 3.0
}
}
error: unable to infer closure type in the current context
My Question
Is this something that can be done at all? Can a condition be used to determine the type of a generic? Or is there another way to hold two variables of the same type, where the type was decided based on a condition?
Edit
What I really want is to:
capture the values before and after a change and save the pair (old + new) for later (a heterogeneous collection of homogeneous pairs).
go through all the collected values and get rid of the ones that can't be interpolated (unless this step could be integrated with the collection step)
interpolate each homogeneous pair individually (mixing old + new).
But it seems like this direction is a dead end when it comes to solving that problem. I'll have to take a couple of steps back and try a different approach (and probably ask a different question if I get stuck again).
As discussed on Twitter, the type must be known at compile time. Nevertheless, for the simple example at the end of the question you could just explicitly type
let evaluation: Foo<Double> = { ... }
and it would work.
So in the case of Capture and valueForKeyPath: IMHO you should cast (either safely or with a forced cast) the value to the Mixable type you expect the value to be and it should work fine. Afterall, I'm not sure valueForKeyPath: is supposed to return different types depending on a condition.
What is the exact case where you would like to return 2 totally different types (that can't be implicitly casted as in the simple case of Int and Double above) in the same evaluation closure?
in my full example I also have cases for CGPoint, CGSize, CGRect, CATransform3D
The limitations are just as you have stated, because of Swift's strict typing. All types must be definitely known at compile time, and each thing can be of only one type - even a generic (it is resolved by the way it is called at compile time). Thus, the only thing you can do is turn your type into into an umbrella type that is much more like Objective-C itself:
let condition = true
let evaluation = {
() -> NSObject in // *
if condition {
return 3
} else {
return NSValue(CGPoint:CGPointMake(0,1))
}
}