swift type erasure understanding, Cannot convert value of type 'AnyMapper<Payload, [Post]>' to expected element type 'AnyMapper<Payload, User> - arrays

struct Payload {}
struct User {}
struct Post {}
protocol Mapper {
associatedtype PayloadType
associatedtype ResultType
func map(_ payload: PayloadType) -> ResultType
}
class AnyMapper<P, R>: Mapper {
private var _mapClouser: (P) -> R
init<M: Mapper>(_ mapper: M) where M.PayloadType == P, M.ResultType == R {
_mapClouser = mapper.map(_:)
}
func map(_ payload: P) -> R {
_mapClouser(payload)
}
}
class UserMapper: Mapper {
func map(_ payload: Payload) -> User {
return User()
}
}
class PostsMapper: Mapper {
func map(_ payload: Payload) -> [Post] {
return [Post(), Post(), Post()]
}
}
let userMapper = AnyMapper(UserMapper())
let postsMapper = AnyMapper(PostsMapper())
var array: [AnyMapper] = [userMapper, postsMapper] <<< Error: Cannot convert value of type 'AnyMapper<Payload, [Post]>' to expected element type 'AnyMapper<Payload, User>'
I tried to put 2 objects into array, but I get this error: Cannot convert value of type 'AnyMapper<Payload, [Post]>' to expected element type 'AnyMapper<Payload, User>'
Can someone explain to me how to fix it?

AnyMapper by itself is not a type. It is a type constructor. Essentially, it is a “function” that
runs at compile time,
takes types as arguments named P and R, and
returns a type.
So for example if you say AnyMapper<Payload, User>, that's a compile-time function call to the type constructor AnyMapper, passing the arguments Payload and User. It returns a type, which we just refer to using the same syntax, AnyMapper<Payload, User>.
Often, Swift can deduce the argument types, so although you don't pass them explicitly, Swift passes them for you.
That's what's happening on these two lines:
let userMapper = AnyMapper(UserMapper())
let postsMapper = AnyMapper(PostsMapper())
Each of those lines “calls“ AnyMapper after deducing the P and R arguments. We can make the P and R arguments explicit, and also make the returned types explicit, like this:
let userMapper: AnyMapper<Payload, User> = AnyMapper<Payload, User>(UserMapper())
let postsMapper: AnyMapper<Payload, [Post]> = AnyMapper<Payload, [Post]>(PostsMapper())
So now we can see that you're creating two objects of two different types.
You next write this:
var array: [AnyMapper] = [userMapper, postsMapper]
Since you're not explicitly passing type arguments to AnyMapper, you're asking Swift to deduce the arguments. It does so by looking at the first element of the array literal, userMapper. Based on that, it deduces P = Payload and R = User, so it acts like you wrote this:
var array: [AnyMapper<Payload, User>] = [userMapper, postsMapper]
But postsMapper is not an AnyMapper<Payload, User>, so Swift can't compile that statement.
What all this means is that AnyMapper erases only the wrapped Mapper's specific type, but not its PayloadType and ResultType associated types.
It's not clear why you would want to put userMapper and postsMapper in the same array anyway. They have different result types, so you wouldn't be able to treat their results generically. Perhaps if you explain why you think you need to put them both in a single array, we can give you better guidance.

[Post] and User are not the same type. You don't need an erasing type to put them into the same array.
var array: [any Mapper] = [UserMapper(), PostsMapper()]

Related

How to check that object of type `Any` is an array of concrete class implementing some protocol

