Swift array doesn't hold values - arrays

I ran into the following problem while trying to build something with Swift. Apparently, the values that I added into an array are not saved pass some point. They are sent just fine with the protocol while the task is running, but after it completes, if I try to see the values in the array, it returns empty.
What am i doing wrong? My guess is that it get deallocated after task finishes. If that is so, is there a way to make it strong? Is there something I should know about this task thingie? Can you please explain to me how this works and what I should do?
Here is the code:
var exchangeArray : ExchangeValues[] = [];
func fetchResult(){
var currenciesOrder = ["EUR", "USD", "GBP", "CHF", "NOK", "SEK", "DKK", "CZK","TRY", "BGN", "MDL", "PLN", "XDR", "XAU", "UAH", "RUB", "RSD","CAD", "AUD", "JPY", "EGP", "BRL","HUF", "MXN","KRW", "CNY","NZD","INR","AED", "ZAR"];
let dateFormat = NSDateFormatter();
dateFormat.dateFormat = "yyyy-MM-dd";
for days in 0..2 {
let daysToSubstract = Double(60*60*24*days);
let date : String = dateFormat.stringFromDate(NSDate().dateByAddingTimeInterval(-daysToSubstract));
var url: NSURL = NSURL(string: "http://openapi.ro/api/exchange/all.json?date=" + date);
var session = NSURLSession.sharedSession();
var task = session.dataTaskWithURL(url, completionHandler: {
(data, response, error) -> Void in
if (response != nil){
var err: NSError?;
if(err?) {
println("request Error \(err!.localizedDescription)");
}
//send the result to protocol
let results = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary;
let temp : NSDictionary = results["rate"] as NSDictionary;
for key in 0..currenciesOrder.count{
for (currencyKey : AnyObject, currencyValue : AnyObject) in temp {
if currenciesOrder[key] as String == currencyKey as String {
let tempExchange = ExchangeValues(currency: currencyKey as? String, value: currencyValue.doubleValue, date:date );
self.exchangeArray.append(tempExchange);
}
}
}
self.delegate?.didReceiveResults(self.exchangeArray);
} else {
println("error: \(error.localizedDescription)");
}
})
task.resume();
}
println("\(exchangeArray.count)");
}

I kind of figured out what the problem is:
The task block returns void, so I think it empties the array after it finishes. The result is to create another function that gets called from the task, where the array works just fine (it gets passed the values while they exist) and any further processing can be done there.
I hope this helps someone. The code is as easy as this:
func sendResults(array : ExchangeValues[]) -> Void{
println("\(exchangeArray.count)"); }
Of course, you can have the function return something if you need to.

Related

Using array of a struct outside or a function and do/catch loop [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 2 years ago.
I have been trying for hours to get the array results values outside of the function it's created in - having defined the var as global outside and such bit it always sets to nil outside the do braces
I also tried to create a return value function but I still cannot pass the variable back as I need to return outside of the do-braces
First question here - please be gentle - I'm a newbie and completing a college project!
func searchAPI(country: String, date: String) {
let headers = [
"x-rapidapi-host": "covid-19-data.p.rapidapi.com",
"x-rapidapi-key": "9d0431109emsh8f01caba0edb8d7p16cebdjsnc69ffe8f2569"
]
var request = URLRequest(url: URL(string: "https://covid-19-data.p.rapidapi.com/report/country/name?date-format=YYYY-MM-DD&format=json&date=\(date)&name=\(country)")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { (data, response, error) in
guard let data = data else {
return
}
do {
let httpResponse = response as? HTTPURLResponse
//print(httpResponse!)
let decoder = JSONDecoder()
let results = try decoder.decode([Covid].self, from: data)
print(results[0].country)
//print(results[0].provinces.count)
//print(results[0].provinces[0])
//print(results[0].provinces[0].active)
}
catch let error {
print("\(error)")
}
}
dataTask.resume()
return
}
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd"
let newDate = formatter.string(from: date)
return newDate
}
Here you need to use closure to get the results outside of the asynchronous call.
You need to modify the function definition and use the completion closure block to send the decoded data through it like this:
func searchAPI(country: String, date: String, completion: #escaping ([Covid]) -> Void) {
//...
let results = try decoder.decode([Covid].self, from: data)
completion(results)
}
Note: It would be better design to add the and Error property too since if the decoding or searchAPI fails to fetch data the error info can be used to show a message for the user. In this case you can modify the definition and completion handler like this:
func searchAPI(country: String, date: String, completion: #escaping ([Covid], Error?) -> Void) {
//...
if error != nil {
completion([], error)
}
//...
let results = try decoder.decode([Covid].self, from: data)
completion(results, nil)
//...
} catch {
completion([], error)
}
}
Better approach: A modern approach is to use the Result type and use the success and failure block to handle both scenarios as #Vadian has just posted.

