Alamofire AFError when work with Flickr API - ios

I am using Alamofire to get JSON data from flickr API.
Here if the function, which prepares an array of URLs from the response:
func urlTolinkslist (tag: String) {
print (viewSearchBar.text)
let url = "https://api.flickr.com/services/feeds/photos_public.gne?tags=\(tag.replacingOccurrences(of: " ", with: "%20"))&format=json&nojsoncallback=1"
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let items = json["items"]
self.linksList = []
for i in 0..<items.count {
let item = items[i]
let media = item["media"]
var link = String(describing: media["m"])
self.linksList.append(link)
}
print("count: \(self.linksList.count)")
self.viewCollectionView.reloadData()
case .failure(let error):
print(error)
}
}
}
"tag" in the API URL is the parameter I get from a search bar. In most of the cases it works fine, but in some cases I get:
[Result]: FAILURE: responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid escape sequence around character 1029." UserInfo={NSDebugDescription=Invalid escape sequence around character 1029.}))
I think there may be some extra characters, but I don't know how to fix it.

Let Alamofire encode for you the parameters:
func urlTolinkslist (tag: String) {
let url = "https://api.flickr.com/services/feeds/photos_public.gne"
var dict = [String:AnyObject]()
dict["tags"] = tag
dict["format"] = "json"
dict["nojsoncallback"] = 1
Alamofire.request(.GET, url, parameters: dict, encoding: .URLEncodedInURL, headers: nil).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let items = json["items"]
self.linksList = []
for i in 0..<items.count {
let item = items[i]
let media = item["media"]
var link = String(describing: media["m"])
self.linksList.append(link)
}
print("count: \(self.linksList.count)")
self.viewCollectionView.reloadData()
case .failure(let error):
print(error)
}
}
}

Related

Swift - Alamofire - Completion Handler is not executed when I call my function

As the title says, I have a function with these parameters:
typealias Handler = (Swift.Result<Any?, APIErrors>) -> Void
func viewApi (ownerId: String, accessToken: String, completionHandler: #escaping Handler ){
var viewArray: [Information] = [Information]()
let headers: HTTPHeaders = [
.authorization("Bearer \(accessToken)")
]
let viewUrl = "\(viewTaskUrl)?userId=\(ownerId)"
let url = URL(string: viewUrl)
var request = URLRequest(url: url!)
request.method = .get
request.headers = headers
var taskList:[Task] = []
AF.request(request).responseJSON(completionHandler: { (response) in
var holder = Information(id: 0, title: "", description: "", status: "")
switch response.result {
case .success(let data):
do{
if response.response?.statusCode == 200 {
// completionHandler(.success("JSON HERE: \
(String(describing: json))"))
let jsonData = try JSONSerialization.data(withJSONObject: data)
let jsonString = String(data: jsonData, encoding: .utf8)
let trySplit = jsonString?.components(separatedBy: ",")
for i in 0...trySplit!.count-1{
if trySplit![i].contains("id"){
let id = trySplit![i].components(separatedBy: ":")[1]
holder.id = Int(id)!
}
if trySplit![i].contains("title"){
let title = trySplit![i].components(separatedBy: ":")[1]
holder.title = title
// print("TITLE: \(title)")
}
if trySplit![i].contains("description"){
let desc = trySplit![i].components(separatedBy: ":")[1]
holder.description = desc
}
if trySplit![i].contains("status"){
let stat = trySplit![i].components(separatedBy: ":")[1]
let edited = stat.replacingOccurrences(of: "}", with: "")
holder.status = edited
viewArray.append(holder)
}
}
}else{
// completionHandler(.failure(.custom(message: "Please check your network connectivity")))
print("Error")
}
}catch{
print(error.localizedDescription)
}
case .failure(let err):
print (err)
}
print("ARRAY: \(viewArray)")
for i in 0 ..< viewArray.count {
let task = Task()
let edited = viewArray[i].status.replacingOccurrences(of: "]", with: "")
let new = viewArray[i].title.replacingOccurrences(of: "\"", with: "")
task.taskName = new
task.id = viewArray[i].id
task.taskStatus = edited
task.taskDescription = viewArray[i].description
task.email = ownerId
taskList.append(task)
}
try! self.realm.write{
self.realm.add(taskList)
}
}).resume()
}
and that function runs well, it does what it needs to do by returning a JSON object and storing the contents in Realm but when I call it in my main ViewController, it does not go in the completionHandler and tries to store the contents from Realm into an array returns an empty array even though the Realm is populated. Here is the call in my ViewController:
self.APIManager.viewApi(ownerId: self.userId!, accessToken: self.token!) { (result) in
switch result{
case .success(_):
//does not run
print("SUCCESS COMPLETION")
case .failure(let err):
print(err.localizedDescription)
}
}
Is there any way for the function to run before the rest of the code is executed so that the array is not empty and so that it actually goes in my completionHandler?
P.S. Don't mind the janky way I'm storing the JSON Object since I still have to find a better way to store it into an array besides converting it to string and then cutting it
try something like this approach:
func viewApi (ownerId: String, accessToken: String, completionHandler: #escaping Handler ) {
//...
AF.request(request).responseJSON { response in // <-- here
//...
completionHandler(.success("JSON HERE:(String(describing: json))")) // <-- here
//...
}
//...
}
what is your Handler type ? and why the completion handler is not setting
return the handler type according to the failure, success example.
self.APIManager.viewApi(ownerId: self.userId!, accessToken: self.token!, completionHandler: CompletionHandler) { (result) in
switch result{
case .success(_):
completionHandler(success:true)
case .failure(let err):
print(err.localizedDescription)
completionHandler(success: false)
}
}
you can return the raw response as it is in the completion, write a helper class to process the json response as per your requirement