I have faced an issue.
Consider I have one protocol and two classes implementing it:
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
then at some point I'm creating an array and passing it to function:
var array = [A]()
func update(object: Any){
}
update(object: array)
from that function update I would like to pass object to another function if it mets another function's conditions:
func process<T: Initiatable>(array: T){
/* ... */
}
So how can I check that object of type Any is an array of concrete class implementing Initiatable protocol? I would like to write something like
func update(object: Any){
if let array = object as Array<T: Initiatable>{
process(array: array)
}
}
But that's doesn't work. Code like:
func update(object: Any){
if let array = object as [Initiatable]{
process(array: array)
}
}
func process(array: [Initiatable]){ }
Compiles fine, but that's not what I want – process function should receive an array of concrete implementation of Initiatable so at some point it could use:
func process<T: Initiatable>(array: [T]){
/* other code */
T.init()
}
So is there any way to do this? Thank you very much in advance!
There's a few parts to this question:
Generating the array of types
Your array declaration is expecting an array of A objects rather than A types. To generate an array with A types, you could pass in a Postfix self expression: (link)
var array = [ A.self ]
This would define array as an array of A.Type, called the Metatype Type (same link).
You could also generate an empty array with this metatype type:
var array:[A.Type] = []
If you wanted an array with both A.self and B.self you could either specify it as [Any]...
var array:[Any] = [A.self,B.self]
...or, make use of the Initiatable protocol you created:
var array:[Initiatable.Type] = [A.self,B.self]
Downcasting an array to an array of types in your update method
You were having trouble downcasting an Any object to an array of types.
Here's my updated update method:
func update(object: Any){
if let array = object as? [Initiatable.Type] { //1
process(array: array)
}
}
You can now perform an optional downcast on the array that is being past into your update method. Downcast it to an array of metadata types of Initiatable: (This is the only line I modified from your method)
Receiving a type as a parameter in the process method
I'm assuming that you just want your process method to receive an array of types, and instantiate a variable based on one of those types. You didn't mention which element in the array, so I've just gone with the first.
func process(array: [Initiatable.Type]){ //1
if let firstType = array.first { //2
let firstObject = firstType.init() //3
}
}
The process method can receive an array of types that adopt the Initiatable protocol.
I'm using optional binding to get at the value of the first element in the array. This should be a type.
Instantiate an object based on the first element in the array.
If your array is nonempty, then you can solve this by grabbing the runtime type of one element from the array:
func update(object: Any){
if let array = object as? [Initiatable]{
process(array: array)
} else {
// Not initiatable
}
}
func process(array: [Initiatable]) { // no need for generics here
guard let first = array.first else {
// Unable to determine array type
// throw or return or whatever
}
// type(of: first) gives A or B
type(of: first).init()
}
If the array is empty, then I don’t know of a way to do this in Swift (as of Swift 3, anyway).
The static type of object is Any, so you need to use the runtime type of object in order to get anywhere.
However, Swift (and most languages with generics) only resolve type parameters like T based on the static types of the values you pass. Therefore your whole approach with process<T: Initiatable>(array: [T]) is a dead end: T can only take on a type the compiler already knew when you called process().
Swift does give you access to runtime types: type(of: object) will return (for example) Array<A>. So close! But:
AFAIK, there is currently no way to extract type parameters from a metatype, i.e. to get A from Array<A>, unless the compiler already knows the type parameter statically.
You can get the string "Array<A>" and extract the string A from it, but AFAIK there is no way to map strings back to types in Swift unless the type is an Objective-C class.
In short, I think you’ve just hit a limit of Swift’s metatype / reflection capabilities.
Unfortunately I can't think of a solution in a given constraints. It seems like you are trying to achieve compile-time polymorphism at runtime.
Using generics implies that compiler understands what class will be called. But using Any implies that at compile time it could be anything and it would be known only at runtime.
The closest solutions that I can propose are:
// Either use generic `update` function
func updateGeneric<T: Initiatable>(array: Array<T>){
process(array: array)
}
// or explicitly convert `Any` to the class,
// so compiler understands what implementation of `process` to call
func updateExplicit(object: Any) {
if let array = object as? [A] {
process(array: array)
}
if let array = object as? [B] {
process(array: array)
}
}
Is this help you,
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
class Test<T : Initiatable>
{
func update(object: Any){
if let array = object as? [T]{
process(array: array)
}
}
func process(array: [T]){
}
}
I found a workaround to solve this problem:
func classTypeFrom(_ className: String) -> AnyClass!{
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)"
return NSClassFromString(classStringName)
}
return nil;
}
func update(object: Any){
if let array = object as? [Initiatable]{
let arrayTypeName = "\(type(of: ar))"
let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7)
if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{
process(array: array, type: arrayType)
}
}
}
func process(array: [Initiatable], type: Initiatable.Type){
var ar = array
let newObj = type.init()
ar.append(newObj)
}

Swift 3 unable to append array of objects, which conform to a protocol, to a collection of that protocol

