Using Generics in completionHandler - ios

I have a simple app, that communicates with server via TCP Socket using custom protocol. I want to achieve HTTP-like response-request behaviour, abstracting from socket layer.
So I have simple protocol:
protocol ResponseType {
init(with frame: SocketMessage)
}
And some of examples:
struct MessageAck: ResponseType {
var messageId: String
init(with frame: SocketMessage) {
messageId = frame.messageId
}
}
I created simple protocol for sending requests:
protocol APIClient {
func send<T: ResponseType>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?)
}
enum SocketAPIRequest {
case textMessage(messageId: String, ...)
...
}
And finally:
enum Result<T> {
case success(T)
case failure(Error)
}
class SocketAPIClient: APIClient {
typealias MessageId = String
private var callbacks = [Receipt: ((Result<ResponseType>) -> Void)]()
...
func send<T>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) where T : ResponseType {
....
callbacks[stompFrame.receiptId] = completion
....
}
}
So, when I want to store callback for each request, to call it after answer will be received, I got such error:
Cannot assign value of type '((Result<T>) -> Void)?' to type '((Result<ResponseType>) -> Void)?'
I guess the problem with mixing Type's and objects, or maybe something else.

Swift generics are not covariant (with special hard-coded exceptions for Array which involve copying the elements). That means that Result<Apple> is not a subtype of Result<Fruit>. See Swift Generics & Upcasting for examples of why.
In your case, what would prevent you from passing a Result<MessageBody> to a callback that expected a Result<MessageAck>? For example:
for callback in callbacks {
callback(result)
}
How could you know this was legal at compile time for any given type of result?
EDIT (BETTER ANSWER):
You can hide the type inside a closure to get what you want. Try this:
class SocketAPIClient: APIClient {
typealias MessageId = String
private var callbacks = [Receipt: ((Result<SocketMessage>) -> Void)]() // <--- Change
func send<T>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) where T : ResponseType {
// Store the closure we don't understand inside a closure we do
callbacks[stompFrame.receiptId] = { result in
switch result {
case .success(let message):
completion?(.success(T.init(with: message)))
case .failure(let error):
completion?(.failure(error))
}
}
}
}
Now, instead of trying to hold T directly in callbacks, it's held in each individual closure, hidden from the rest of the class, and T never escapes this function. When you get to wherever you call callback in your code, just pass it the Result<SocketMessage> that I assume you already have somewhere.
OLD ANSWER:
The simplest solution to your problem is to have the callback always pass a Result<Data> and remove T entirely:
protocol APIClient {
func send(request: SocketAPIRequest, completion: ((Result<Data>) -> Void)?)
}
Then leave it to the MessageAck (in the completion handler) to deserialize itself from the raw data.
There are other ways to achieve all this with type erasers, but they're much more complex and sometimes very fiddly.

Have you tried the following signature
func send<T:ResponseType>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?){ ... }
and still getting error?
Edit 1:
or probably you should try something like this
protocol APIClient {
associatedtype T
func send(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?)
}
and,
class SocketAPIClient: APIClient {
typealias MessageId = String
typealias T = ResponseType
private var callbacks = [Receipt: ((Result<ResponseType>) -> Void)]()
...
func send(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) {
....
callbacks[stompFrame.receiptId] = completion
....
}
}

Related

swift question about "#escaping" inside class

