Swift: Completion closures with instances that conforms to protocol - ios

I try to use Swift's closures like the completion blocks in ObjC when calling async requests.
This seems to works. I'm using a protocol for my model classes and in conjunction with an Array I get problems. The relevant code:
//ModelProtocol.swift
protocol ModelProtocol {
// all my models should implement `all`
class func all(completion: ((models: Array<ModelProtocol>) -> Void) )
}
//Person.swift
// calls the HTTP request and should return all Person-Objects in `completion`
class func all(completion: ((models: Array<ModelProtocol>) -> Void) ) {
let request = HTTPRequest()
request.getAll() { (data:NSArray) in
var persons:Person[] = //... `data` is the result from the HTTP GET request and will be parsed here - this is ok
completion(models: persons)
}
}
//HTTPRequest.swift
func getAll(completion: ((data: NSArray) -> Void) ) {
//... some setup would be here
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
var jsonResponse: NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSArray
completion(data: jsonResponse)
}
}
//ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// use this whole code here - receive all Persons and show in a tableView or something like this
Person.all( { (models: Array<ModelProtocol>) in
println(models) //CRASH here
})
}
When I change the protocol definition of function all (and so the function all in Person.swift) to class func all(completion: ((models: Person[]) -> Void) ) it is working.
But I want to use Array<ModelProtocol> to use polymorphismus and only use classes that conforms to ModelProtocol, that can be Person or House or whatever.
I think I'm missing something important or basic here. I hope my problem is clear enough.
Edit:
In ViewController.swift the execution of the App stops at the println(models) statement with the message EXC_BAD_ACCESS.

Maybe this will do what you want:
protocol ModelProtocol {
// all my models should implement `all`
class func all(completion: ((models: Array<Self>) -> Void) )
}
class func all(completion: ((models: Array<Person>) -> Void) ) {
let request = HTTPRequest()
request.getAll() { (data:NSArray) in
var persons:Person[] = //... `data` is the result from the HTTP GET request and will be parsed here - this is ok
completion(models: persons)
}
}

Related

Migrating generic function from Alamofire 3 to Alamofire 4

I'm migrating this code with Alamofire 3 to Alamofire 4.
The reference is: Creating a generic method with AlamoFire in Swift
I have this class:
import Foundation
import Alamofire
import AlamofireObjectMapper
import ObjectMapper
class HttpHandler {
static func sendRequest<T:BaseUser>(url: String, parameters: [String: AnyObject]?) -> T {
let res : T
Alamofire.request(url)
.validate()
.responseObject { (response: DataResponse<[T]>) in
switch response.result {
case .Success(let value):
res.valueHandle?(value)
case .Failure(let error):
res.errorHandle?(error)
}
}
return res
}
}
class BaseUser: ResponseObjectSerializable {
var valueHandle : ((BaseUser)->())?
var errorHandle : ((NSError)->())?
required init?(response: HTTPURLResponse, representation: AnyObject) {
}
}
public protocol ResponseObjectSerializable {
init?(response: HTTPURLResponse, representation: AnyObject)
}
But, I'm getting this error:
Cannot convert value of type '(DataResponse<[T]>) -> ()' to expected
argument type '(DataResponse<_>) -> Void'
How I can fix it?
You are returning from asynchronous method, and expecting that the result is valid, I think you should rethink your implementation of sendRequest method a bit. You can make it such that it takes in completion closure.
Also, your BaseUser does not seem to be a model class, it rather seems like some handler class, so to say. It only has two properties which are closure, what are you trying to map from http request. Should it have some real attributes.
With that, if you have a pure model class and a proper send method, you can achieve what you are trying pretty easily.
The error in your case is because you are using, AlamofireObjectMapper and trying to map values without actually implementing protocol.
Look at the signature of the method,
public func responseObject<T>(queue: DispatchQueue? = default,
keyPath: String? = default,
mapToObject object: T? = default,
context: MapContext? = default,
completionHandler: #escaping(Alamofire.DataResponse<T>) -> Swift.Void) -> Self where T : BaseMappable
Here the generic parameter T is expected to be of type BaseMapper. If you conform to the protocol Mappable from ObjectMapper, it should be just fine.
class BaseUser: BaseMappable {
func mapping(map: Map) {
// map your actual properties here
// like username, name
}
}
I have also changed your sendRequest method a bit, such that instead of returning values, it does the request and does a callback after finishing.
static func sendRequest<T:BaseUser>(url: String,
parameters: [String: AnyObject]?,
completion: #escaping ([T], Error?) -> Void) {
Alamofire.request(url)
.validate()
.responseObject { (response: DataResponse<[T]>) in
switch response.result {
case .success(let value):
completion(value, nil)
case .failure(let error):
completion([], error)
}
}
}
The code looks fine, but it does not yet work. You are expecting that your response to be mapped to array of BaseUser, but AlamofireObjectMapper, expect it to be of type, Mappable.
Now you can use extension to array and conform to the protocol. We can make use of Swift 4.1 Conditional Conformance here.
extension Array: BaseMappable where Element: BaseMappable {
public mutating func mapping(map: Map) {
var userHolder: [BaseUser] = []
// map array of values to your BaseUser
}
}
And with this you are good to go. Note, that I showed you how the problem that you had could be solved. I also did some small refactoring just to make code more clearer.
Please make changes according to your need.

Using Generics in completionHandler

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
....
}
}

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.

