Array of generics with metatypes (using AlamofireObjectMapper) - swift

I suspect I may be making the same mistake as described by Rob in this post here in that I should be doing this whole thing another way, but with that in mind:
I'm trying to use AlamofireObjectMapper in a generic way. It has a protocol
public protocol Mappable
I then have various model classes that adopt it
class Dog: Mappable
class Cat: Mappable
class Bird: Mappable
I have this method
func loadEntityArray<T: Mappable>(type: T.Type)
and the reason this is generic is because it calls a function load() that needs a completion block that uses this generic param. The 'type' argument is never actually used, but you can't make a func generic without the generic type being in the func's parameter list.
func load(completion:(Response<T, NSError> -> Void))
loadEntityArray is called from another method
func letsgo() { loadEntityArray(Dog.self); loadEntityArray(Cat.self) }
So far so good, this all works. But I want to pass an array of which models to load to letsgo() and I can't work out how to do this. If I change letsgo() to
func letsgo<T:Mappable>(models: [T.Type]) {
for mod in models {
loadEntityArray(mod)
}
}
and then call letsgo() with 1 param like
letsgo([Dog.self])
it works, but as soon as I have an array of 2 or more, I get a compiler error 'cannot convert value of type NSArray to expected argument type [_.Type]' I don't now how I would explicitly type this array either.
letsgo([Dog.self, Cat.self])
I've tried various permutations and nothing seems to work. Am I doing something impossible here? It seems to me the compiler has enough information at compile time for this to work, so I'm not sure if this is a syntax thing or I'm doing something wrong here with generics.

Looking at your function :
func letsgo<T:Mappable>(models: [T.Type])
Its model parameter should be an Array of all the same Type. So an array of only Dog Types for example. That's why
letsgo([Dog.self])
works but
letsgo([Dog.self, Cat.self])
won't as it has multiple Types.
The solution :
Use Mappable.Type directly :
func loadEntityArray(type: Mappable.Type) {}
func letsgo(models: [Mappable.Type]) {
for mod in models {
loadEntityArray(mod.self)
}
}
And cast your array of Types as an array of Mappable Types :
letsgo([Dog.self, Cat.self] as [Mappable.Type])
Hope this achieves what you're looking for !

So in the end I came to the conclusion that this is not possible. I changed the code such that I pass in an enum to letsgo() and in a switch statement on that enum I call loadEntityArray(Dog.self) etc. etc. with an explicitly coded call, for each of my possible types. Then the compiler can see all the possible types and is happy.

Related

Swift 5: how to specify a generic type conforming to protocol when declaring a variable

