I'm trying to write a protocol for http get requests and use it in my view controllers. The idea is to prevent writing request in each VC and write it once in app. I wrote a protocol with get function and wrote an extension for it to implement get request using Alamofire.
protocol Irequest
{
func getRequest(url: String, completion: (JSON) -> ())
func postRequest(url: String, param: Parameters, completion: (JSON) -> ())
}
extension Irequest
{
func getRequest(url: String, completion:#escaping (JSON) -> ())
{
Alamofire.request(url.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!, method: .post)
.responseJSON { response in
debugPrint(response)
if let Json = response.result.value
{
let j = JSON(Json)
completion(j)
}
else
{
completion(JSON("{}"))
}
}
}
}
Now, when i wanna use it in my class it say class X doesn't conform protocol Irequest. How can i use it? and in more general term, how can i have one get request function in all app?
class X : UIViewController , Irequest
1.
the protocol's get method signature is != the extension's get signature. one has no #escaping, one does.
==> methods don't match, ergo get isn't there
2.
post isn't implemented at all
==> ergo method is missing
from the comments you already know about 2 so fix 1 and you should be good :)
Related
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.
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
....
}
}
I'm looking to create a very generic service layer that looks to call Alamofire. See code:
func getRequest(from endpoint:String!, withParameters parameters:[String:Any]?,withModel model:RuutsBaseResponse, andCompleteWith handler:#escaping (RuutsBaseResponse?, NSError?) -> ()){
func generateModel(withResponse result:NSDictionary?, withError error:NSError?) -> (){
handler(model.init(fromDictionary: result),error);
}
alamoFireService.AlamoFireServiceRequest(endpoint:endpoint, httpVerb:.get, parameters:parameters!, completionHandler:generateModel);
}
This is what the RuutsBaseResponse looks like:
protocol RuutsBaseResponse {
init(fromDictionary dictionary: NSDictionary);
}
The getRequest looks to do the following:
Taken in any class so long as it conforms to RuutsBaseResponse protocol.
Make a service call using alamoFire using the parameters passed into it.
alamoFire will call the generateModel method once the service call is completed.
When it calls the generateModel the method is supposed to instantiate the model and pass into it the Dictionary received from alamoFire.
The issue is the model, i'm struggling to achieve the requirements above. I keep getting:
Error:(22, 21) 'init' is a member of the type; use 'type(of: ...)' to
initialize a new object of the same dynamic type
All I'm looking to do is make a layer generic enough to make a service call and create an object/model that is created from the Dictionary passed back from alamoFire.
What you are looking for is how to use Generics:
protocol RuutsBaseResponse {
init(fromDictionary dictionary: NSDictionary);
}
struct BaseModel: RuutsBaseResponse {
internal init(fromDictionary dictionary: NSDictionary) {
print("instantiated BaseModel")
}
}
struct SecondaryModel: RuutsBaseResponse {
internal init(fromDictionary dictionary: NSDictionary) {
print("instantiated SecondaryModel")
}
}
// state that this function handles generics that conform to the RuutsBaseResponse
// protocol
func getRequest<T: RuutsBaseResponse>(handler: (_ response: T) -> ()) {
handler(T(fromDictionary: NSDictionary()))
}
getRequest(handler: { model in
// will print 'instantiated BaseModel'
(model as! BaseModel)
})
getRequest(handler: { model in
// will print 'instantiated SecondaryModel'
(model as! SecondaryModel)
})
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.
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)
}
}