Async task don't change external variable. Swift 3 - ios

I can't save data from URL, because function in infinit loop. How fix it?
My code:
func getRegion2(){
let method = "region/"
var url = serviceUrl+method
var myArray: [String]()
while(url != nil){
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success(let data):
let nextUrl = JSON(data)["next"].stringValue
url = nextUrl
myArray = myArray + myArray
print(nextUrl)
case .failure(let error):
print("Request failed with error: \(error)")
}
}
}
print(myArray)
}
If run without the "while", then everything works fine.

One possible solution is to combine a recursive function and a dispatch group (not tested):
func getRegion2(){
let method = "region/"
var url = serviceUrl+method
var myArray: [String] = []
let group = DispatchGroup()
func getRegion(with url: String) {
group.enter()
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success(let data):
let nextUrl = JSON(data)["next"].stringValue
myArray = myArray + someArrayFromRespnse
print(nextUrl)
if nextUrl != nil {
getRegion(with: nextUrl)
}
group.leave()
case .failure(let error):
print("Request failed with error: \(error)")
}
}
}
getRegion(with: url)
group.notify(queue: DispatchQueue.main) {
print(myArray)
}
}
I would use a completionBlock:
func getRegion2(completion: () -> [String]?) {
let method = "region/"
var url = serviceUrl+method
var myArray: [String] = []
func getRegion(with url: String) {
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success(let data):
let nextUrl = JSON(data)["next"].stringValue
myArray = myArray + someArrayFromRespnse
print(nextUrl)
if nextUrl != nil {
getRegion(with: nextUrl)
} else {
completion(myArray)
}
case .failure(let error):
completion(nil)
}
}
}
getRegion(with: url)
}

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

JSON Decoding Not Populating Table View

