Function as a variable with generics in it? - ios

I am trying to create a variable of a generic function type:
import Foundation
typealias Processor<T:Decodable> = (T.Type, URL) -> Promise<T>
struct Service {
let processor: Processor
func getAPI1() -> Promise<TypedResponse1> {
let url = ...
return processor(TypedResponse1.self, url)
}
func getAPI2(params: [String]) -> Promise<TypedResponse2> {
let url = ...
return processor(TypedResponse2.self, url)
}
}
Compiling gives me an error:
Reference to generic type 'Processor' requires arguments in <...>
However, I don't want to specialize the variable since it kinda beats the purpose of having it and will be more difficult to test.
How can I accomplish this?

Related

Swift - Injecting and storing protocols

I have a mapper as follows
protocol MapperProtocol {
associatedtype T
associatedtype U
func map(item: T) -> U?
}
And I want to inject it to a class as follows
protocol ParserProtocol {
associatedtype T
associatedtype U
func parse(from: T) -> U?
}
class TrackParser: ParserProtocol {
typealias T = String
typealias U = Track
private let mapper: MapperProtocol
init(mapper: MapperProtocol) {
self.mapper = mapper
}
func parse(from path: String) -> Track? {
guard let data = try? Data(contentsOf: URL(filePath: path)) else { return nil }
return mapper.map(item: data)
}
}
TrackParser will be initialised from somewhere else so it doesn't need to know the concrete type of the mapper.
When I want to implement it that way I get the following error.
Any ideas how to fix it?
Notice that the parse implementation requires that the mapper has T == Data and U == Track, but you haven't specified those constraint anywhere in TrackParser.
We can make T and U the primary associated types of MapperProtocol, so that the same-type requirements can be specified very easily as MapperProtocol<Data, Track>.
protocol MapperProtocol<T, U> { // Notice the "<T, U>"
associatedtype T
associatedtype U
func map(item: T) -> U?
}
Also, starting from Swift 5.7, existential types are required to be prefixed with "any", so you would write:
private let mapper: any MapperProtocol<Data, Track>
init(mapper: any MapperProtocol<Data, Track>) {
self.mapper = mapper
}

How to create a nested Encodable class and return it from a function in Swift?

In my iOS project, I want to do the following:
create an Encodable class (named ChannelAnswer) which has multiple attributes, including another generic Encodable object
pass an instance of that class as an argument of a function or return it from a function
Here is what I tried:
class ChannelAnswer<T> : Encodable where T : Encodable
{
let errorCode: String?
let data: T?
let origin: Int = 2
init(_ errorCode: String?, _ data: T? = nil)
{
self.errorCode = errorCode
self.data = data
}
}
Now, if I want to return an instance of that class from a function, like the following:
func test() -> ChannelAnswer
{
...
return ChannelAnswer("abc", anyEncodableObject)
}
I get the following error:
Reference to generic type 'ChannelAnswer' requires arguments in <...>
The thing is: the data attribute could be of any type, I only know that that type is Encodable (the test()function above is just an example for the sake of simplicity).
So how can I create my ChannelAnswer class and successfully pass it as an argument of a function or return it from a function?
Thanks.
What you need is a generic method.
func test<T: Encodable>(data: T) -> ChannelAnswer<T> {
// ...
return ChannelAnswer("abc", data)
}

Mocking a class with generic parameter function inside Swift

I am trying to unit test simple HttpClient behaviour. For that I have created an GenericHttpClientInterface protocol and concrete class GenericHttpClient that implements this protocol.
protocol GenericHttpClientInterface {
func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T>
}
class GenericHttpClient: GenericHttpClientInterface {
func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T> {
return URLSession.shared.rx.data(request: request).jsonDecode(to: T.self)
}
}
What I wanted to achieve is to mock that class :
class MockHttpClient: GenericHttpClientInterface {
var invokedMakeRequestCount = 0
var invokedMakeRequestParameters: (request: URLRequest, Void)?
var stubbedMakeRequestResult: Observable<Any>!
func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T> {
invokedMakeRequestCount += 1
invokedMakeRequestParameters = (request, ())
return stubbedMakeRequestResult as! Observable<T>;
}
}
What gives ma a problem is that the method I am mocking has generic parameter T inside which is a class that the request will be decoded to. I don't know this parameter until I call this function so basically in MockHttpClient class for a property that stores stub data for makeRequest I've created:
stubbedMakeRequestResult: Observable<Any>
and after returning it I'm trying to cast that to result type Observable. This gives me an warning
Cast from 'Observable<Any>?' to unrelated type 'Observable<T>' always fails
and in consequence
Thread 1: signal SIGABRT.
Any idea how to stub that data?
Example test that creates SIGABRT:
class GenericHttpTest: XCTestCase {
var sut: Repository!
var mockHttpClient: MockHttpClient!
override func setUp() {
mockHttpClient = MockHttpClient()
sut = Repository(httpClient: mockHttpClient)
}
let test_mocked_data_stub = DataModelStruct(args: DataModelStruct.InsideModelStruct(foo1: "bar"))
func test_should_return_mocked_data_from_mock_http_client() {
mockHttpClient.stubbedMakeRequestResult = Observable.just(test_mocked_data_stub)
let response = try! sut.getFooBar().toBlocking().first()
XCTAssertEqual(response, test_mocked_data_stub)
}
}
Due to the fact that generics are invariant in Swift, Observable<Any> will never ever be convertible to Observable<T>, unless T is Any. This is what cause your crash, as when you assign the value to stubbedMakeRequestResult, the concrete Observable is converted to Observable<Any>, and there's no turning point from here.
What you can do to avoid this is to make stubbedMakeRequestResult an Any, as this will not make any conversions behind the scenes. A small problem is that you loose the type safety and inference, but you can fix this stubbing via a function:
class MockHttpClient: GenericHttpClientInterface {
...
var stubbedMakeRequestResult: Any!
func stubMakeRequestResult<T>(_ result: Observable<T>) {
stubbedMakeRequestResult = result
}
...
}

Generic parameter could not be inferred - Array extension

I have generic method to create object that extend protocol FromResponse.
extension FromResponse {
static func object<T>(_ response: [String: Any]?) -> T? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
let obj: T = T()
return obj
}
}
So whenever I want to call it from anywhere in a code there is no issue. Let's say:
let myObject: MyObject? = MyObject.object(response)
Work's perfectly. But sometimes I'm getting array of objects from my response so I would like to have generic parser as well:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
var returnArray: [T] = [T]()
for singleResponse in response {
if let object: T = T.object(singleResponse) {
returnArray.append(object)
}
}
return returnArray
}
So I expect from this method to return array of MyObject, but In fact I'm getting compiler error when I'm calling this:
let myObjects: [MyObject]? = MyObject.objects(response)
It says:
Generic parameter 'T' could not be inferred
Well, I know what does it mean but I did specify type, so this error should not happen. Also when I do this:
var typ: [MyObject] = [MyObject]()
for singleResponse in (response as? [[String: Any]])! {
let pack: MyObject? = MyObject.object(singleResponse)
typ.append(pack!)
}
It works!
Why? How to have parser that returns array of generics objects?
I don't know for sure why Swift says “Generic parameter 'T' could not be inferred”, but my guess is it has to do with array covariance.
What's covariance? Consider this:
class Base { }
class Sub: Base { }
func f(_ array: [Base]) { }
Can you pass an [Sub] to f? In Swift, you can. Because Sub is a subtype of Base, [Sub] is a subtype of [Base]. (This is called “covariance”.) So you can pass a [Sub] anywhere that a [Base] is allowed:
f([Sub]())
// No errors.
And you can return a [Sub] where a [Base] is expected:
func g() -> [Base] { return [Sub]() }
// No errors.
And you can assign a [Sub] to a [Base] variable:
let bases: [Base] = [Sub]()
// No errors.
So back to your code:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? ...
let myObjects: [MyObject]? = MyObject.objects(response)
Certainly MyObject.objects(_:) must return a type that can be treated as [MyObject]?. But any subtype of [MyObject]? is also acceptable. The type is not tightly constrained. I guess this is why Swift doesn't like it.
The fix is to tell Swift explicitly what type you want, using a pattern you'll see in many places in the Swift standard library:
static func objects<T>(ofType type: T.Type, from response: [[String: Any]]?) -> [T]? ...
// Note that you might not actually have to use the `type` parameter
// in the method definition.
let myObjects = MyObject.objects(ofType: MyObject.self, from: response)
It's not clear why this method is on the MyObject class at all. Perhaps you should make it a method on [[String: Any]]:
extension Collection where Element == [String: Any] {
func objects<T>(ofType type: T.Type) -> [T]? ...
}
let myObjects = response.objects(ofType: MyObject.self)

is it possible to create a generic closure in Swift? [duplicate]

This question already has answers here:
Closure with generic parameters
(2 answers)
Closed 7 months ago.
func myfunc<T>(i:T) -> T {
return i
}
is it possible to make this generic function a closure?
let myfunc = { <T>(i:T) -> T in
return i
}
this doesn't work...
No, because variables and expressions can't be generic. There are only generic functions and generic types.
To clarify: In some languages you can have types with a universal quantifier, like forall a. a -> a. But in Swift, types cannot have a universal quantifier. So expressions and values cannot be themselves generic. Function declarations and type declarations can be generic, but when you use such a generic function or an instance of such a generic type, some type (which could be a real type or a type variable) is chosen as the type argument, and thereafter the value you get is no longer itself generic.
Probably you need something like this.
Type declaration:
typealias ResultClosure<T> = (ResultCode, String?, T?) -> Void
Function declaration:
func loginUser(userName: String, password: String, resultHandler: ResultClosure<TokenModel>?)
Usage:
NetConnector.shared.loginUser(userName: userName ?? "", password: password ?? "") { (code, message, data) in
self.display?.unlockScreen()
if code == .success {
if let activeToken = data {
AppData.shared.userToken = activeToken
}
self.display?.showHome()
} else {
self.display?.showError(errorMessage: message)
}
}
As mentioned, variables in Swift cannot be generic, so creating a closure, whose generic types are specified by the caller is not possible. However, there are workarounds:
With SE-253, it is possible to make arbitrary (nominal) types callable. So instead of declaring a generic closure, we can declare a (non-generic) struct that has a generic callAsFunction method:
struct MyFunc {
func callAsFunction<T>(_ i: T) -> T {
return i
}
}
Now, we can declare a non-generic variable that we can call with a generic value:
let myFunc = MyFunc()
let x = myFunc(42) // -> Int
let y = myFunc("foo") // -> String
Note that this workaround doesn't apply to all situations, but it can be helpful in some.
I have found some alternative way , you can use Anyobject in your closure and pass any values to your method .
typealias genericCompletion<T:AnyObject> = ((Bool,T,String) -> Void)
struct Student {
var name:String = "Kishore"
var age : String = "125"
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.createAGenericReturn { (success, object, message) in
}
self.createStructGeneric { (success, student, message) in
}
}
func createAGenericReturn(callback:#escaping(genericCompletion<AnyObject>)){
callback(true,434.433 as AnyObject,"kishoreTest")
}
func createStructGeneric(callback:#escaping(genericCompletion<AnyObject>)){
callback(true,Student.init() as AnyObject,"kishoreTest")
}
}
Here you can see I mentioned Generic as Anyobject typealias genericCompletion = ((Bool,T,String) -> Void) , So you can pass any values to it .

Resources