callback function from caller to callee in swift

I have a view controller and a class for doing the bits to call the services and get the data from server.
The ViewController code is below,
class ViewController : UIViewController
{
override func viewDidLoad() {
let parser = Parser()
parser.connectServer("abc URL" , ..... <gotDataFromServer> ..... )
}
func gotDataFromServer(response:String)
{
...... Do our things here .......
}
}
and the parser code is below,
class Parser
{
func connectServer(apiURL:String,...<call back function name>...)
{
let manager = RequestOperationManager.sharedManager()
manager.GET(apiURL ,
parameters: nil,
success: { (operation,responseObject) ->Void in
.....<Call back the function which is passed in parameter> ....
},
failure: { (operation , error) in
print ("error occurred")
})
}
}
Now in the above sample code i want to pass call back function "gotDataFromServer" as a parameter and when the inner function get the response from the server then i want to call this function back.
Can anyone please help.
You can use delegates to achieve that. Try out following code
class ViewController : UIViewController, DataDelegate
{
override func viewDidLoad() {
let parser = Parser()
parser.delegate = self
parser.connectServer("abc URL" , ..... <gotDataFromServer> ..... )
}
func gotDataFromServer(response:String)
{
...... Do our things here .......
}
}
And add protocol in parser as follows
protocol DataDelegate {
func gotDataFromServer(response:String)
}
class Parser
{
var delegate : DataDelegate!
func connectServer(apiURL:String,...<call back function name>...)
{
let manager = RequestOperationManager.sharedManager()
manager.GET(apiURL ,
parameters: nil,
success: { (operation,responseObject) ->Void in
delegate.gotDataFromServer("") //parameter is your data
},
failure: { (operation , error) in
print ("error occurred")
})
}
}
Here's an example how you can do it using closure
class Parser {
func connectServer(apiURL: String, completion: String -> Void) {
// ... make call, get data
// share the results via completion closure
completion("data")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
let parser = Parser()
// Option #1
parser.connectServer("mybackend.com/connect") {
print("received data \($0)")
}
// Option #2 is the same as Option #1 but a bit longer
parser.connectServer("mybackend.com/connect") { (data) -> Void in
print("received data \(data)")
}
// Option #3 - Or if you have a separate funciton
// be careful with retain cycle
parser.connectServer("mybackend.com/connect", completion: gotDataFromServer)
}
func gotDataFromServer(response:String) { }
}

Using template/generic in swift with typealias

Supposing we have a method like this:
func retrieve<T>(completion: (response: ResponseItems<T>?, error: NSError?) -> Void) {
/* whatever */
}
Until you have to call this method "directly" there are no problems:
service.retrieve { (response: ResponseItems<Myclass>?, error) -> Void in
}
Now the question: if I want to call this method with a generic class type?
I have:
enum ClassType: String {
case FirstClass: "FirstClass"
case SecondClass: "SecondClass"
case FooClass: "FooClass"
}
func retrieveAll() {
for i in 1...3 {
/* suppose that this method returns class name from enum */
let classStringName = self.getClassName(i)
/* here TRICK */
service.retrieve{ (response: ResponseItems<WHAT??>?, error) -> Void in
}
}
}
Any help will be appreciated!
Notice that all class type in enum inherit from a base class Mappable.
update
Basically, retrieve method do a request to an API and return a jsonReponse, then I have to map it to a generic object, with ObjectMapper.
func retrieve<T>(completion: (response: ResponseItems<T>?, error: NSError?) -> Void) {
var response : T? = nil
response = Mapper<T>().map(jsonResponse)
completion(response: response, error: error)
}
I don't want to change retrieve function behaviuor, I want that it remain as "ignorant" as possibile and delegate the issue to the caller.

Resources