I am trying to parse data from the website movieDatabase.com However there's some issue decoding the data to json and populating my table view.I am not sure why this is happening. Please I need help spotting out the problem. Here's my code. https://github.com/lexypaul13/Movie-Browser/tree/main/Movie-Browser
struct Movies: Codable {
let overview:String?
let original_title: String?
let poster_path:String
}
struct ApiResponse:Codable, Hashable {
let page:Int
let shows:[Movies]
enum CodingKeys:String, CodingKey {
case page = "page"
case shows = "results"
}
}
class NetworkManger{
enum EndPoint{
case showList
}
static let shared = NetworkManger()
private let baseURL : String
private var apiKeyPathCompononent :String
private init(){
self.baseURL = "https://api.themoviedb.org/3/movie/now_playing?"
self.apiKeyPathCompononent = "api_key=a07e22bc18f5cb106bfe4cc1f83ad8ed"
}
private var jsonDecoder:JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
func get<T:Decodable>(_ endPoints: EndPoint, urlString: String, completed:#escaping(Result<T?,ErroMessage>)->Void){
guard let url = urlBuilder(endPoint: endPoints) else {
completed(.failure(.invalidURL))
return
}
let task = URLSession.shared.dataTask(with: url){ data, response, error in
if let _ = error {
completed(.failure(.unableToComplete))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode==200 else {
print(ErroMessage.invalidResponse.rawValue)
completed(.failure(.invalidResponse))
return
}
guard let data = data else{
completed(.failure(.invalidData))
return
}
do{
let apiResponse = try self.jsonDecoder.decode([T].self, from: data)
DispatchQueue.main.async {
completed(.success(apiResponse as? T))
}
} catch{
print(ErroMessage.invalidData.rawValue)
}
}
task.resume()
}
private func urlBuilder(endPoint:EndPoint )->URL?{
switch endPoint {
case .showList:
return URL(string: baseURL + apiKeyPathCompononent )
}
}
func getMovies(){
NetworkManger.shared.get(.showList, urlString: "") { [weak self] (result: Result<[Movies]?,ErroMessage> ) in
guard let self = self else { return }
switch result{
case .success(let movies):
self.movies = movies ?? []
DispatchQueue.main.async {self.tableView.reloadData()}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
The root object returned from the api is your ApiResult struct. This contains an array of movies (which you have mapped to the shows property of the ApiResult)
You need to change the getMovies function so that the right generic type can be inferred and the json decoder can do the right thing
func getMovies(){
NetworkManger.shared.get(.showList, urlString: "") { [weak self] (result: Result<ApiResult,ErroMessage> ) in
guard let self = self else { return }
switch result{
case .success(let apiResult):
self.movies = apiResult.shows
DispatchQueue.main.async {self.tableView.reloadData()}
case .failure(let error):
print(error.localizedDescription)
}
}
}

Unstable error message from the API in Xcode?

while i am trying to decode some json data using Alamofire and PromiseKit, I am encountering some unstable error message.. why I am saying " unstable error message is that sometimes when I hit the API .GET request, i am getting the response in full format, without any error, but after a couple of times running the application Xcode throwing an Alamofire Response Serialization Error which is so confusing.. . I have implemented Coding keys too, data and json response formats are also in right format.. can anyone please help me decode this error message .?
here is the response i got from the API in one of random application run:
and after the next run, here is the error message from the xcode console:
Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))))
if any part of the code is required for more analysis, please let me know so that I will update the question contents with those required pieces of code.. .
here is my json response model structs:
// MARK: - TickerByPair
struct TickerByPair {
var price, ask: Double
var askVolume: Double
var bid: Double
var bidVolume, volume: Double
var time: String
}
extension TickerByPair: Decodable{
enum TrackCodingKeys: String, CodingKey {
case price = "price"
case ask = "ask"
case askVolume = "askVolume"
case bid = "bid"
case bidVolume = "bidVolume"
case volume = "volume"
case time = "time"
}
init(from decoder: Decoder) throws {
let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
if trackContainer.contains(.price){
price = try trackContainer.decode(Double.self, forKey: .price)
}else{
price = 0
}
if trackContainer.contains(.ask) {
ask = try trackContainer.decode(Double.self, forKey: .ask)
} else {
ask = 0
}
if trackContainer.contains(.askVolume) {
askVolume = try trackContainer.decode(Double.self, forKey: .askVolume)
} else {
askVolume = 0
}
if trackContainer.contains(.bid) {
bid = try trackContainer.decode(Double.self, forKey: .bid)
} else {
bid = 0
}
if trackContainer.contains(.bidVolume) {
bidVolume = try trackContainer.decode(Double.self, forKey: .bidVolume)
} else {
bidVolume = 0
}
if trackContainer.contains(.volume) {
volume = try trackContainer.decode(Double.self, forKey: .volume)
} else {
volume = 0
}
if trackContainer.contains(.time) {
time = try trackContainer.decode(String.self, forKey: .time)
}
else {
time = ""
}
}
}
// MARK: - TradingPairElement
struct TradingPair {
var id: Int
var name: String
var quoteAsset: String
var baseAsset: String
}
extension TradingPair: Decodable {
enum TrackCodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case quoteAsset = "quoteAsset"
case baseAsset = "baseAsset"
}
init(from decoder: Decoder) throws {
let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
if trackContainer.contains(.id){
id = try trackContainer.decode(Int.self, forKey: .id)
}else{
id = 0
}
if trackContainer.contains(.name) {
name = try trackContainer.decode(String.self, forKey: .name)
} else {
name = ""
}
if trackContainer.contains(.quoteAsset) {
quoteAsset = try trackContainer.decode(String.self, forKey: .quoteAsset)
} else {
quoteAsset = ""
}
if trackContainer.contains(.baseAsset) {
baseAsset = try trackContainer.decode(String.self, forKey: .baseAsset)
} else {
baseAsset = ""
}
}
}
and the API .GET request using Alamofire :
class ServerCommunicator {
static func getAssets() -> Promise<[Assets]> {
let decoder = JSONDecoder()
return Promise { seal in
AF.request(API.assets, method: .get, parameters: .none, headers: .none).responseDecodable(of: [Assets].self, decoder: decoder) { response in
switch response.result {
case .success(let assets):
return seal.fulfill(assets)
case .failure(let error):
return seal.reject(error)
}
}
}
}
static func getPairs() -> Promise<[TradingPair]> {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .useDefaultKeys
return Promise { seal in
AF.request(API.tradingPairs, method: .get, parameters: .none, headers: .none).responseDecodable(of: [TradingPair].self, decoder: decoder) { response in
switch response.result {
case .success(let pairs):
return seal.fulfill(pairs)
case .failure(let error):
return seal.reject(error)
}
}
}
}
static func getPair(with pairName: String?) -> Promise<TickerByPair> {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .useDefaultKeys
return Promise { seal in
AF.request(API.getPairByTicker(pairName: pairName!), method: .get, parameters: .none, headers: .none).responseDecodable(of: TickerByPair.self, decoder: decoder) { response in
switch response.result {
case .success(let ticker):
return seal.fulfill(ticker)
case .failure(let error):
return seal.reject(error)
}
}
}
}
}
and my json response format is also as follows:
[
{
"name": "ETH-KRW",
"baseAsset": "ETH",
"quoteAsset": "KRW"
}, {
"name": "BTC-KRW",
"baseAsset": "BTC",
"quoteAsset": "KRW"
}, {
"name": "BCH-KRW",
"baseAsset": "BCH",
"quoteAsset": "KRW"
}
]
-------------
{
"price": 10194500,
"ask": 10195000,
"bid": 10184500,
"volume": 1752.05558316,
"time": "2018-03-14T03:50:41.184Z"
}
If there are cases where your API that supposed to return array, returns dictionary instead, you can try re-implementing the convenient responseDecodable(of:decoder:) function of Alamofire.
Use the responseData(queue:completionHandler:) and try to decode your response data to array and if that fails try again to an object type.
Your getPairs() function for example could be something like this:
static func getPairs() -> Promise<[TradingPair]> {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .useDefaultKeys
return Promise { seal in
AF.request(API.tradingPairs, method: .get, parameters: .none, headers: .none).responseData { response in
switch response.result {
case .success(let data):
do {
let pairs = try decoder.decode([TradingPair].self, from: data)
return seal.fulfill(pairs)
} catch DecodingError.typeMismatch {
do {
let pair = try decoder.decode(TradingPair.self, from: data)
return seal.fulfill([pair])
} catch {
return seal.reject(error)
}
} catch {
return seal.reject(error)
}
case .failure(let error):
return seal.reject(error)
}
}
}
}
Or more generally, make an extension of DataRequest like this:
extension DataRequest {
#discardableResult func responseArray<T: Decodable>(
of type: T.Type,
decoder: DataDecoder,
completionHandler: #escaping (AFDataResponse<[T]>) -> Void
) -> Self {
responseData { response in
switch response.result {
case .success(let data):
do {
let array = try decoder.decode([T].self, from: data)
let dataResponse = self.dataResponse(from: response, result: .success(array))
completionHandler(dataResponse)
} catch DecodingError.typeMismatch {
do {
let object = try decoder.decode(T.self, from: data)
let dataResponse = self.dataResponse(from: response, result: .success([object]))
completionHandler(dataResponse)
} catch {
let dataResponse: AFDataResponse<[T]> = self.dataResponse(
from: response,
result: .failure(.responseSerializationFailed(reason: .decodingFailed(error: error)))
)
completionHandler(dataResponse)
}
} catch {
let dataResponse: AFDataResponse<[T]> = self.dataResponse(
from: response,
result: .failure(.responseSerializationFailed(reason: .decodingFailed(error: error)))
)
completionHandler(dataResponse)
}
case .failure(let error):
let dataResponse: AFDataResponse<[T]> = self.dataResponse(from: response, result: .failure(error))
completionHandler(dataResponse)
}
}
}
private func dataResponse<T: Decodable>(from response: AFDataResponse<Data>, result: Result<[T], AFError>) -> AFDataResponse<[T]> {
return .init(
request: response.request,
response: response.response,
data: response.data,
metrics: response.metrics,
serializationDuration: response.serializationDuration,
result: result
)
}
}
and then your getPairs() function will be much simpler:
static func getPairs() -> Promise<[TradingPair]> {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .useDefaultKeys
return Promise { seal in
AF.request(API.tradingPairs, method: .get, parameters: .none, headers: .none).responseArray(of: TradingPair.self, decoder: decoder) { response in
switch response.result {
case .success(let pairs):
return seal.fulfill(pairs)
case .failure(let error):
return seal.reject(error)
}
}
}
}