I am a newbie and maybe this is a silly question but I need some help.
I have this code like below but I wonder that should I remove "#escaping" inside the checkSignedIn function.
Class A{
public func checkSignedIn(complete: #escaping (Bool) -> Void) {
_ = Amplify.Auth.fetchAuthSession { (result) in
do {
let session = try result.get()
complete(session.isSignedIn)
} catch {
print("Fetch auth session failed with error - \(error)")
complete(false)
}
}
}
I imagine that using "#escaping" will escape the return value from closure if I assign complete() to a variable like below.
Class A{
var complete: (() -> Void)?
public func checkSignedIn(complete: #escaping (Bool) -> Void) {
_ = Amplify.Auth.fetchAuthSession { (result) in
do {
let session = try result.get()
self.complete = complete(session.isSignedIn)
} catch {
print("Fetch auth session failed with error - \(error)")
self.complete = complete(false)
}
}
}
Then I can call A.complete again.
Am I wrong? I appreciate it if you teach me about this.
No, they won't be the same.
The complete: #escaping (Bool) -> Void defines this:
a function (or a callback) that takes 1 argument (Bool), and returns nothing (Void). It's an equivalent of function that looks like this:
func complete(_ value: Bool) { }
this function escapes the scope of the function it's passed to, as it runs asynchronously (that's #escaping keyword meaning)
And then this function is called with complete(session.isSignedIn), where session.isSignedIn is a boolean argument you pass in, just as function's definition states, and function returns nothing
The statement self.complete = complete(session.isSignedIn) won't compile:
You defined self.complete as (() -> Void) type - that is a function or callback that takes no arguments, and returns nothing. It's an equivalent of function:
func y() { }
So complete(session.isSignedIn) returns Void type as we know. Assigning Void type to (() -> Void) type is not going to work.
If you want to save the escaping function / callback to be used elsewhere, you can do this:
// Make sure signature of the variable matches that of a function argument
var complete: ((Bool) -> Void)?
public func checkSignedIn(complete: #escaping (Bool) -> Void) {
// Save callback at the start of the function
self.complete = complete
// Then continue to a asynch part of the code:
_ = Amplify.Auth.fetchAuthSession { (result) in
// Inside this callback, you still can use the function argument
complete(session.isSignedIn)
...
}
// But in another function, you have to use self.complete, e.g.:
func x() {
// Use saved callback. You don't have to say `self` here, just using it for clarity
self.complete(true)
}

Defining type of optional generic closure parameter

I want to create an interface, that can be invoked with a generic and a non generic parameter used in the Result type.
The API would look like the following:
struct NonGenericParameter {}
func taskPreparation<T: Decodable>(onTypedComplete: ((Result<T, Error>) -> Void)?,
onTyplessComplete: ((Result<NonGenericParameter, Error>) -> Void)?) {
// Do the neccessery preparation...
if let onComplete = onTypedComplete {
task(onComplete: onComplete)
}
if let onComplete = onTyplessComplete {
task(onComplete: onComplete)
}
}
func task<T: Decodable>(onComplete: #escaping (Result<T, Error>) -> Void) {
// do task...
}
func task(onComplete: #escaping (Result<NonGenericParameter, Error>) -> Void) {
// do task...
}
However, when i try to invoke the taskPreparation API, specifying onTyplessComplete as nil
taskPreparation(onTypedComplete: nil,
onTyplessComplete: { result in // Do something ... })
I receive the error
Generic parameter 'T' could not be inferred.
I understand, i have to specify the type of the generic parameter. I have tried to create a dummy decodable parameter, and pass it to the closure.
struct DummyDecodable: Decodable {}
taskPreparation(onTypedComplete: { (result: Result<DummyDecodable, Error>) in },
onTyplessComplete: { result in // Do something ... })
But obviously, in this case the onTypedComplete closure is not nil.
Does someone have an idea how could I specify a nil closure and satisfy the type inference too?
You would still need the DummyDecodable for this, which is kind of ugly, but at least you are passing a nil value:
Simply pass ((Result<DummyDecodable, Error>) -> Void)?.none. nil is in fact just a syntactic sugar for Optional<WhateverType>.none.
struct DummyDecodable: Decodable {}
taskPreparation(onTypedComplete: ((Result<DummyDecodable, Error>) -> Void)?.none,
onTyplessComplete: { result in /* Do something ...*/ })

How to pass a generic decodable data using a delegate

I have managed to create an APIClient and router for my app and here is my code
my result
enum Result<T> {
case success(data: T)
case failure(error: Error) }
Here is my server request
func request<T: Decodable>(router: APIRouter, completion: #escaping (Result<T>) -> ()) {
And the completion handler
completion(Result<T>.failure(error: ErrorResult.defaultError))
completion(Result.success(data: result))
And here is my viewModel where i am getting the result
switch result {
case .success(let successData):
print("successData \(successData)")
self.viewModelDelegate?.didFinishFetchingData(successData)
case .failure(let errorData):
print("errorData \(errorData.localizedDescription)")
self.viewModelDelegate?.didFinishFetchingDataWithFailure?(errorData.localizedDescription)
}
Now what i need is i want to pass the result as decodable to my viewcontroller using delegate
#objc public protocol ViewModelDelegate {
func didStartFetchingData()
func didFinishFetchingData(_ result:Decodable)
#objc optional func didFinishFetchingDataWithFailure(_ message:String)
}
This is what I did but it is getting error
All I need is I want to pass the success data (decodable data) to my view controller from view model using custom delegate
You can do generic with this, check out the below example
func didFinishFetchingData<T: Decodable>(_ result: T)
// example models
struct Foo: Decodable {
}
struct Boo: Decodable {
}
didFinishFetchingData(Foo())
didFinishFetchingData(Boo())
To make it optional as you need , simply add ? in T .
func didFinishFetchingData<T: Decodable>(_ result: T?)
Note : You can't pass plain nil into the parameter but you can pass a nullable type of T check the code below .
let model = try? JSONDecoder().decode(Foo.self, from: Data()) // model is type of Foo?
didFinishFetchingData(model) // works fine

How to constraint function generic type according to a parameter's property?

I have this enum:
enum ItemType: String {
case image
case movie
var rawValue: String {
switch self {
case .image: return String(kUTTypeImage)
case .movie: return String(kUTTypeMovie)
}
}
}
and this function inside a class
func items<T>(for type: ItemType, completion: ([T]) -> Void) where T: NSSecureCoding {}
Now what I would like to achieve is that if the ItemType is .image I would like the completion to be inferred as of type ([UIImage]) -> Void otherwise if it is .video I would like it to be inferred as ([URL]) -> Void
Is this possible in any way in Swift? Or what would be an alternative approach to make the completion type infer according to the type provided.
Additional details:
The body of the function uses NSItemProvider loadItem instance method whose closure returns any type conforming to NSSecureCoding. So as long as I can give a type like that I don't care about what type specifically it is.
func items<T>(for type: ItemType, completion: ([T]) -> Void) where T: NSSecureCoding {
itemProviders(for: [type]).forEach { itemProvider in
itemProvider.loadItem(forTypeIdentifier: type.rawValue, options: nil, completionHandler: { (item: T, error: Error!) in
})
}
}
You can't do this because the parameter type is evaluated at runtime, while at compile time, T needs to be inferred.
A workaround is to separate this into two methods:
func itemsForImages(completion: ([UIImage]) -> Void) { ... }
func itemsForMovies(completion: ([URL]) -> Void) { ... }
And then determine which method to call:
switch itemType {
case .image:
itemsForImages { images in ... }
case .movies:
itemsForMovies { urls in ... }
}
Another alternative is to have a closure of type ([Any]) -> Void and the caller needs to cast the parameter to the correct types, but this is not so type safe.

Writing API requests with completion blocks using Swift generics

I am experimenting with generics in Swift and I am attempting to push it to its limits.
In my application I have a super simple API wrapper around Alamofire. The structure is like so:
API -> Request -> Alamofire request
Here is some generic code that I threw into a playground to test some concepts. Here is what I have so far:
protocol SomeProtocol {
var cheese: String { get }
init()
}
class Something: SomeProtocol {
required init() { }
var cheese: String {
return "wiz"
}
}
class API {
class func performRequest<T: SomeProtocol>(completion: (T?, NSError) -> Void) {
// This code is irrelevant, just satisfying the completion param
let test = T()
let error = NSError(domain: "Pizza", code: 1, userInfo: nil)
completion(test, error)
}
}
func test() {
API.performRequest<Something> { item, error in
}
}
Calling the function gives the error:
"Cannot explicitly specialize a generic function"
****** UPDATE ******
As per the answer below, removing the typical <> generic type specifier and instead adding the expected type to the completion params solves the issue. Just a quick example:
func test() {
API.performRequest { (item: Something?, error) in
}
}
Additionally, I have discovered that making the API wrapper class a generic class solves the issue like so:
protocol SomeProtocol {
var pizza: String { get }
}
class SomeObject: SomeProtocol {
var pizza: String { return "pie" }
}
class API<T: SomeProtocol> {
class func performRequest(completion: (T?, NSError?) -> Void) {
}
}
func test() {
API<SomeObject>.performRequest { item, error in
// Do something with item, which has a type of SomeObject
}
}
Either way, the end goal is accomplished. We have a single generic method that will perform a set of tasks and return, via completion closure, the object based on the type passed in with each use.
The way generics work is they allow a function to use unspecialized variables inside of its implementation. One can add functionality to these variables by specifying that the variables must conform to a given protocol (this is done within the declaration). The result is a function that can be used as a template for many types. However, when the function is called in the code itself, the compiler must be able to specialize and apply types to the generics.
In your code above, try replacing
func test() {
API.performRequest<Something> { item, error in
}
}
with
func test() {
API.performRequest { (item: Something?, error) in
}
}
this lets the compiler know which type it must apply to the function without explicitly specifying. The error message you received should now make more sense.
Here is what i did using alamofire and alamofire object mapper:
Step 1: Create modal classes that conforms to Mappable protocols.
class StoreListingModal: Mappable {
var store: [StoreModal]?
var status: String?
required init?(_ map: Map){
}
func mapping(map: Map) {
store <- map["result"]
status <- map["status"]
}
}
Step 2: Create a fetch request using the generic types:
func getDataFromNetwork<T:Mappable>(urlString: String, completion: (T?, NSError?) -> Void) {
Alamofire.request(.GET, urlString).responseObject { (response: Response<T, NSError>) in
guard response.result.isSuccess else{
print("Error while fetching: \(response.result.error)")
completion(nil, response.result.error)
return
}
if let responseObject = response.result.value{
print(responseObject)
completion(responseObject, nil)
}
}
}
Step 3: Now all you need is to call this fetch function. This can be done like this:
self.getDataFromNetwork("your url string") { (userResponse:StoreListingModal?, error) in
}
You will not only get your response object but it will also be mapped to your modal class.

Resources