Below I have pasted code which you should be able to paste into a Swift 3 playground and see the error.
I have a protocol defined and create an empty array of that type. I then have a class which conforms to the protocol which I try to append to the array but I get the below error.
protocol MyProtocol {
var text: String { get }
}
class MyClass: MyProtocol {
var text = "Hello"
}
var collection = [MyProtocol]()
var myClassCollection = [MyClass(), MyClass()]
collection.append(myClassCollection)
argument type '[MyClass]' does not conform to expected type 'MyProtocol'
Note that collection += myClassCollection returns the following error:
error: cannot convert value of type '[MyProtocol]' to expected argument type 'inout _'
This was working in earlier versions of Swift.
The only solution I have found so far is to iterate and add each element to the new array like so:
for item in myClassCollection {
collection.append(item)
}
Any help appreciated, thanks!
EDIT
The solution as show below is:
collection.append(contentsOf: myClassCollection as [MyProtocol])
The real issue is a misleading compiler error when you are missing "as [MyProtocol]"
The compiler error reads:
error: extraneous argument label 'contentsOf:' in call
collection.append(contentsOf: myClassCollection)
This error causes users to remove contentsOf: from the code which then causes the error I first mentioned.
append(_ newElement: Element) appends a single element.
What you want is append(contentsOf newElements: C).
But you have
to convert the [MyClass] array to [MyProtocol] explicitly:
collection.append(contentsOf: myClassCollection as [MyProtocol])
// or:
collection += myClassCollection as [MyProtocol]
As explained in Type conversion when using protocol in Swift, this
wraps each array element into a box which holds "something that conforms to MyProtocol", it is not just a reinterpretation
of the array.
The compiler does this automatically for a single value (that is why
for item in myClassCollection {
collection.append(item)
}
compiles) but not for an array. In earlier Swift versions, you
could not even cast an entire array with as [MyProtocol], you
had to cast each individual element.
Your trying to append an array when collection is only expecting individual items. For example, changing collection to this compiles:
var collection = [[MyProtocol]]()
here is a way you can go about adding two arrays together:
func merge<T: MyProtocol>(items: inout [T], with otherItems: inout [T]) -> [T] {
return items + otherItems
}
var myClassCollection = [MyClass(), MyClass()]
var myClassCollection2 = [MyClass(), MyClass()]
let combinedArray = merge(items: &myClassCollection, with: &myClassCollection2)

Cannot convert value of type 'Array[String]' to expected argument type 'Set<String>'

In Swift, I have a function that I am passing an array to, and then using that array in another function. I keep getting this error:
Cannot convert value of type 'Array[String]' to expected argument type 'Set<String>'
#objc func getProductInfo(productIDs: Array<String>) -> Void {
print(productIDs) //this works with correct data
SwiftyStoreKit.retrieveProductsInfo(productIDs) { result in
...
The rest works, and is tested when I pass in a regular array of ["Monthly", "Yearly", "etc..."].
["Monthly", "Yearly", "etc..."] is not an array, it's an array literal. Set can be implicitly initialized with an array literal.
let ayeSet: Set<String> = ["a"] // Compiles
But, it cannot be implicitly initialized with an array.
let bees: Array<String> = ["b"]
let beeSet: Set<String> = bees // Causes Compiler Error
However, if you explicitly initialize it, then it will work.
let sees: Array<String> = ["c"]
let seeSet: Set<String> = Set(sees) // Compiles
So, in your example explicitly initialization should work.
#objc func getProductInfo(productIDs: Array<String>) -> Void {
print(productIDs) //this works with correct data
SwiftyStoreKit.retrieveProductsInfo(Set(productIDs)) { result in
...
You just need to change you method parameter type. SwiftyStoreKit method is expecting a String Set. Your method declaration should be:
func getProductInfo(productIDs: Set<String>)
I've face the issue using the same lib.
This should work
SwiftyStoreKit.retrieveProductsInfo(Set(productIDs))

Generic type array do not take empty array as input

import Foundation
func insertionSort<T where T: Comparable>(var items:[T])-> [T] {
for (index, _) in items.enumerate().dropFirst() {
var j = index
while ((j > 0) && (items[j] < items[j-1])) {
swap(&items[j], &items[j-1])
j = j-1
}
}
return items
}
// Test the function
insertionSort([]) // Generic type array is not taking empty array
When I am trying to call insertionSort with empty array, I get
Cannot invoke 'insertionSort' with an argument list of type '([_])'
I am not able to figure out how to fix this.
To call generic functions in Swift, Swift needs to be able to infer the generic parameters.
One way giving the type information to Swift, is using an intermediate variable.
(Like noted in Lu_'s comment.)
let arr: [Int] = []
let result = insertionSort(arr)
Another way is using as.
let result = insertionSort([] as [Int])
(Remember, var parameter does not modify the actual argument. It just makes a mutable copy, but does not write it back to the original argument. Swift 3 removed var parameters, as it's so confusing. You may need to assign the return value of the function to a variable.)

Swift 2.2: cannot convert value of type '[B]' to specified type '[A]'

I'm officially confused why this is not working (there isn't much to explain here):
protocol A {
var value: Int { get set }
}
struct B: A {
var value: Int
}
let array: [B] = [B(value: 10)]
let singleAValue: A = array[0] // extracting works as expected
var protocolArray: [A] = []
protocolArray.append(singleAValue) // we can put the value inside the `protocolArray` without problems
print(protocolArray)
let newProtocolArray: [A] = array // but why does this conversion not work?
The array of the protocol type has a different memory representation than an array of B structs. Because an array of A can contain many different types of objects, the compiler has to create an indirection (a wrapper around the elements in the array) to ensure that they all have the same size.
Since this conversion is potentially costly (if the source array is large), the compiler forces you to make it explicit by mapping over the source array. You can write either this:
let newProtocolArray = array.map { $0 as A }
or this:
let newProtocolArray: [A] = array.map { $0 }
Both are equivalent.

Resources