I would recommend to use generics if this suits your need. See example below.
class Processor<Output> {
// This closure holds the specific logic for processing data into the desired 'Output' type.
private let processingLogic: (Data) async -> Output
init(processingLogic: @escaping (Data) async -> Output) {
self.processingLogic = processingLogic
}
func process(_ data: Data) async -> Output {
return await processingLogic(data)
}
}
class Downloader {
func processData<Output>(from url: URL, with processor: Processor<Output>) async -> Output {
////
}
}
func fetchData() async {
let downloader = Downloader()
let url = URL(string: "https://example.com")!
// --- Create an image processor instance ---
let imageProcessor = Processor<UIImage>(processingLogic: { data in
// Logic to turn data into a UIImage
return UIImage(systemName: "photo.artframe") ?? UIImage()
})
// The result is known to be a UIImage at compile time.
let image: UIImage = await downloader.processData(from: url, with: imageProcessor)
print("Success! Got a UIImage: \(image)")
// --- Create a text processor instance ---
let textProcessor = Processor<String>(processingLogic: { data in
// Logic to turn data into a String
return "This is the processed text result."
})
// The result is known to be a String at compile time.
let text: String = await downloader.processData(from: url, with: textProcessor)
print("Success! Got a String: \(text)")
}
Why is this better? Well, first its easy to scale it, for a new type to be supported you just have to create a new processor, but the best part is that compiler will know when you call Processor<UIImage> that the type is exactly UIImage so no need of type casting, results in better performance.