passing data (json) to another controller in swift 5.2 con Alamofire

I have a Login view that asks for a card and password. I consult an API and if the entered data is correct, it sends me a JSON like this. Which return has the button method? How do I send that data to the other view? I occupy Alamofire 5.0 and have my Model class.
#IBAction func myButtonIngresarAction(_ sender: Any) {
guard let carnet = self.txtCarnet.text else {return}
guard let contrasena = self.txtPassword.text else {return}
let ingresologinmodel = IngresoLoginModel(usuario: carnet, password: contrasena)
self.apiCall(IngresoLoginModel: ingresologinmodel){
(result) in
switch result{
case .success(let json):
print(json)
**//This is where I want to send that json with the data to the other view. ******
case .failure(let err):
print(err.localizedDescription)
}
}
}
enum ApiErros: Error {
case custom(message : String)
}
typealias Handler = (Swift.Result<Any?, ApiErros>) -> Void
func apiCall(IngresoLoginModel: IngresoLoginModel, completionHandler: #escaping Handler)
{
let header: HTTPHeaders = [
.contentType("application/json")
]
AF.request("https://url/xxxx/api/Login", method: .post, parameters: IngresoLoginModel,
encoder: JSONParameterEncoder.default, headers: header).response{ response in
debugPrint(response)
switch response.result{
case .success(let data):
do{
let json = try JSONDecoder().decode([LoginModel].self, from: data!)
print(json)
if response.response?.statusCode == 200{
completionHandler(.success(json))
}else{
completionHandler(.failure(.custom(message: "Por favor verifica tu internet")))
}
}
catch
{
print(error)
completionHandler(.failure(.custom(message: "Problemas")))
}
case .failure(let err):
print(err.localizedDescription)
}
}
}
Class model
struct LoginModel: Codable {
let idEmpleado: Int
let Nombre: String
let CodEmpleado: String
let password: String
let idPerfil: Int
let activo: Int
let Descripcion: String
let idRegion: Int
let correo: String
}
This is the json that the Api sends me the data changes them for these example
{
"idEmpleado": 1,
"nombre": “test”,
"codEmpleado": “000000”,
"password": “123”,
"idPerfil": 4,
"activo": 1,
"Descripcion": “test”,
"idregion": 1,
"correo": “test#test.com"
}
many way like create a variable to save this json in OtherViewController and call, self?.otherViewController.json = json
https://learnappmaking.com/pass-data-between-view-controllers-swift-how-to/
use didSet
var page = [Datas]() {
didSet {
self.myVariable = page[0].date!
}
}
typealias Handler = (Swift.Result <[LoginModel]?, ApiErros>) -> Void
#IBAction func myButtonIngresarAction(_ sender: Any) {
guard let carnet = self.txtCarnet.text else {return}
guard let contrasena = self.txtPassword.text else {return}
let ingresologinmodel = IngresoLoginModel(usuario: carnet, password: contrasena)
self.apiCall(IngresoLoginModel: ingresologinmodel){
(result) in
switch result{
case .success(let json):
print(json)
//Here is that I do not know how to send it to the other controller all the json
let viewControllerB = HomeMenuViewController()
viewControllerB.datosPersonales = json!
self.navigationController?.pushViewController(viewControllerB, animated: true)
case .failure(let err):
print(err.localizedDescription)
}
}
}
second controller
class HomeMenuViewController: UIViewController {
#IBOutlet weak var mylabel: UILabel!
var datosPersonales = [LoginModel]()
override func viewDidLoad() {
super.viewDidLoad()
print("***************")
print(datosPersonales)
print("***************")
}
}

Return value after Alamofire dowloaded data

I have a function that download data using Alamofire and then I would like to return that data. Now I know that Alamofire runs asynchronously and in order to to return data I should use completionHandler, however I don't get it how to use it. Since I am not the first with such problem, I found some solutions to similar problems, yet I am not sure to apply them to my case. Here is my code:
func downloadImageFromServer(imageUrl: String) -> (String, String) {
var myData1 = String()
var myData2 = String()
Alamofire.request(.GET, imageUrl)
.responseImage { response in
switch response.result {
case .Success:
if let newImage = response.result.value {
myData1 = //returned image name
myData2 = //edited image name
}
case .Failure:
//Do something
}
}
return (myData1, myData2)
}
Should I do something like this:
func downloadImageFromServer(imageUrl: String, completionHandler: (String?, String?) -> ()) {
var myData1 = String()
var myData2 = String()
Alamofire.request(.GET, imageUrl)
.responseImage { response in
switch response.result {
//if user does have a photo
case .Success:
myData1 = //Something
myData2 = //Something else
completionHandler(myData1 as? String, myData2 as? String)
case .Failure:
//Print error
}
}
}
Update
Yes, my question is very similar to that other question, however in my case, code has to return 2 values and that's where I find difficulties.
Here's my code for gettin values:
func getImages(orders: String, completionHandler: (String?, String?) -> ()) {
justDoIt(orders, completionHandler: completionHandler)
}
and then
getImages(imgURL) { responseObject, error in
print(responseObject)
return
}
And it works, however I am able to access only first value of the two, how to access both?
Your approach is correct. You could use another variable in your closure to see if the request was called properly (or another variable in function, e.g. errorHandler). Example of usage:
downloadImageFromServer(imgURL) { (data1, data2) in
print("Data1: \(data1). Data2: \(data2)")
}
Basic example of adding success/failure variable to your function:
func downloadImageFromServer(imageUrl: String, completionHandler: (Bool, String?, String?) -> ()) {
var myData1 = String()
var myData2 = String()
Alamofire.request(.GET, imageUrl)
.responseImage { response in
switch response.result {
//if user does have a photo
case .Success:
myData1 = //Something
myData2 = //Something else
completionHandler(true, myData1 as? String, myData2 as? String)
case .Failure:
completionHandler(false, nil, nil)
//Print error
}
}
}
Usage of the improved version of downloadImageFromServer():
downloadImageFromServer(imgURL) { (succes, data1, data2) in
if success {
print("Success. Data1: \(data1). Data2: \(data2)")
} else {
print("Error. Data1: \(data1). Data2: \(data2)")
}
}

Resources