简体   繁体   中英

Swift: cannot convert value of type 'SomeType<T>' to expected argument type 'SomeType<_>'

I am using generics to build a paging library where I have code similar to this one (simplified):

struct PageData<T> {
    let item: T
    let pageNumber: Int
}

protocol PagedServiceProvider: class {
    func fetchPage<ItemType>(page: Int, completion: @escaping (PageData<ItemType>?) -> Void)
}


class TestPagedServiceProvider<ItemType>: PagedServiceProvider {
    var pageData: PageData<ItemType>?

    func fetchPage<ItemType>(page: Int, completion: @escaping (PageData<ItemType>?) -> Void) {
        completion(pageData) // COMPILATION ERROR HERE
    }
}

the call to completion(pageData) produces the following error:

Cannot convert value of type PageData<ItemType>? to expected argument type PageData<_>?

This workaround casting with as? gets rid of the error:

class TestPagedServiceProvider<ItemType>: PagedServiceProvider {
    var pageData: PageData<ItemType>?

    func fetchPage<ItemType>(page: Int, completion: @escaping (PageData<ItemType>?) -> Void) {
        completion(pageData as? PageData<ItemType>)
    }
}

Also if var pageData: PageData<ItemType>? is declared as a local variable inside the fetchPage<ItemType>... method, the error is gone as well.

NOTE: This gist provides additional context.

I am confused as to why the compiler requires an explicit cast (that looks redundant to me in this case).

Could it be a matter of placeholder types scope (the ItemType in the fetchPage<ItemType>... method is not the same as the ItemType for the TestPagedServiceProvider<ItemType> class)?

I tried giving them different names and using where clauses to no avail.

The following code worked for me.

struct PageData<T> {
    let item: T
    let pageNumber: Int
}

protocol PagedServiceProvider: class {
    associatedtype T
    var pageData: PageData<T>? { get set }
    func fetchPage(page: Int, completion: @escaping (PageData<T>?) -> Void)
}


class TestPagedServiceProvider: PagedServiceProvider {

    var pageData: PageData<String>?

    func fetchPage(page: Int, completion: @escaping (PageData<String>?) -> Void) {
        completion(pageData)
    }

}

I think the below code will work for you. I have passed String as the Generic type while instantiating the PageData object.

func fetchPage<String>(page: Int, completion: @escaping (PageData<String>?) -> Void) {

        let page = PageData<String>(item: "Item", pageNumber: 1)
        completion(page)
}

Your type signature says that the caller can pass any type as ItemType, but then you require the closure to accept a PageData<String>? .

fetchPage is not actually generic (it can't accept types chosen by the caller), so you can't claim it is. What you mean is to remove the <ItemType> and say what you require:

func fetchPage(page: Int, completion: @escaping (PageData<String>?) -> Void) {
              ^^                                          ^^^^^^

This, however, breaks your PagedServiceProvider protocol, because that protocol requires that TestPagedServiceProvider accept absolutely any ItemType, and it doesn't. You'll need to decide what you really mean. You might mean a protocol with an associated type here, which is a type that the implementation gets to pick rather than the caller.

protocol PagedServiceProvider: class {
    associatedtype ItemType
    func fetchPage(page: Int, completion: @escaping (PageData<ItemType>?) -> Void)
}

This opens up some headaches, however, since now PagedServiceProvider cannot easily be put in an Array (it's no longer a concrete type). How you deal with this depends heavily on what problem you're actually solving. There is no general solution that is always appropriate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM