Using Generic parameter in closure - swift

I have a function that makes a service call to fetch data, uses JSONDecoder to decode the JSON response into a model object and then return that object as part of a parameter in the completion block provided to the function. In the object, I am actually using the Result object.
Here is the code -
static func fetchData(_ completion: #escaping (Result<ExampleModelObject, Error>) -> Void)
I am trying to make this function generic, where it can accept the url it needs to call as one parameter and the Model object as another parameter and so I modified it to this -
static func fetchData <T: Decodable>(urlString: String, _ completion: #escaping (Result<T, Error>) -> Void)
This is the code where I use jsondecoder -
let parsedJSON = try jsonDecoder.decode(T.self, from: data)
completion(.success(parsedJSON))
Here is how I am trying to call this function from another class -
DataRetriever. fetchData(urlString: dataURLString) { (result: ExampleModelObject) in
However, I am getting 2 errors during compilation -
Cannot convert value of type '(ExampleModelObject) -> Void' to expected argument type '(Result<_, Error>) -> Void'
Generic parameter 'T' could not be inferred
Would anyone please be able to help me with how I can fix these errors?

You have to specify the full type
DataRetriever.fetchData(urlString: dataURLString) { (result: Result<ExampleModelObject,Error>) in

try to
fetchData(urlString: "") { (model: Result<ExampleModelObject, Error>) in
}
or use two completion blocks like so:
func fetchData <T> (urlString: String, success: #escaping(T) -> Void, failure: #escaping(Error) -> Void) where T: Decodable {
}
execution:
fetchData(urlString: "") { (model: ExampleModelObject) in
} failure: { error in
}

With an additional parameter you can help the combiler to recognise the type.
If you define the function in this way, it is no longer necessary to specify the type in the closure parameter.
static func fetchData <T: Decodable>(type: T.Type, urlString: String, _ completion: #escaping (Result<T, Error>) -> Void)
The call then looks like this:
DataRetriever.fetchData(type: ExampleModelObject.self, urlString: dataURLString) { result in }

Related

Result type with generic Success Type

I'm switching over a project from a custom Result type to the native Swift Result type and keep running into this - or similar - error messages:
Member 'success' in 'Result<_, Error>' produces result of type 'Result<Success, Failure>', but context expects 'Result<_, Error>'
protocol DataFetcher {
func request<T>(completion: #escaping (Result<T, Error>) -> Void )
}
struct RandomFetcher: DataFetcher {
func request<String>(completion: #escaping (Result<String, Error>) -> Void) {
let str = "Hello"
completion(.success(str))
}
}
The idea is to have make a bunch of generic Fetchers for different data extraction calls and to pass these to VC's who would have a var fetcher: DataFetcher property. The VC's know which concrete types they expect from their request. I can't use an associated type as I need to save a bunch of these in an array and I thought I could get away with just the generic implementation - but it almost seems as if the Result type being declared as a generic in the protocol, means that it won't accept when I specify it in the implementation. What am I missing?
func request<String>(completion: #escaping (Result<String, Error>) -> Void) {
This is a classic generics mistake in Swift. This does not mean "request requires T==String." This means "there is an arbitrary type called String that this function accepts." There is no relationship between this String and Swift.String.
What you're trying to do here violates the protocol. The protocol says that the caller gets to pick any T they want. You can't conform to that protocol with a restricted T.
If you want the conforming type (RandomFetcher) to get to decide the type of T, then you have to use an associatedType (that's what associatedType means).
The thing you're trying to build is pretty common, but not trivial, and requires a different way of thinking about the problem. I walk through it step-by-step in this series.
In this case an associated type is preferable
protocol DataFetcher {
associatedtype FetchType
func request(completion: #escaping (Result<FetchType, Error>) -> Void )
}
struct RandomFetcher: DataFetcher {
func request(completion: #escaping (Result<String, Error>) -> Void) {
let str = "Hello"
completion(.success(str))
}
}

Pass a closure in a function

I have a little experience in Swift and facing a problem to pass a closure in a function as a parameter.
//1.
public func changeMyStatus(to f:?, _ completion:#escaping (_ isSucced:Bool)->()){
//
}
//2.
func goLive(_ completion:#escaping (_ isSucced:Bool)->()){
}
//3.
func goNonLive(_ completion:#escaping (_ isSucced:Bool)->()){
}
Now , I want to use first function in my controller and wants to pass second/third function as a parameter. Closure in first will return true/false depending on what returned by closure in second/third.
i)What will be the type I should put in first function?
Also I want to call first function from my class like this
changeMyStatus(to: goNonLive) { (isSuccess) in
}
please help
You need to change the changeMyStatus function signature and implementation like:
public func changeMyStatus(to f: (#escaping (Bool) -> ()) -> () , _ completion:#escaping (_ isSucced:Bool)->()){
f { (status) in
completion(status)
}
}
You can call these function like:
// goLive
changeMyStatus(to: goLive(_:)) { (status) in
print(status)
}
// goNonLive
changeMyStatus(to: goNonLive(_:)) { (status) in
print(status)
}
Your second and third function has a completion parameter with a type of : (Bool -> Void) -> Void
So in order to pass it to your first function, try this way :
public func changeMyStatus(to f: ((Bool) -> ()), _ completion:#escaping (_ isSucced:Bool)->()) { // Your body}
In Swift, you need to see a function as a type like Int, Double, String...

Cannot convert value of type '(T) -> Void'

Example:
struct Wrapper<T> {
var key: Int = 0
var listeners: [Int: (T) -> Void] = Dictionary()
mutating func add(_ handler:#escaping (T) -> Void) {
self.key += 1
self.listeners[self.key] = handler
}
func get(key: Int) -> (T) -> Void {
return self.listeners[key]!
}
}
Test protocol:
protocol CommonProtocol {
}
Class that create Wrapper of test class
class C {
var wrapper: Wrapper = Wrapper<CommonProtocol>()
func add<T: CommonProtocol>(_ handler: #escaping (T) -> Void) {
self.wrapper.add(handler) //Cannot convert value of type '(T) -> Void' to expected argument type '(CommonProtocol) -> Void'
}
}
Image with error
I get error:
Cannot convert value of type '(T) -> Void' to expected argument type '(CommonProtocol) -> Void'
Question:
Why (T) -> Void can't be casted to (CommonProtocol) -> Void ? The T
is explicitly declared as <T: CommonProtocol>
This is my first question, if you have some suggestions please don't hesitate to contact me
You don't need to make func add generic.
When you specify in func add<T: CommonProtocol>... you explicitly telling the compiler that your function accepts all Types that inherit CommonProtocol but your Wrapper specifies that accepts CommonProtocol not inherited types.
Solution
Either type-erase class C:
Class C<T: CommonProtocol> {
var wrapper: Wrapper<T>
....
}
or if type T doesn't actually matter to you then:
func add(_ handler: #escaping (CommonProtocol) -> Void)
but second one doesn't make sense at all. You have to downcast it every-time you'll use this method (and downcasts are very bad :D)
Note: It's actually not related to this question, but one of your options is to type-erase the CommonProtocol too.

Swift override static method compile error

I have these two swift classes:
class A {
static func list(completion: (_ result:[A]?) -> Void) {
completion (nil)
}
static func get(completion: (_ result:A?) -> Void) {
completion (nil)
}
}
class B: A {
static func list(completion: (_ result:[B]?) -> Void) {
completion (nil)
}
static func get(completion: (_ result:B?) -> Void) {
completion (nil)
}
}
Trying to compile this raise the error "overriding declaration requires an 'override' keyword" but just for the 'get' method of class B. 'list' method compiles fine. What is the difference between [B]? and B? for the compiler in this case?
Edit: Also notice that adding 'override' is not possible. I get the error 'Cannot override static method'.
In class B, the method list is a separate method from list in class A. They just share the same name, that's all.
The parameters of the two list methods are actually different:
// A.list
static func list(completion: (_ result:[A]?) -> Void) {
// B.list
static func list(completion: (_ result:[B]?) -> Void) {
A.list takes an argument of type (_ result: [A]?) -> Void while B.list takes a (_ result: [B]?) -> Void. The array type in the closure type's parameter list is different!
So you're not overridding anything, you're just overloading.
Note:
static methods can never be overridden! If you want to override a method, use class instead of static.
class A {
class func get(completion: (_ result:A?) -> Void) {
completion (nil)
}
}
class B: A {
override class func get(completion: (_ result:B?) -> Void) {
completion (nil)
}
}
In short, as per rule, static methods can't be overridden.

Casting to a specified generic function parameter

Let's say I have a Commander object that executes commands. The return type is not always the same and changes according to the command.
I'd like to be able to use a function that forwards a command to the Commander, tests if the result is of a certain type (passed as a parameter), before calling a success closure if the cast succeeded, and a failure closure otherwise.
I've tried using generic parameters with something like this:
func postCommand<T>(command: String, expectedResponseType: T, success: T -> Void, failure: (NSError?) -> Void) {
Commander.execute(command, completion: { (response: AnyObject?) in
guard let content = response as? T else {
failure(nil)
return
}
success(content)
})
}
Calling it that way
self.postCommand("command", expectedResponseType: [String: AnyObject], success: { (content: [String: AnyObject]) in
print("Success")
}) { (error: NSError?) in
print("Failure")
}
But I get an error from the compiler:
Cannot convert value of type '([String : AnyObject]) -> Void' to expected argument type '_ -> Void'
If I try to do the cast like this:
guard let content = response as? expectedResponseType
the compiler complains that expectedResponseType is not a type.
I can't figure out how to do that. Is it even possible?
The problem is not with the casting, but with the type of the expectedResponseType: parameter.
If you wish to pass a type to a function, you need to use the metatype type as the argument type. In this case, the expectedResponseType: parameter of your function should be of type T.Type – allowing you to pass in a type to define T.
func postCommand<T>(_ command: String, expectedResponseType: T.Type, success: (T) -> Void, failure: (NSError?) -> Void) {
// ...
}
You'll also need to use the postfix .self in order to refer to the actual type of whatever you pass into the expectedResponseType: parameter:
self.postCommand("command", expectedResponseType: [String: AnyObject].self, success: { content in
print("Success")
}) { error in
print("Failure")
}
Although you should note that the type of T can be inferred directly from the success closure you pass to the function:
func postCommand<T>(_ command: String, success: (T) -> Void, failure: (NSError?) -> Void) {
// ...
}
self.postCommand("command", success: { (content: [String: AnyObject]) in
print("Success")
}) { error in
print("Failure")
}