0

I have the following code setup (written in Swift):

protocol DataFetcherType {
    init(_ mainData: String, fetchData: Bool)
}

class DataFetcher1: DataFetcherType {
    required init(_ mainData: String, fetchData: Bool) {
    }
}

class DataFetcher2: DataFetcherType {
    required init(_ mainData: String, fetchData: Bool) {
    }
}

struct Data<FetcherType: DataFetcherType> {
    var mainData: String
    var fetcher: DataFetcherType
    
    init(_ mainData: String, using fetcher: FetcherType, fetchData: Bool = true) {
        self.mainData = mainData
        self.fetcher = FetcherType(mainData, fetchData: fetchData)
    }
}

And I'm trying to instantiate Data like so:

Data("foo", using: DataFetcher1)

But I get the error:

Type 'DataFetcher1.Type' cannot conform to 'DataFetcherType'

The same is true if I try it with DataFetcher2.

I've been at it for hours and I feel like I've changed every possible thing, but I just can't get it to work. My initial attempt didn't use Generics, but I eventually decided that might be what I need to do. Was that wrong, or am I fundamentally missing something about how Swift works?

Mattia Righetti
  • 1,265
  • 1
  • 18
  • 31
  • inside your Data structs initializer do self.fetcher = fetcher, exactly what you want to achieve? – Kazi Abdullah Al Mamun May 09 '21 at 23:17
  • @Kazi No, because I still need to initialize fetcher with `mainData` and other arguments. The goal is to instantiate `Data` and have it fetch the relevant data automatically/asynchronously – Hunter Holland May 09 '21 at 23:23

2 Answers2

0

You have to take a type instead of an object through the parameter.

Rewrite your data structures initializer as:

struct Data<FetcherType: DataFetcherType> {
    var mainData: String
    var fetcher: DataFetcherType
    
    init(_ mainData: String, using fetcher: FetcherType.Type, fetchData: Bool = true) {
        self.mainData = mainData
        self.fetcher = FetcherType(mainData, fetchData: fetchData)
    }
}

let data = Data("Data fetcher1", using: DataFetcher2.self)
print(data.fetcher)
  • I'm sorry but this is just not correct. You do not need to pass the type, and doing so makes the generic completely pointless. – matt May 11 '21 at 00:35
  • And it is also wrong to type a variable as a protocol. – matt May 11 '21 at 00:41
  • @matt you are completely right but I tried to resolve his error only and yes according to his implementation there is no reason to use generic when he can get type through initializer. But I don't understand that why type a variable as protocol is wrong? – Kazi Abdullah Al Mamun May 11 '21 at 17:28
0

You don't need to pass any type at all. The using parameter is completely unnecessary. Delete it. This is a generic; passing the type makes the generic pointless!

Just resolve the generic when you declare what you want to instantiate. (And do not call something Data, that name is taken.)

So, if we start with this:

protocol DataFetcherType {
    init(_ mainData: String, fetchData: Bool)
}
class DataFetcher1: DataFetcherType {
    required init(_ mainData: String, fetchData: Bool) {
    }
}
class DataFetcher2: DataFetcherType {
    required init(_ mainData: String, fetchData: Bool) {
    }
}

... then this is all you need:

struct MyData<FetcherType: DataFetcherType> {
    var mainData: String
    var fetcher: FetcherType
    init(_ mainData: String, fetchData: Bool = true) {
        self.mainData = mainData
        self.fetcher = FetcherType.init(mainData, fetchData: fetchData)
    }
}

...because to make one, you can just resolve the type:

    let mydata = MyData<DataFetcher1>("hey")

Also, notice that I have changed the type of the variable fetcher. It should be the generic type, not the protocol.

In general if you find yourself passing types around in Swift, that's a bad smell and you should rethink. It can be done, and it sometimes is done, but on the whole Swift does not like metatypes and you should avoid them.

And the same is true of protocols; if you find you are typing something as a protocol, rethink.

This is exactly why generics exist, so you don't do those things.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Hey, thanks so much. This makes perfect sense, and you answered what I asked perfectly. But I'm realizing that I didn't communicate a detail that I wanted. I wanted to stay away from the Generic syntax when creating a `MyData` instance and instead, if possible, ideally, pass something like an enum as an argument so the call would be `let mydata = MyData("hey", using: .DataFetcher1)`. I recognize your solution accomplished the same goal, but I trying to make the calling syntax as elegant/readable as possible, even if it may be considered a bad practice. Thoughts? – Hunter Holland May 11 '21 at 01:57
  • Not enough info. Why would you want to do that? Any time you find yourself switching on a type like that, that sounds like rock-bottom bad OOP. – matt May 11 '21 at 02:01