I am on Swift 5. I have a protocol:
protocol Pipe {
associatedtype T
func await() -> Void
func yield( to: Any, with listener: Selector ) -> Void
}
And I would like to reference an instance of this protocol somewhere in code. That is, I want foo : , or a variable of generic type T implementing Pipe. Per this documentation: https://docs.swift.org/swift-book/ReferenceManual/GenericParametersAndArguments.html
I tried writing:
var imageSource: <Pipe T>
and any permutation of said symbols, ie imageSource: but the syntax is wrong across all cases.
In fact, T conform to two protocols, Renderable and Pipe, so I really want:
var imageSource: <Pipe, Renderable T>
Syntax wise this is gibberish, but semantically it's not an uncommon use case.
__________________ EDIT after two answers have been given __________
I tried simplifying the Pipe protocol for this post, but now I realize I simplified it too much. In my code base it's
protocol Pipe {
associatedtype T
func await() -> Void
func yield( to: Any, with listener: Selector ) -> Void
func batch() -> [T]
}
That's why there's a T there. But it's not crucial, I can drop the batch() -> [T] if I am able to write what I want above.
An associated type is used when you want your protocol to work with a variety of types, think a Container protocol that might have several methods all dealing with one contained type.
But your protocol is not that, it doesn't need to know any other types to specify the necessary behavior, so get rid of the associated type.
protocol Pipe {
func await() -> Void
func yield( to: Any, with listener: Selector ) -> Void
}
class Foo {
var imageSource: Pipe & Renderable
}
This is called a generalized existential, and is not available in Swift. A protocol with an associated type describes other types; it is not a type itself and cannot be the type of a variable, or put into a collection.
This specific protocol doesn't make a lot of sense, since you don't use T anywhere. But what you would need to do is pull this into the containing type:
struct Something<Source> where Source: Pipe & Renderable {
var imageSource: Source
}
I suspect you really want to redesign this in a different way, however. This looks like a fairly common misuse of protocols. You probably want Pipe and Renderer types that are structs (or even just functions). Without knowing what the calling code looks like, I can't say precisely how you would design it.
If you remove T (which isn't being used here), then Max's answer will address this issue. Protocols without associated types have an implicit existential type, and so you can treat them somewhat as "normal" types (assigning them to variables or putting them in collections).

Swift: casting un-constrained generic type to generic type that confirms to Decodable

Situation
I have a two generic classes which will fetch data either from api and database, lets say APIDataSource<I, O> and DBDataSource<I, O> respectively
I will inject any of two class in view-model when creating it and view-model will use that class to fetch data it needed. I want view-model to work exactly same with both class. So I don't want different generic constraints for the classes
// sudo code
ViewModel(APIDataSource <InputModel, ResponseModel>(...))
// I want to change the datasource in future like
ViewModel(DBDataSource <InputModel, ResponseModel>(...))
To fetch data from api ResponseModel need to confirms to "Decodable" because I want to create that object from JSON. To fetch data from realm database it need to inherit from Object
Inside ViewModel I want to get response like
// sudo code
self.dataSource.request("param1", "param2")
If developer tries to fetch api data from database or vice-versa it will check for correct type and throws proper error.
Stripped out version of code for playground
Following is stripped out version of code which shows what I want to achieve or where I am stuck (casting un-constrained generic type to generic type that confirms to Decodable)
import Foundation
// Just to test functions below
class DummyModel: Decodable {
}
// Stripped out version of function which will convert json to object of type T
func decode<T:Decodable>(_ type: T.Type){
print(type)
}
// This doesn't give compilation error
// Ignore the inp
func testDecode<T:Decodable> (_ inp: T) {
decode(T.self)
}
// This gives compilation error
// Ignore the inp
func testDecode2<T>(_ inp: T){
if(T.self is Decodable){
// ??????????
// How can we cast T at runtime after checking T confirms to Decodable??
decode(T.self as! Decodable.Type)
}
}
testDecode(DummyModel())
Any help or explanation that this could not work would be appreciated. Thanks in advance :)
As #matt suggests, moving my various comments over to an answer in the form "your problem has no good solution and you need to redesign your problem."
What you're trying to do is at best fragile, and at worst impossible. Matt's approach is a good solution when you're trying to improve performance, but it breaks in surprising ways if it impacts behavior. For example:
protocol P {}
func doSomething<T>(x: T) -> String {
if x is P {
return "\(x) simple, but it's really P"
}
return "\(x) simple"
}
func doSomething<T: P>(x: T) -> String {
return "\(x) is P"
}
struct S: P {}
doSomething(x: S()) // S() is P
So that works just like we expect. But we can lose the type information this way:
func wrapper<T>(x: T) -> String {
return doSomething(x: x)
}
wrapper(x: S()) // S() simple, but it's really P!
So you can't solve this with generics.
Going back to your approach, which at least has the possibility of being robust, it's still not going to work. Swift's type system just doesn't have a way to express what you're trying to say. But I don't think you should be trying to say this anyway.
In the method that fetch data I will check type of generic type and if it confirms to "Decodable" protocol I will use it to fetch data from api else from database.
If fetching from the API vs the database represents different semantics (rather than just a performance improvement), this is very dangerous even if you could get it to work. Any part of the program can attach Decodable to any type. It can even be done in a separate module. Adding protocol conformance should never change the semantics (outwardly visible behaviors) of the program, only the performance or capabilities.
I have a generic class which will fetch data either from api or database
Perfect. If you already have a class, class inheritance makes a lot of sense here. I might build it like:
class Model {
required init(identifier: String) {}
}
class DatabaseModel {
required init(fromDatabaseWithIdentifier: String) {}
convenience init(identifier: String) { self.init(fromDatabaseWithIdentifier: identifier )}
}
class APIModel {
required init(fromAPIWithIdentifier: String) {}
convenience init(identifier: String) { self.init(fromAPIWithIdentifier: identifier )}
}
class SomeModel: DatabaseModel {
required init(fromDatabaseWithIdentifier identifier: String) {
super.init(fromDatabaseWithIdentifier: identifier)
}
}
Depending on your exact needs, you might rearrange this (and a protocol might also be workable here). But the key point is that the model knows how to fetch itself. That makes it easy to use Decodable inside the class (since it can easily use type(of: self) as the parameter).
Your needs may be different, and if you'll describe them a bit better maybe we'll come to a better solution. But it should not be based on whether something merely conforms to a protocol. In most cases that will be impossible, and if you get it working it will be fragile.
What you'd really like to do here is have two versions of testDecode, one for when T conforms to Decodable, the other for when it doesn't. You would thus overload the function testDecode so that the right one is called depending on the type of T.
Unfortunately, you can't do that, because you can't do a function overload that depends on the resolution of a generic type. But you can work around this by boxing the function inside a generic type, because you can extend the type conditionally.
Thus, just to show the architecture:
protocol P{}
struct Box<T> {
func f() {
print("it doesn't conform to P")
}
}
extension Box where T : P {
func f() {
print("it conforms to P")
}
}
struct S1:P {}
struct S2 {}
let b1 = Box<S1>()
b1.f() // "it conforms to P"
let b2 = Box<S2>()
b2.f() // "it doesn't conform to P"
This proves that the right version of f is being called, depending on whether the type that resolves the generic conforms to the protocol or not.

