Collection of NSFetchRequests in Swift 3 - swift

I have an object that uses a dictionary of key paths to fetch requests. Each fetch request represents a different entity. This is all fine and working under Swift 2.3 and below. However, when I try to convert my project to Swift 3, I run into issues with the new generics for fetch requests. Fetch requests tend to have the type that they return associated with them, but my dictionary needs to have fetch requests that return all kinds of types. When I try to create a dictionary of type [String:NSFetchRequest<NSManagedObject>] or [String:NSFetchRequest< NSFetchRequestResult >] with fetch requests that are specifically of type NSFetchRequest<Message>. (Message being an NSManagedObject subclass) I get a compiler error:
Cannot convert value of type 'NSFetchRequest< Message >' to expected
dictionary
value type 'NSFetchRequest'
How can I downcast one fetch request to a more general fetch request type?

Apparently you can cast from one fetch request type to another. It's kind of clunky though. I can't imagine there being a situation where the cast would fail. In ObjC you wouldn't even need the cast.
class A: NSManagedObject {}
class B: NSManagedObject {}
let fetchRequestA = NSFetchRequest<A>()
let fetchRequestB = NSFetchRequest<B>()
let fetchRequests: [String:NSFetchRequest<NSManagedObject>] = [
"a" : fetchRequestA as! NSFetchRequest<NSManagedObject>,
"b" : fetchRequestB as! NSFetchRequest<NSManagedObject>,
]

Related

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.

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.

How to show Array of Dictionaries in a SwiftUI List?

After a few months of learning Swift by migrating parts of a legacy ObjC app, I'm looking forward to starting a new app in pure Swift - I'm hoping that working with pure Swift base classes will lead to less unwrapping and other bridging shenanigans.
However, very quickly I've found myself facing similar problems.
I want to read some JSON from a web service, and show in in a list implemented with SwiftUI - should be simple, right?
The data (actually read from the Twitter API) comes in, and I deserialise it,
do {
if let results = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments ) as? [String: Any] {
print(results)
if let followers = results["users"] as? [[String: Any]] {
print (followers.count)
print("followers class \(type(of: followers))")
} else {
print(results["errors"])
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
You'll see I print the class of followers, and this shows,
followers class Array<Dictionary<String, Any>>
...a nice array of Dictionaries, using Swift base classes. That would seem a reasonable data structure to present via a List in SwiftUI. However, it doesn't seem to be so simple, as the data elements need to be Identifiable (fair enough) but I don't have a struct for each element, and I don't want the overhead of processing the array into an array of structs carrying identifiers.
A bit of research, and it seems there's a solution available, as I can initialise List with an Array, something like the following,
var body: some View {
List (twitter.followers!, id: \.self) { follower in // <<< Compilation error
Text("\(follower["name"])")
}
}
However, that code gives the compilation error on the flagged line,
Protocol type 'Any' cannot conform to 'Hashable' because only concrete
types can conform to protocols
I think the issue is that the compiler sees followers as 'Any', rather than an Array, but why?
btw, I've seen the answer to this question, but it seems the List initialiser should be a more elegant solution, if I can get it to work...
You have to create a struct for follower/user and declare it as Decodable and Identifiable.
Then you will be able to use JSONDecoder to read the data from the struct into an array for you.
As a result your twitter.followers will be an array of identifiable objects and you will be able to use it in ForEach().

Type "Any" has no subscript members despite casting as AnyObject on Swift 3?

Recently converted code from earlier version of swift to swift 3. Got a lot of errors signifying that type "any" has no subscript members and I thought this could be fixed by casting as AnyObject, but the error persists (and therefore the code I post here does not have this cast in it). Here is the relevant code:
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell) {
let parentCellIndexPath = tblExpandable.indexPath(for: parentCell)
var address = ""
address = "\(newText)"
// TODO: add a pin to the map from input address
cellDescriptors[0][11].setValue(address, forKey: "primaryTitle")
location = cellDescriptors[0][11]["primaryTitle"]! as! String
tblExpandable.reloadData()
}
Note that cellDescriptors is defined earlier in the code as an NSMutableArray. The error shows up right after cellDescriptors[0] in both lines that it is in. Not sure how to fix this.
It's because you're using more than one subscript operator, because presumably this is something like an array of arrays. But NSMutableArray's subscript operator returns Any. As a result, cellDescriptors[0] is Any. You try to use [11] on the result, but Any doesn't accept subscripts because it's Any, not a collection type.
Casting to AnyObject doesn't help because AnyObject is also not a collection type.
What you should do is cast cellDescriptors[0] to something that accepts subscripts. The right choice depends on what kind of data you're storing in cellDescriptors, but it's presumably some kind of collection, probably an array type.
Another approach would be to change cellDescriptors to be a Swift type instead of NSMutableArray. You could specifically declare the types for each part of your data structure, and then type casting wouldn't be needed.

Converting a string to a class in Swift

I am using Alamofire to make a GET request and am using the ObjectMapper library to convert the response into its own class in Swift.
Alamofire.request(self.REST_METHOD!, self.REQUEST_URL, headers : ["Authentication_Token" : authToken]).responseJSON { response in
if response.response != nil && response.response?.statusCode == 200 {
let json = JSON((response.result.value as? NSDictionary)!)
let classType : AnyObject.Type = NSClassFromString(entityType)! as AnyObject.Type
//let model = Mapper<classType>.map(json.rawString())
}
}
The entityType variable can be one of many types i.e. User, Student, Teacher, Staff, etc. I am trying to dynamically create a class type and create the model based on that.
However it crashes on the line let classType : AnyObject.Type = NSClassFromString(entityType)! as AnyObject.Type giving the error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
Also when I uncomment the line let model = Mapper<classType>.map(json.rawString()), it gives me a compiler error:
classType is not a type
What is wrong with the above code
You're getting the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Because NSClassFromString is failing and returning nil, which then you are unwrapping, thereby causing the error.
I'm guessing that entityType contains a class name along the lines of myClass. Swift now uses namespaces, so to create a class from a string, the string must contain AppName.myClass.
You could either hardcode your app name, or use the following code to get it:
NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
There are other problems with your code too. When NSClassFromString succeeds, it will create an instance of the class. This cannot be cast to a type. Also, you can't pass a variable as a generic in Mapper<classType>, as Swift needs to know the class type at compile time. Instead you could change the Mapper class to take the type as a parameter. For example:
Mapper.map(classInstance.dynamicType, json.rawString())
Although now that I re-read your question, you're using a library for Mapper, and are therefore probably reluctant to change it.
And looking at the doco for ObjectMapper, it needs you to create a Mapper instance - ie. instead of Mapper<MyClass>.map you need Mapper<MyClass>().map.