I'm trying to write generic serializers for Alamofire and I'm trying to understand how I can make the generic response serializers determine if there was an error and return a custom error object instead (The server returns an error dictionary in that situation).
Any input would be appreciated!
Here's the example code that I'm currently using:
#objc public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Alamofire.Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
return (T(response: response!, representation: JSON!), nil)
} else {
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object, error)
})
}
}
Related
So we have this function that retrieves JSON data and presents it in its completion block, what I'm trying to understand is why use the signature: ((Data) -> Void) instead of just (Data), is the void really necessary? Here is the function:
typealias JSONData = ((Data) -> Void)
func getJSONData(type: String, urlExtension: String, completion: #escaping JSONData) {
let request = URLRequest(url: URL(string:"\(baseURL)\(type)/\(urlExtension)?api_key=\(apiKey)®ion=US&append_to_response=videos,images,releases")! )
let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) in
if error == nil {
if let httpResponse = response as? HTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
if let data = data {
completion(data)
}
default:
print(httpResponse.statusCode)
}
}
} else {
DispatchQueue.main.async {
if let error = error {
print("Error: \(error.localizedDescription)") }
return
}
}
})
dataTask.resume()
}
Swift syntax dictates that you must declare closures with a return type after the ->.
You have two options:
typealias JSONData = (Data) -> Void
typealias JSONData = (Data) -> ()
I see Apple using #1 most frequently.
This question already has answers here:
Run code only after asynchronous function finishes executing
(2 answers)
Closed 5 years ago.
I'm not very familiar with closure. I'm using this function to download a JSON file from a remote server
requestJson(){
// Asynchronous Http call to your api url, using NSURLSession:
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://api.site.com/json")!, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
// Access specific key with value of type String
let str = json["key"] as! String
} catch {
// Something went wrong
}
}
}).resume()
}
Is it possible to make the function requestJson() return the JSON file when its loaded? Or it's not possible because it's loaded asynchronously and could not be ready? Want I'm trying to do is something like following:
requestJson() -> **[String : AnyObject]**{
// Asynchronous Http call to your api url, using NSURLSession:
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://api.site.com/json")!, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
// Access specific key with value of type String
**return json**
} catch {
// Something went wrong
}
}
}).resume()
}
You can give the function params an #escaping callback returning an array or whatever you need;
An example of this for a network request;
class func getGenres(completionHandler: #escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
Then to call it you could do something like this;
override func viewDidLoad() {
getGenres {
genres in
print("View Controller: \(genres)")
}
}
//MARK: Request method to get json
class func requestJSON(completion: #escaping (returnModel: String?) -> Void) {
//here you write code for calling API
}
//MARK: Calling function to retrieve return string
API.requestJSON(completion: { (string) in
//here you can get your string
})
func httpGet(request: NSURLRequest!, callback: (NSString, NSString?) -> Void)
{
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
callback("", error.localizedDescription)
} else {
var result = NSString(data: data, encoding:
NSASCIIStringEncoding)!
callback(result, nil)
}
}
task.resume()
}
func makeRequest(callback: (NSString) ->Void) -> Void {
var request = NSMutableURLRequest(URL: NSURL(string: "http://sample_url")!)
var result:NSString = ""
httpGet(request){
(data, error) -> Void in
if error != nil {
result = error!
} else {
result = data
}
callback(data)
}
}
Usage:-
self.makeRequest(){
(data) -> Void in
println("response data:\(data)")
}
Hi I just migrated to alamofire 4 and I just want to send the error coming from the server, I found a couple ways but I just want to make sure that this is the correct way, here is my custom responseobject class
public protocol ResponseObject {
init?(response: HTTPURLResponse, representation: Any)
}
enum BackendError: Error {
case network(error: Error)
case dataSerialization(error: Error)
case jsonSerialization(error: Error)
case xmlSerialization(error: Error)
case objectSerialization(reason: String)
}
extension DataRequest {
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
func responseObject<T: ResponseObject>(
queue: DispatchQueue? = nil,
completionHandler: #escaping (DataResponse<T>) -> Void)
-> Self
{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else {
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
debugPrint(result)
return .failure(BackendError.network(error: error!))
}
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
I add a debugprint so see the error from the server and I see it but do I have to serialize de data again inside the error?? and how can I pass the message to my custom error?
I am trying to use this piece of code I got raywenderlich.com in Xcode 7. But at the return line is giving me error saying
Cannot convert return expression of type (NilLiteralConvertible,
NilLiteralConvertible) to return type Result<UIImage>
extension Alamofire.Request {
public static func imageResponseSerializer() -> GenericResponseSerializer<UIImage> {
return GenericResponseSerializer { request, response, data in
if data == nil {
return (nil, nil)
}
let image = UIImage(data: data!, scale: UIScreen.mainScreen().scale)
return (image, nil)
}
}
public func responseImage(completionHandler: (NSURLRequest, NSHTTPURLResponse?, UIImage?, NSError?) -> Void) -> Self {
return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: completionHandler)
}
}
See original code at http://www.raywenderlich.com/85080/beginning-alamofire-tutorial
It looks like when you converted your project to Swift 2 you also upgraded to AlamoFire 2.x. The tutorial was written for Swift 1.2 where the closure's signature was:
(NSURLRequest?, NSHTTPURLResponse?, NSData?) -> (SerializedObject?, NSError?)
With AlamoFire 2 the signature is now:
(NSURLRequest?, NSHTTPURLResponse?, NSData?) -> Result<SerializedObject>
This means your method needs to return .Success(image!) in the passing condition and .Failure(data, myError) in the failing condition. It also means you can't just pass image without unwrapping since that initializer is nullable and the result's parameter is not.
Your serializer could look something like this:
return GenericResponseSerializer { request, response, data in
guard let validData = data else {
let error = ...
return .Failure(data, error)
}
guard let image = UIImage(data: validData, scale: UIScreen.mainScreen().scale) else {
let error = ...
return .Failure(data, error)
}
return .Success(image)
}
For your error you could either define your own ErrorType enum that will be helpful to you or use AlamoFire.Error:
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: "Image parsing failed.")
Your responseImage function will need a similar change:
public func responseImage(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<UIImage>) -> Void) -> Self {
return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: completionHandler)
}
This will in turn require you to update code that uses responseImage but those error messages should be helpful.
It work for me, remove old Alamofire from Ray's sample, and add last version form git https://github.com/Alamofire/Alamofire, and change sample XMLResponseSerializer , for UIImage it looks like :
extension Alamofire.Request {
public static func imageResponseSerializer() -> ResponseSerializer<UIImage, NSError> {
return ResponseSerializer { request, response, data, error in
guard error == nil else { return .Failure(error!) }
guard let validData = data else {
let failureReason = "Image parsing failed."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
guard let image = UIImage(data: validData, scale: UIScreen.mainScreen().scale) else {
let failureReason = "Image format failed."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure( error)
}
return .Success(image)
}
}
public func responseImage(completionHandler: Response<UIImage, NSError> -> Void) -> Self {
return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: completionHandler)
}
}
I have updated to XCode 7 beta 6 and Alamofire needed to be updated to beta 3. In doing so, I'm having to update areas of the code that use Alamofire. One area in particular that I'm having difficulty updating is the code which is used to retrieve an image from a specified URL and load it into a UIImageView.
Previously, the extension for Alamofire that handled that was:
extension Alamofire.Request {
class func imageResponseSerializer() -> Serializer {
return { request, response, data in
if data == nil {
return (nil, nil)
}
let image = UIImage(data: data!, scale: UIScreen.mainScreen().scale)
return (image, nil)
}
}
func responseImage(completionHandler: (NSURLRequest, NSHTTPURLResponse?, UIImage?, NSError?) -> Void) -> Self {
return response(serializer: Request.imageResponseSerializer(), completionHandler: { (request, response, image, error) in
completionHandler(request!, response, image as? UIImage, error)
})
}
}
But not that is throwing the error
Use of undeclared type 'Serializer'
I do realize that Alamofire doesn't use Serializer anymore, but does anyone know where I can find some documentation or examples what to do now when retrieving images?
As you can find in the readme the serialization was rewritten.
You shoudl be able to use the method below:
public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
UIImage serializer for Alamofire, updated for Alamofire 2.0 and Swift 2
Alamofire 2.0 and Swift 2.0