Get the class or struct that conforms to a protocol for use in function using Generics

My question if you have an Array of objects that conforms to a protocol. I want to iterate over the array calling a method on each member in the array. However the method I want to call is static and uses generics. I need to get the class of the element in the array to do this. how do you get the class of that object? Is it possible?
I am writing a library of generic functions in Swift.
I have a protocol called DBAble which has as function:
static func get<T: DBable >(id:Int) -> T?
I have an array of objects that conform to DBAble:
let objs:[DBAble] = []
I want to iterate over the array and call:
for obj in objs {
obj.get(id: anInt)
}
however I am getting this message:
Static member 'get' cannot be used on instance of type 'DBable.Protocol'
Is there a way of finding the class (or type of struct) of the object that conforms to the protocol? I understand that I can do:
if obj is User {
}
however this is not the solution I am looking for.
The problem with the application approach it that the type T in the bellow method
static func get<T: DBable >(id:Int) -> T?
has to be known at compile time, whereas the the dynamicType will give you the type at run time.
For anyone else considering this question the answer is no it is not possible to find the type for use in a generic function at runtime. As explained in the comments by #hamish
"element.dynamicType is the actual type of a given element in your array. Your get(_:) method has a generic parameter T that must be known at compile time"

Swift type conformance to Any

Does every type in swift, apart from functions, conform to Any? I understand that AnyObject represents all class types and Any is at a higher level than that, representing all other types. However I came across a situation recently where I had a function that looked like:
func myFunFunction(someArgument: Any) {
...
}
And when calling it with a String argument: myFunFunction("This is a string") I get an error saying that type String can't be converted to type Any.
Edit:
The above example is just that, an example. I wanted to simplify the code just to get an answer but here's my actual code:
I have a class final class CollectionViewBinder: NSObject, UICollectionViewDataSource, UICollectionViewDelegate { }
that serves as a collection view binding helper for ReactiveCocoa.
The init method:
init(collectionView: UICollectionView, dataSignal: SignalProducer<[[Any]], NoError>, supplementarySignal: SignalProducer<[[Any]], NoError>?) { ... }
If the type of dataSignal is SignalProducer<[[String]], NoError>, that's when I get the error saying type SignalProducer<[[String]], NoError> can't be converted to type SignalProducer<[[Any]], NoError> which would lead me to think my error is something to do with ReactiveCocoa possibly?
You are right about Any, apple docs say:
Any can represent an instance of any type at all, including function types.
The example you gave works for me without any problems. Usually in this kind of situations the error is in some other place but the compiler gives a misleading message.
What you expect is called "generic covariance" and is currently not supported by Swift.
https://nomothetis.svbtle.com/type-variance-in-swift

Swift Extensions for Collections

I'm working on a framework to make it easier to work with Key Value Observing and I've defined a protocol for converting native Swift types to NSObject as follows:
public protocol NSObjectConvertible {
func toNSObject () -> NSObject
}
Extending the builtin types was easy, simply defining the function to convert the given type to the appropriate NSObject:
extension Int8: NSObjectConvertible {
public func toNSObject () -> NSObject {
return NSNumber(char: self)
}
}
When I got to the Array type, I hit a number of snags, which I tried to work out. I didn't want to extend any array type, but only arrays whose element type was itself NSObjectConvertible. And naturally, needed Array to itself conform to the protocol.
After hunting around on SO, it looks like extending the Array type itself is a little harder because it's generic, but extending SequenceType can be done. Except that I can't both constrain the element type and declare its conformance to the protocol in the same declaration.
The following:
extension SequenceType where Generator.Element == NSObjectConvertible : NSObjectConvertible = {
public func toNSObject () -> NSObject {
return self.map() { return $0.toNSObject() }
}
}
Produces a compiler error:
Expected '{' in extension
And the carat points to the ":" where I'm trying to declare the protocol conformance. Removing the protocol conformance compiles without errors, but obviously doesn't help the case.
I'm not sure if this is a bug, or if Swift simply can't (or doesn't want to) support what I'm trying to do. Even if I simply define the extension, then try to take care of the conformance in the body, it produces the risk of passing sequences that don't really conform to what they should.
At best it's a hacky solution to just fail in cases where a sequence with non-conforming members are passed. I'd much rather let the compiler prevent it from happening.
(This is in Swift 2.1, Xcode 7.1.1)
You can't add the protocol conformance, unfortunately.