how to determine the specific 409 error from an AFError?

I have a method that returns a Single<(HTTPURLResponse, Any)> doing a call to a webservice.
This call returns an 409 for multiple reasons and this reason is passed as a JSON in the response.
I know the JSON is in the data attribute of the DataResponse object but I would like to have it in the AFError that I pass when an error occurs. I want to display the specific 409 error message related to the JSON response to the user to allow him understand what happened.
How could I do that ?
I searched for that in Stackoverflow and also on the github of Alamofire but couldn't find any help to my case.
return Single<(HTTPURLResponse, Any)>.create(subscribe: { single in
let request = self.sessionManager.request(completeURL, method: httpMethod, parameters: params, encoding: encoding, headers: headers)
request.validate().responseJSON(completionHandler: { (response) in
let result = response.result
switch result {
case let .success(value): single(.success((response.response!, value)))
case let .failure(error): single(.error(error))
}
})
return Disposables.create { request.cancel() }
})
I'm working with Alamofire 4.9.1
request.validate().responseJSON { (response) in
let statusCode = response.response?.statusCode ?? 0
guard statusCode != 409 else {
if let data = response.data, let errorJson = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
let errorMessage = errorJson["message"] as? String
let customError = CustomError(message: errorMessage)
single(.error(customError))
}
return
}
let result = response.result
switch result {
case let .success(value): single(.success((response.response!, value)))
case let .failure(error): single(.error(error))
}
}
I guess you can achieve your requirement by this way. create a custom Error class to pass the error to completion. dont forget to call completion if errorJson is not serialised.
class CustomError: Error {
var localizedDescription: String { message ?? "" }
var message: String?
init(message: String?) {
self.message = message
}
}

How to get key Value from post webservice API and fetch that to tableview

I got data from API in this format but the problem is that I want to get all questions and answers from the API but whenever I try to get the value by using the key value it returns nil value and application crashes
this is my api data looks like after getting into a dictionary
here's my code for getting data from API
Alamofire.request(url, method: .post, parameters: parameters,encoding: JSONEncoding.default, headers: header ).responseJSON {
response in
switch response.result {
case .success:
print(response)
if let result = response.result.value {
print(result)
let responseDict = result as! [String : Any]
print(responseDict)
let data = responseDict["Result"] as! [Any]
print(data)
}
break
case .failure(let error):
print(error)
}
}
You can try
if let res = responseDict["Result"] as? [[String:Any]] {
for item in res {
if let ques = item["Question"] as? String {
print(ques)
}
if let op = item["Options"] as? [[String:Any]] {
print(op)
}
}
}

Parsing JSON Lines with Alamofire/Codable