convert array of string into Double in swift

I'm trying to convert a string into a double in swift. I managed to extract the string from a website (www.x-rates.com) into an array but I cannot convert it after in a double in order to make some work around this number. Can anyone tell me what I'm supposed to do or what I did wrong? I know that my label don't update now but I will do it later, the first thing that I'm trying to do is the conversion.
thx a lot!
Here is the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var moneyTextField: UITextField!
#IBAction func convert(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var message = ""
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
message = newContentArray[0]
var message = Float(newContentArray[0])! + 10
}
}
}
}
}
DispatchQueue.main.sync(execute: {
self.resultLabel.text = "the value of the dollar is " + message
}
)}
task.resume()
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I will talk about convert an Array of String to Array of Double.
In swift Array has a method called map, this is responsable to map the value from array, example, in map function you will receive an object referent to your array, this will convert this object to your new array ex.
let arrOfStrings = ["0.3", "0.4", "0.6"];
let arrOfDoubles = arrOfStrings.map { (value) -> Double in
return Double(value)!
}
The result will be
UPDATE:
#LeoDabus comments an important tip, this example is considering an perfect datasource, but if you have a dynamic source you can put ? on return and it will work, but this will return an array with nil
like that
let arrOfStrings = ["0.3", "0.4", "0.6", "a"];
let arrOfDoubles = arrOfStrings.map { (value) -> Double? in
return Double(value)
}
Look this, the return array has a nil element
If you use the tips from #LeoDabus you will protect this case, but you need understand what do you need in your problem to choose the better option between map or compactMap
example with compactMap
let arrOfStrings = ["0.3", "0.4", "0.6", "a"];
let arrOfDoubles = arrOfStrings.compactMap { (value) -> Double? in
return Double(value)
}
look the result
UPDATE:
After talk with the author (#davidandersson) of issue, this solution with map ou contactMap isn't his problem, I did a modification in his code and work nice.
first I replaced var message = "" per var rateValue:Double = 0.0 and replacedFloattoDouble`
look the final code
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var rateValue:Double = 0.0;
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
rateValue = Double(newContentArray[0])! + 10
}
}
}
}
}
//
print("Rate is \(rateValue)"); //Rate is 11.167
}
task.resume()
Hope to help you
The reason your code doesn’t work in my opinion is that you have two variables with the same name that are defined in different scopes and you use the wrong one at the end.
At the beginning you define
var message = ""
And then when converting to a number further down
var message = Float(newContentArray[0])! + 10
So change the last line to something like
var number = Float(newContentArray[0])! + 10
And use number in your calculations. Although I think
var number = Double(message)
should work equally fine since you have assigned newContentArray[0] to message already and Double is more commonly used than Float (I don’t understand + 10)

How get Single Url From an Array of urls?

This is My Code:
urll = NSURL(string: "http://xxxxxxxxxxx.com/api/?slider=uij6sdnb")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(urll) {(NSData, response, error) -> Void in
do {
let records = try NSJSONSerialization.JSONObjectWithData(NSData!, options: NSJSONReadingOptions.MutableContainers) as! NSArray
for record in records {
// let urlid = Int(record["slide_id"] as! String)
let urimage = record["slide_url"] as! String
self.urls = [urimage]
print(self.urls.count)
}
}
catch {
print("Json Error")
}
}
task.resume()
When I Print :
print(urimage)
it gaves me 4 url like this:
http://sdkladlkasjd1.jpg
http://sdkladlkasjd2.jpg
http://sdkladlkasjd3.jpg
http://sdkladlkasjd4.jpg
When I print:
print(urimage[1])
It gaves me :
'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
When i put it in another value :
var urls = [String]()
self.urls = [urimage]
and I print:
print(self.urls.count)
it gaves me
1
1
1
1
How on earch I can access one of this urls !?
I want to Show them on imageview but I can !
As Julian Kniephoff rightly mentioned, you are printing each URL in the for loop, thus you cannot access one particular one. However, there is also another issue, in that you are replacing the urls array with the latest url each time.
To solve this, simply replace the line self.urls = [urimage] with self.urls.append(urimage).
You can then access a particular image outside the for loop by doing something like self.urls[1].
This is also why printing the count of the array returns 1, since each time around you are setting the array to just the one latest element in the loop.
In the end, your code may look something like this
url = NSURL(string: "http://xxxxxxxxxxx.com/api/?slider=uij6sdnb")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) {(data, response, error) -> Void in
do {
let records = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSArray
for record in records {
//let urlid = Int(record["slide_id"] as! String)
let urimage = record["slide_url"] as! String
self.urls.append(urimage)
}
print(self.urls[1]) //Prints http://sdkladlkasjd2.jpg
}
catch {
print("Json Error")
}
//print(self.urls[1])
}
task.resume()

Swift - Populating an array with NSDictionary String data

I have successfully set up a session to pull json data from a site - it works. However, when I start to populate my array with the data the array is not populating the information. Please help.
Here is my Person class:
class Person {
var name1 = "name"
var info1 = "info"
init(name1: String, info1: String) {
self.name1 = name1
self.info1 = info1
}
}
Here is my setUpPerson function:
func setUpPerson() {
let url = NSURL(string: "http://www.my-sites-jsondata")
if url != nil {
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
let urlError = false
if error == nil {
var urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding) as NSString!
var data: NSData = urlContent.dataUsingEncoding(NSUTF8StringEncoding)!
do {
let jsonObject = try (NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSArray)!
var index1 = 0
while index1 < jsonObject.count {
var maindata = (jsonObject[index1] as! NSDictionary)
var nameA = maindata["Name"] as! String
var infoA = maindata["Info"] as! String
var data1 = Person(name1: nameA, info1: infoA)
self.arrayOfPersons.append(data1)
index1++
}
} catch {
print(urlError)
}
}
})
task.resume()
}
}
Your code looks all good to me.
To me your self.arrayOfPersons is also being set (I just tested it with my test app).
You're probably attempting to print it before it's been set in the async dataTaskWithURL block.
An async block performs in the background, so as the code written immediately after your block executes on the main thread, the code within the async block will not yet be complete.
To me, the code has definitely executed. But because the async code hasn't finished executing yet, you are not seeing it set.
You will have to wait until after the async code executes before checking for the new value.
You can try like this:
dispatch_async(dispatch_get_main_queue(), ^{
// Set your data on property here
});
I was able to successfully complete this project. The answer came in two fold...
I needed dispatch_async(dispatch_get_main_queue()
reloadData()

Swift Array: [AnyObject] vs. [xxxxxxxxClass] and the method "append"

Here's my code. You don't need to look at all of it. I added comments where I'm confused:
class ProductData: NSObject {
var title = ""
var icon = ""
private init(dict: NSDictionary){
title = dict["title"] as! String
icon = dict["icon"] as! String
super.init()
}
class func getTheData(fromJSONPath JSONPath: String) -> [ProductData] {
let JSONData = NSData(contentsOfFile: JSONPath)!
var JSONArray = [[String : AnyObject]]()
do {
JSONArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.MutableContainers) as! [Dictionary]
} catch { print("error")}
-----------------------------------------------------------------------------------------
//↓↓↓↓↓↓↓↓↓ different: data = "[AnyObject]()" or "[ProductData]()" ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
var data = [AnyObject]()
// var data = [ProductData]()
for d in JSONArray {
data.append(ProductData(dict: d))
}
return data as! [ProductData]
// return data
//↑↑↑↑↑↑↑↑↑ and here: return "data as! [ProductData]" or "data" ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
}
I use "var data = [ProductData](), retun data" first. There's no error or warning, but when I run my app, and run to the code data.append(ProductData(dict: d)), it crashes with the error: thread 1:exc_bad_access(code=1,address=0x10). What?!
I found a way to fix it: if I use var datas = [AnyObject]() and return datas as! [ProductData], it works very well.
I am so confused:
Why does [AnyObject] make the code OK?
When I use [ProductData], why does the code: data.append(ProductData(dict: d)) crash?
What is the different between [AnyObject] and [ProductData]?
Your original version works for me (screenshot) (only slightly modified for testing with my data). You shouldn't have to do this dance, something else is causing trouble.
I suggest cleaning up your class a bit and take advantage of Swift 2 using guard, map and error. It will be easier to debug and will work more efficiently anyway.
Here's an example. The only difference is that I'm using NSURL to access the data in my case and I've removed the icon value, but it's easy to change it back to your case.
class ProductData: NSObject {
var title = ""
private init(dict: [String : AnyObject]){
if let t = dict["title"] as? String { self.title = t }
super.init()
}
class func getTheData(fromJSONPath JSONPath: String) -> [ProductData] {
do {
// safely unwrap and typecast the values else return empty array
guard let url = NSURL(string: JSONPath),
let JSONData = NSData(contentsOfURL: url),
let JSONArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: [])
as? [[String : AnyObject]] else { return [] }
return JSONArray.map() { ProductData(dict: $0) }
} catch {
// this `error` variable is created by the `catch` mechanism
print(error)
// return empty array if unkown failure
return []
}
}
}
let test = ProductData.getTheData(fromJSONPath: "http://localhost:5678/file/test.json")
Note: I'm sure you know it but just in case for the readers, NSData(contentsOf... is a synchronous function, so it will block the main thread (unless executed from a background thread). It's better practice to use asynchronous functions when possible.

Resources