Swift 5 storing and passing KeyPaths - swift

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.

Related

Is there a way to get the name of a key path as a string? [duplicate]

How can you get a string value from Swift 4 smart keypaths syntax (e.g., \Foo.bar)? At this point I'm curious about any way at all, does not matter if it's complicated.
I like the idea of type information being associated with smart key path. But not all APIs and 3rd parties are there yet.
There's old way of getting string for property name with compile-time validation by #keyPath(). With Swift 4 to use #keyPath() you have to declare a property as #objc, which is something I'd prefer to avoid.
A bit late to the party, but I've stumbled upon a way of getting a key path string from NSObject subclasses at least:
NSExpression(forKeyPath: \UIView.bounds).keyPath
Short answer: you can't. The KeyPath abstraction is designed to encapsulate a potentially nested property key path from a given root type. As such, exporting a single String value might not make sense in the general case.
For instance, should the hypothetically exported string be interpreted as a property of the root type or a member of one of its nested types? At the very least a string array-ish would need to be exported to address such scenarios...
Per type workaround. Having said that, given that KeyPath conforms to the Equatable protocol, you can provide a custom, per type solution yourself. For instance:
struct Auth {
var email: String
var password: String
}
struct User {
var name: String
var auth: Auth
}
provide an extension for User-based key paths:
extension PartialKeyPath where Root == User {
var stringValue: String {
switch self {
case \User.name: return "name"
case \User.auth: return "auth"
case \User.auth.email: return "auth.email"
case \User.auth.password: return "auth.password"
default: fatalError("Unexpected key path")
}
}
usage:
let name: KeyPath<User, String> = \User.name
let email: KeyPath<User, String> = \User.auth.email
print(name.stringValue) /* name */
print(email.stringValue) /* auth.email */
I wouldn't really recommend this solution for production code, given the somewhat high maintenance, etc. But since you were curious this, at least, gives you a way forward ;)
For Objective-C properties on Objective-C classes, you can use the _kvcKeyPathString property to get it.
However, Swift key paths may not have String equivalents. It is a stated objective of Swift key paths that they do not require field names to be included in the executable. It's possible that a key path could be represented as a sequence of offsets of fields to get, or closures to call on an object.
Of course, this directly conflicts with your own objective of avoiding to declare properties #objc. I believe that there is no built-in facility to do what you want to do.
Expanding on #Andy Heard's answer we could extend KeyPath to have a computed property, like this:
extension KeyPath where Root: NSObject {
var stringValue: String {
NSExpression(forKeyPath: self).keyPath
}
}
// Usage
let stringValue = (\Foo.bar).stringValue
print(stringValue) // prints "bar"
I needed to do this recently and I wanted to ensure that I get a static type check from the compiler without hardcoding the property name.
If your property is exposed to Objective-C(i.e #objc), you can use the #keyPath string expression. For example, you can do the following:
#keyPath(Foo.bar)
#keyPath(CALayer.postion)
See Docs

Swift generic collection of Element cannot convert to collection of Any

I'm facing a problem I don't understand.
In my project, I want to make a collection of elements with some customised methods (like an update from a server). But when I try to group all these collections in an array, I get an error: "Cannot convert value of type MyCollection<someElement> to expected argument type MyCollection<Any>"
What I don't understand is that the same code with Array is working... Array isn't a collection?
// My collection which would contain an update method
class MyCollection<Element> {
var object:Element? = nil
}
let x = MyCollection<Int>()
var list = [MyCollection<Any>]()
list.append(x) //Cannot convert value of type 'MyCollection<In>' to expected argument type 'MyCollection<Any>'
let a = Array<Int>()
var lista = [Array<Any>]()
lista.append(a) //Doesn't get error at all...
I know I can do this with an array of the specific type but by grouping all of MyCollection in an array, I wish to use a code like :
func update() {
for e in list { // array of MyCollection<Any>
e.update()
}
}
Thank you in advance for your help ;)
Being able to convert from SomeType<Subtype> to SomeType<Supertype> is called covariance. In Swift, Array<T> is covariant on T by "compiler magic", and you can't do the same for your own types.
The type checker hardcodes conversions from Array to Array if there is a conversion from T to U. Similar rules exist for Optional and Dictionary. There's no mechanism for doing this with your own types.
Your own generic types are always invariant, meaning that there is never a conversion between SomeType<T> to SomeType<U>, as long as T and U are different types.
Let's imagine what would happen if the conversion on MyCollection were allowed. You could do:
let myCollectionInt = MyCollection<Int>()
let myCollectionAny: MyCollection<Any> = myCollectionInt // suppose you can do this
myCollectionAny.object = "string" // myCollectionAny.object is of type Any?, so this should be ok
We've set myCollectionAny.object to "string", but MyCollection is a reference type, so myCollectionInt.object should also be "string". But myCollectionInt.object is an Int?!
Of course this type-unsafety is also a problem with arrays, but the language designers have decided that casting arrays is a common enough thing to do, that disallowing it would do more hard than good.

Can I make a Swift data type infix operator?

So, I want to make an operator ('or') which will allow me to declare a variable like this:
var someNum: Int or Double
This bring an example. I want to actually use it on some custom made data types. But is it possible to make an operator for variable declarations that will allow for said variable to be one of two types depending on what its being assigned? I know what data types are possible of being entered, but unfortunately I would currently either assign it a type of 'Any' with a bunch of failsafe code implemented or change the original data types created. So I was just wondering if this is possible or might even exist.
I used this article as a reference, but from what I read I'm not sure if I can or how I would implement it for my needs.
Custom Operators in Swift
Thanks for any and all the help in advance.
You can't do this in the way you're asking. It's not possible syntactically to use a operator in a declaration like that.
What you can do is use an enum to distinguish the kinds:
enum NumericInput {
case integral(Int)
case fractional(Double)
}
and take that as the type of your variable:
var value: NumericInput
Then you say
value = .integral(someInteger)
You could do this with generics:
struct MyStruct<T>
{
var someNum: T
}
You can then explicitly state the dataType you wish to use by specifying the type on creation: let a = MyStruct<Int>(someNum: 4).
One thing Swift does that makes this all absolutely beautiful is derive the data type from the constructor, so you can also just do this:
let intStruct = MyStruct(someNum: 4)
let floatStruct = MyStruct(someNum: 5.0)
You can just declare the value with type Any.
For example,
var myVar: Any = shouldAssignDouble ? Double(20) : Float(20)
Later when you want to know if the actual type is a Float or Double, you can check it with
myVar is Double //returns true

How do I store a value of type Class<ClassImplementingProtocol> in a Dictionary of type [String:Class<Protocol>] in Swift?

I want to store a more specialized type in a Dictionary of type [String:SomeClass]. Here is some sample code illustrating my problem (also available to play with at https://swiftlang.ng.bluemix.net/#/repl/579756cf9966ba6275fc794a):
class Thing<T> {}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Vanilla>()
It produces the error ERROR at line 9, col 28: cannot assign value of type 'Thing<Vanilla>' to type 'Thing<Any>?'.
I've tried casting Thing<Vanilla>() as Thing<Flavor> but that produces the error cannot convert value of type 'Thing<Vanilla>' to type 'Thing<Flavor>' in coercion.
I've also tried to define the Dictionary as type [String:Thing<Any>] but that doesn't change anything either.
How do I create a collection of different Things without resorting to plain [String:AnyObject]?
I should also mention that the class Thing is not defined by me (in fact it's about BoltsSwift Tasks), so the solution to create a base class of Thing without a type parameter doesn't work.
A Thing<Vanilla> is not a Thing<Flavor>. Thing is not covariant. There is no way in Swift to express that Thing is covariant. There are good reasons for this. If what you were asking for were allowed without careful rules around it, I would be allowed to write the following code:
func addElement(array: inout [Any], object: Any) {
array.append(object)
}
var intArray: [Int] = [1]
addElement(array: &intArray, object: "Stuff")
Int is a subtype of Any, so if [Int] were a subtype of [Any], I could use this function to append strings to an int array. That breaks the type system. Don't do that.
Depending on your exact situation, there are two solutions. If it is a value type, then repackage it:
let thing = Thing<Vanilla>(value: Vanilla())
dict["foo"] = Thing(value: thing.value)
If it is a reference type, box it with a type eraser. For example:
// struct unless you have to make this a class to fit into the system,
// but then it may be a bit more complicated
struct AnyThing {
let _value: () -> Flavor
var value: Flavor { return _value() }
init<T: Flavor>(thing: Thing<T>) {
_value = { return thing.value }
}
}
var dict = [String:AnyThing]()
dict["foo"] = AnyThing(thing: Thing<Vanilla>(value: Vanilla()))
The specifics of the type eraser may be different depending on your underlying type.
BTW: The diagnostics around this have gotten pretty good. If you try to call my addElement above in Xcode 9, you get this:
Cannot pass immutable value as inout argument: implicit conversion from '[Int]' to '[Any]' requires a temporary
What this is telling you is that Swift is willing to pass [Int] where you ask for [Any] as a special-case for Arrays (though this special treatment isn't extended to other generic types). But it will only allow it by making a temporary (immutable) copy of the array. (This is another example where it can be hard to reason about Swift performance. In situations that look like "casting" in other languages, Swift might make a copy. Or it might not. It's hard to be certain.)
One way to solve this is adding an initialiser to Thing and creating a Thing<Flavor> that will hold a Vanilla object.
It will look something like:
class Thing<T> {
init(thing : T) {
}
}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Flavor>(thing: Vanilla())

Cannot convert value of type 'Int' to expected argument type '_?'

Note: I'm a rookie in Swift
I'm using Former.
I'm fetching data from a realm model.
let industries = realm.objects(Industry)
Then I try to define a list of InlinePickerItem from it:
$0.pickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, value: industry.id)
})
But XCode keeps saying: Cannot convert value of type 'Int' to expected argument type '_?', pointing to industry.id.
Am I missing something? I don't know if the issue comes from Former or from something that I don't understand in Swift. For example, what kind of type is _??
UPDATE:
After #dfri comment, attempt was unsuccessful. From my small understanding of Swift, I get that Swift gets lost. So I extracted the initialisation of the list of InlinePickerItem from the closure.
let industries = realm.objects(Industry)
let inlinePickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id)
})
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>(instantiateType: .Nib(nibName: "ProfileLabelCell")) {
$0.titleLabel.text = "CATEGORY".localized
}.configure {
$0.pickerItems = inlinePickerItems
}
The error is disappeared when calling InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id) but I get something new when assigning it to $0.pickerItems which now is:
Cannot assign value of type '[InlinePickerItem<Int>]' to type '[InlinePickerItem<String>]'
Hope this will provide you with some helpful hints.
Type mismatch when assigning array to different type array
After the re-factoring of your code (after "update") its now apparent what is the source of error.
Immutable catRow is of type InlinePickerRowFormer<ProfileLabelCell, String>. From the source of [InlinePickerRowFormer] we see the that the class and its property pickerItems is declared as follows
public class InlinePickerRowFormer<T: UITableViewCell, S where T: InlinePickerFormableRow>
: ... {
// ...
public var pickerItems: [InlinePickerItem<S>] = []
// ...
}
The key here is that for an instance InlinePickerRowFormer<T,S> its property pickerItems will be an array with elements of type InlinePickerItem<S>. In your example above S is String
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>
/* |
S = String */
Hence pickerItems is an array of InlinePickerItem<String> instances.
You try, however, to append the immutable inlinePickerItems to pickerItems, which means you're trying to assign an array of InlinePickerItem<Int> instances to an array with elements of type InlinePickerItem<String>; naturally leading to a type mismatch.
You can solve this type mismatch by:
Setting your catRow immutable to be of type InlinePickerRowFormer<ProfileLabelCell, Int>.