Is it possible to parse JSON lines with Alamofire and codable?
Here is my code right now.
Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseString {(response) in
switch response.result {
case .success(let value):
print ("response is \(value)")
case .failure(let error):
print ("error is \(error)")
}
}
This prints all the JSON lines as a string but I want to serialize the response as an array of JSON. How would I do that? The problem with JSON lines is that it returns each set of json on a separate line so it is not familiar to alamofire.
Here is what I tried as if this was traditional JSON which obviously it is not so this did not work:
Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON {(response) in
switch response.result {
case .success(let value):
print ("response is \(value)")
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let data = try! JSONSerialization.data(withJSONObject: value)
do {
let logs = try decoder.decode([Logs].self, from: data)
completion(logs)
} catch let error {
print ("error parsing get logs: \(error)")
}
case .failure(let error):
print ("failed get logs: \(error) ** \(response.result.value ?? "")")
}
}
For anybody unfamiliar with json lines here is the official format info: http://jsonlines.org
{"_logtype":"syslogline","_ingester":"agent","_ip":"40.121.203.183","pid":5573,"program":"docker","_host":"k8s-master-5A226838-0","logsource":"k8s-master-5A226838-0","_app":"syslog","_file":"/var/log/syslog","_line":"docker[5573]: I0411 00:18:39.644199 6124 conversion.go:134] failed to handle multiple devices for container. Skipping Filesystem stats","_ts":1491869920198,"timestamp":"2017-04-11T00:18:39.000Z","_id":"804760774821019649"}
{"_logtype":"syslogline","_ingester":"agent","_ip":"40.121.203.183","pid":5573,"program":"docker","_host":"k8s-master-5A226838-0","logsource":"k8s-master-5A226838-0","_app":"syslog","_file":"/var/log/syslog","_line":"docker[5573]: I0411 00:18:39.644167 6124 conversion.go:134] failed to handle multiple devices for container. Skipping Filesystem stats","_ts":1491869920198,"timestamp":"2017-04-11T00:18:39.000Z","_id":"804760774821019648"}
{"_logtype":"syslogline","_ingester":"agent","_ip":"40.121.203.183","pid":5573,"program":"docker","_host":"k8s-master-5A226838-0","logsource":"k8s-master-5A226838-0","_app":"syslog","_file":"/var/log/syslog","_line":"docker[5573]: I0411 00:18:37.053730 6124 operation_executor.go:917] MountVolume.SetUp succeeded for volume \"kubernetes.io/secret/6f322c04-e1d2-11e6-bca0-000d3a111245-default-token-swb07\" (spec.Name: \"default-token-swb07\") pod \"6f322c04-e1d2-11e6-bca0-000d3a111245\" (UID: \"6f322c04-e1d2-11e6-bca0-000d3a111245\").","_ts":1491869917193,"timestamp":"2017-04-11T00:18:37.000Z","_id":"804760762212941824"}
Here is an example of writing custom DataSerializer in Alamofire that you could use for decoding your Decodable object.
I am using an example from random posts json url https://jsonplaceholder.typicode.com/posts
Here is an example of Post class and custom serializer class PostDataSerializer.
struct Post: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
}
struct PostDataSerializer: DataResponseSerializerProtocol {
enum PostDataSerializerError: Error {
case InvalidData
}
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<[Post]> {
return { request, response, data, error in
if let error = error {
return .failure(error)
}
guard let data = data else {
return .failure(PostDataSerializerError.InvalidData)
}
do {
let jsonDecoder = JSONDecoder()
let posts = try jsonDecoder.decode([Post].self, from: data)
return .success(posts)
} catch {
return .failure(error)
}
}
}
}
You could simply hook this upto your Alamofire client which sends request to remote url like so,
let request = Alamofire.request("https://jsonplaceholder.typicode.com/posts")
let postDataSerializer = PostDataSerializer()
request.response(responseSerializer: postDataSerializer) { response in
print(response)
}
You could also do additional error checking for the error and http response code in serializeResponse getter of the custom serializer.
For you json line, it seems that each json object is separated with new line character in so called json line format. You could simply split the line with new line characters and decode each line to Log.
Here is Log class I created.
struct Log: Decodable {
let logType: String
let ingester: String
let ip: String
let pid: Int
let host: String
let logsource: String
let app: String
let file: String
let line: String
let ts: Float64
let timestamp: String
let id: String
enum CodingKeys: String, CodingKey {
case logType = "_logtype"
case ingester = "_ingester"
case ip = "_ip"
case pid
case host = "_host"
case logsource
case app = "_app"
case file = "_file"
case line = "_line"
case ts = "_ts"
case timestamp
case id = "_id"
}
}
And custom log serializer that you could use with your Alamofire. I have not handled error in the following serializer, I hope you can do it.
struct LogDataSerializer: DataResponseSerializerProtocol {
enum LogDataSerializerError: Error {
case InvalidData
}
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<[Post]> {
return { request, response, data, error in
if let error = error {
return .failure(error)
}
guard let data = data else {
return .failure(LogDataSerializerError.InvalidData)
}
do {
let jsonDecoder = JSONDecoder()
let string = String(data: data, encoding: .utf8)!
let allLogs = string.components(separatedBy: .newlines)
.filter { $0 != "" }
.map { jsonLine -> Log? in
guard let data = jsonLine.data(using: .utf8) else {
return nil
}
return try? jsonDecoder.decode(Log.self, from: data)
}.flatMap { $0 }
return .success(allLogs)
} catch {
return .failure(error)
}
}
}
}
Alamofire is extensible, so I'd suggest writing your own response ResponseSerializer that can parse JSON by line. It seems that each line should parse fine, they're just not parseable together since the whole document isn't valid JSON.

Alamofire receive and parse an array of strings swift

I getting the result as an array of strings like this
["India","America","Australia","China","Russia"]
And I'm using Alamofire to get the response using code. There's no error, but I got the result as null. Please help in parsing this.
sessionManager?.request(strURL, method: method, parameters: params, encoding: encoding , headers: headers).responseJSON { (response) in
switch response.result {
case .success:
let resJson = JSON(response.result.value!)
success(resJson)
break
case .failure(let error):
failure(error as NSError)
break
}
}
Try this:
if let responseData = response.result.value{
let responsevalue = responseData as? [String]
}
For anyone looking for another derived answer, just put this chunk of code after Alamofire.request(...):
.responseJSON(completionHandler: { (response) in
switch response.result{
case .success(let value):
// Here is your array of String
let arrayOfStrings = value as? [String]
case .failure(let error):
// Some code when error happens...
print(error.localizedDescription)
}
})
This solution using SwiftyJSON:
.responseJSON(completionHandler: { (response) in
switch response.result{
case .failure(let error):
print(error.localizedDescription)
case .success(let res):
let json = JSON(res)
let res = json["result"]
var models = [String]()
if let models1 = company["models"].array {
for model in models1 {
guard let mod = model.string else { return }
models.append(mod)
}
}
}
})

Resources