My goal is to create a modular network service layer which can easily be unit tested.
In the quest for that, I started simply with the following protocol:
protocol NetworkService {
associatedtype Response
func fetchData() async throws -> Response
}
I then proceed to create a service to fetch the news feed like so:
struct FetchNewsService: NetworkService {
typealias Response = [NewsAssetModel]
func fetchData() async throws -> Response {
guard let fetchNewsURL = URL(string: SystemConstants.NetworkConstants.fetchNewsEndpoint) else {
throw NetworkError.invalidURL
}
let urlRequest = URLRequest(url: fetchNewsURL)
let session = URLSession(configuration: .default)
guard let (data, response) = try? await session.data(for: urlRequest) else {
// Would need to handle no internet error as well
throw NetworkError.server
}
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode > 400 {
throw NetworkError.server
}
guard let newsResponse = try? JSONDecoder().decode(NewsResponseModel.self,
from: data) else {
throw NetworkError.decode
}
return newsResponse.assets
}
}
In my view model, I would like to make a request to fetch the news feed items and so I wanted to inject a type of NetworkService into my view model.
class NewsAssetManager {
private(set) var newsAssets: [NewsAssetModel] = []
private var newsService: NetworkService // 1 - error on this line
// 2 - and this line below
init(withNewsService newsService: NetworkService = FetchNewsService()) {
}
}
The first error that I get at the property declaration is Protocol 'NetworkService' can only be used as a generic constraint because it has Self or associated type requirements
The second error that I get at the initializer is also the same Protocol 'NetworkService' can only be used as a generic constraint because it has Self or associated type requirements
What am I doing incorrectly and how can I fix / improve this ?
Update with generics
protocol NetworkService {
func fetchData<T>() async throws -> T
}
struct FetchNewsService: NetworkService {
func fetchData<T>() async throws -> T {
guard let fetchNewsURL = URL(string: SystemConstants.NetworkConstants.fetchNewsEndpoint) else {
throw NetworkError.invalidURL
}
let urlRequest = URLRequest(url: fetchNewsURL)
let session = URLSession(configuration: .default)
guard let (data, response) = try? await session.data(for: urlRequest) else {
// Would need to handle no internet error as well
throw NetworkError.server
}
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode > 400 {
throw NetworkError.server
}
guard let newsResponse = try? JSONDecoder().decode(NewsResponseModel.self, from: data)
else {
throw NetworkError.decode
}
return newsResponse.assets // Compile error
}
}
I get an error on the last line Cannot convert return expression of type '[NewsAssetModel]' to return type 'T'
Update based on comments & Answer
Adding a combination of Any & Some to the property or initialiser still doesn't get rid of the compilation errors faced.
I also confirmed via terminal and this code that I at least am running Swift 5.6:
#if swift(>=5.6)
print("swift 5.6")
#endif



This is how I would define the protocol when not using an associated type. Make the function generic instead and the type should conform to
DecodableThen I would change the implementation of the conforming type to
Now the implementation isn't so hardcoded to one solution, actually it is so general that we can move it into an extension of the protocol
Which means in this case
FetchNewsServiceisn't needed anymore and it can be deleted.The
NewsAssetManagercan then be changed to below where all the code that is specific to this particular api and response type now resides.