NSMutableArray remains empty - ios

my knowledge on closures is still premature, Im having issues with adding objects to an NSMutableArray from inside a dispatch_async call which retrieves json data from the web, im attempting to add objects to an NSMutableArray which is initialized outside the function but it remains empty when i add objects inside the function, im sure there is another way around this ..... please help! And by the way if I remove the dispatch_async then everything works fine but i want to do the network call on another thread.
class Users:NSObject {
var returnedUsersDetailsFromWeb = NSMutableArray()
func detailsOfUsersFromWeb(){
let url = NSURL(string: "http://Sheldons-MacBook-Pro.local/real/get_data.php")
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { () -> Void in
if let data = NSData(contentsOfURL: url!){
var error:NSError?
let arr = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as! NSArray
for var i = 0 ; i < (arr as NSArray).count ; i++ {
self.returnedUsersDetailsFromWeb.addObject((arr as NSArray) .objectAtIndex(i))
}
}
}
println(self.returnedUsersDetailsFromWeb) // this is empty, if i remove the dispatch_async then this prints out everything
}
}

Related

Extracting data from API (JSON format) doesn't save data outside of function call

I am trying to get an array of temperatures in a given time period from an API in JSON format. I was able to retrieve the array through a completion handler but I can't save it to another variable outside the function call (one that uses completion handler). Here is my code. Please see the commented area.
class WeatherGetter {
func getWeather(_ zip: String, startdate: String, enddate: String, completion: #escaping (([[Double]]) -> Void)) {
// This is a pretty simple networking task, so the shared session will do.
let session = URLSession.shared
let string = "api address"
let url = URL(string: string)
var weatherRequestURL = URLRequest(url:url! as URL)
weatherRequestURL.httpMethod = "GET"
// The data task retrieves the data.
let dataTask = session.dataTask(with: weatherRequestURL) {
(data, response, error) -> Void in
if let error = error {
// Case 1: Error
// We got some kind of error while trying to get data from the server.
print("Error:\n\(error)")
}
else {
// Case 2: Success
// We got a response from the server!
do {
var temps = [Double]()
var winds = [Double]()
let weather = try JSON(data: data!)
let conditions1 = weather["data"]
let conditions2 = conditions1["weather"]
let count = conditions2.count
for i in 0...count-1 {
let conditions3 = conditions2[i]
let conditions4 = conditions3["hourly"]
let count2 = conditions4.count
for j in 0...count2-1 {
let conditions5 = conditions4[j]
let tempF = conditions5["tempF"].doubleValue
let windspeed = conditions5["windspeedKmph"].doubleValue
temps.append(tempF)
winds.append(windspeed)
}
}
completion([temps, winds])
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print("JSON error description: \(jsonError.description)")
}
}
}
// The data task is set up...launch it!
dataTask.resume()
}
}
I am calling this method from my view controller class. Here is the code.
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
}
//Here it prints out an empty array
print(weatherData)
}
The issue is that API takes some time to return the data, when the data is return the "Completion Listener" is called and it goes inside the "getWeather" method implementation, where it prints the data of array. But when your outside print method is called the API hasn't returned the data yet. So it shows empty array. If you will try to print the data form "weatherData" object after sometime it will work.
The best way I can suggest you is to update your UI with the data inside the "getWeather" method implementation like this:
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
// Update your UI here.
}
//Here it prints out an empty array
print(weatherData)
}
It isn't an error, when your controller get loaded the array is still empty because your getWeather is still doing its thing (meaning accessing the api, decode the json) when it finishes the callback will have data to return to your controller.
For example if you were using a tableView, you will have reloadData() to refresh the UI, after you assign data to weatherData
Or you could place a property Observer as you declaring your weatherData property.
var weatherData:[Double]? = nil {
didSet {
guard let data = weatherData else { return }
// now you could do soemthing with the data, to populate your UI
}
}
now after the data is assigned to wheaterData, didSet will be called.
Hope that helps, and also place your jsonParsing logic into a `struct :)

Json Values Append Array but ViewDidLoad it seem Empty

I am working in iOS and when I take JSON datas , I print it with no error in the function and append it a String array ( I did it correctly )
But if I want to reach to String array later the json Function it seem empty array . For testing I printed the array in a Button Action then it worked for me but I want to use in String Datas in other Function when Json Function ends.
The Codes:
func getAllDataFromURL()
{
var i : Int = 0;
var urlDatas : [String] = allJsonDatas.getAllTours();
for(i = 0 ; i < allJsonDatas.getAllTours().count ; i += 1)
{
Alamofire.request(.GET, urlDatas[i]).responseJSON
{(response) -> Void in
if let arrivedData = response.result.value
{
print("********")
print(arrivedData["generalTourDistrict"] as! String);
print(arrivedData["otherTourDistrict"] as! String);
print(arrivedData["photoURLS"] as! [String]);
print(arrivedData["subTourDistrict"] as! String);
print(arrivedData["tourCalendar"] as! String);
print(arrivedData["tourDistrict"] as! String);
print(arrivedData["tourName"] as! String);
self.tryingSomething.appendContentsOf(arrivedData["photoURLS"] as! [String]);
print("********\n")
}
}
}
}
And viewDidLoad is
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.Request.addAcceptableImageContentTypes(acceptableContentTypes);
self.getAllDataFromURL();
self.printTheJsonDatas();
}
For Example , in getAllDataFromURL() function I can print it correctly.
This is the result for url data from the json:
["https://acenta.dominant.com.tr//dominant/webout/R12/php/product/img.php?path=L2QvMS9jcmkvc295a3VsL3VydW4vMDAvMjIvMTIvaW1hZ2UvL3VfMDAyMjEyLjAwMDAxLmpwZWc=&rx=650&ry=400", "https://acenta.dominant.com.tr//dominant/webout/R12/php/product/img.php?path=L2QvMS9jcmkvc295a3VsL3VydW4vMDAvMjIvMTIvaW1hZ2UvL3VfMDAyMjEyLjAwMDA2LmpwZWc=&rx=650&ry=400"]
But printTheJsonDatas() prints an empty array like -> [ ]
func printTheJsonDatas()
{
print(tryingSomething)
//tryingSomething : [String]
}
Surprisingly , when I put printTheJsonDatas() into buttonClickedAction, then it worked as I said before.
I think the problem is about threads but I can not say anything clearly .
You are calling both your web service request and print functions consecutive like they are both syncronous but they are not. When you make a web servic request with Alamofire it works asyncronous. So you can not expect it to finish in a certain time in future. What you should do is call your print function in .response block of the Alamofire request:
func getAllDataFromURL()
{
var i : Int = 0;
var urlDatas : [String] = allJsonDatas.getAllTours();
for(i = 0 ; i < allJsonDatas.getAllTours().count ; i += 1)
{
Alamofire.request(.GET, urlDatas[i]).responseJSON
{(response) -> Void in
if let arrivedData = response.result.value
{
self.tryingSomething.appendContentsOf(arrivedData["photoURLS"] as! [String]);
self.printTheJsonDatas();
}
}
}
}
An other problem with your code is it is making multiple web service call. Even if you call your print function in .response block it probably wont work as you think. It will print multiple times with different sizes.
getAllDataFromURL() runs asynchronously with a completion block (known as a closure in swift, or callback on other languages). The order of events is:
getAllDataFromURL()
printTheJsonDatas()
Alamofire.request()
(wait for AF response)
self.tryingSomething.appendContentsOf(...)
If you want the print function to work correctly, you'll need to call it inside the completion block of the AF request, e.g.:
func getAllDataFromURL()
{
var i : Int = 0;
var urlDatas : [String] = allJsonDatas.getAllTours();
for(i = 0 ; i < allJsonDatas.getAllTours().count ; i += 1)
{
Alamofire.request(.GET, urlDatas[i]).responseJSON
{(response) -> Void in
if let arrivedData = response.result.value
{
self.tryingSomething.appendContentsOf(arrivedData["photoURLS"] as! [String]);
//
self.printTheJsonDatas();
// ^
}
}
}
}
You could also rewrite getAllDataFromURL() with a closure to be asynchronous (the more appropriate way to write this, I'd say):
func getAllDataFromURL(completion: (result: Array) -> Void) {
...
var returnArray = []
// ^ here this is a local variable, doesn't need to be global anymore
returnArray.appendContentsOf(arrivedData["photoURLS"] as! [String]);
completion(returnArray)
}

How to download and process 2 sets of data, one after the other?

I am making a webservice call to get a list of data. After I have all of that data I would like to make a web call for every item on that list. Here is the code I have this far:
let zipcde:String = self.zipCode
let username:String = "tr1gger"
//webservice call
var listZip = [String]()
let wsUrl: NSURL = NSURL(string: "http://api.url.org/findNearbyTheseCodesJSON?postalcode=" + zipcde + "&maxRows=100&country=US&radius=25&username=" + username)!
let task = NSURLSession.sharedSession().dataTaskWithURL(wsUrl, completionHandler: { (data, response, error) -> Void in
//will happen when task is complete
if let urlContent = data {
let jsonObject = JSON(data: urlContent)
if let jsonDict:JSON = jsonObject["postalCodes"]{
let postalCode = "postalCode"
for var i:Int = 0; i < jsonDict.count; i++ {
print("Property: \"\(jsonDict[i][postalCode])\"")
listZip.append(String(jsonDict[i][postalCode]))
}
self.showLoadingMessage()
self.listOfZips = listZip
self.getStores()
}
}
})
task.resume()
the self.getStores is a function that begins a for loop and calls a webservice for every item on the first list:
func getStores(){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
for areaCode in self.listOfZips{
let url = NSURL(string: "http://myUrl.thisPlace.net/getStores.php?zipcode=" + areaCode + "&ammoType=" + self.aType)!
let task2 = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data2, response2, error2) -> Void in
if let jsonObject2: NSArray = (try? NSJSONSerialization.JSONObjectWithData(data2!, options: NSJSONReadingOptions.MutableContainers)) as? NSArray{
var storeName = [String]()
var storeAddress = [String]()
var storeId = [String]()
var ammoStId = [String]()
var ammoStock = [String]()
var ammoPrice = [String]()
for obj in jsonObject2{
if let name: String = obj["storeName"] as? String{
storeName.append(name)
storeAddress.append((obj["storeAddress"] as? String)!)
storeId.append((obj["storeId"]as? String)!)
}
else if let id: String = obj["storeId"] as? String{
ammoStId.append(id)
ammoStock.append((obj["ammoStock"] as? String)!)
if let priceTemp: String = obj["ammoPrice"] as? String{
ammoPrice.append(priceTemp)
}
}
}
var storeList = [StoreItem]()
for var index:Int = 0; index < storeId.count; ++index{
let sId = storeId[index]
for var i:Int = 0; i < ammoStId.count; ++i{
let aId = ammoStId[i]
if sId == aId{
//creating object
let storeItem:StoreItem = StoreItem()
storeItem.setAddress(storeAddress[index])
storeItem.setName(storeName[index])
storeItem.setId(Int(storeId[index])!)
storeItem.setAmmoStock(Int(ammoStock[i])!)
storeItem.setAmmoPrice(ammoPrice[i])
storeList.append(storeItem)
}
}
}
self.storeListFinal.appendContentsOf(storeList)
}
self.myAlert.dismissViewControllerAnimated(true, completion: nil)
self.tableView.reloadData()
})
task2.resume()
}
})
As you can see I am populating a table at the end of this call. This code takes about 18-20 seconds to finish. On my android version it takes like 2 seconds. How can I optimize this?
Thanks for any help.
Invoke UI update (reload table) in background thread can cause delay. So, you should move the UI update code to main thread:
let task2 = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data2, response2, error2) -> Void in
....
dispatch_async(dispatch_get_main_queue(), {
self.myAlert.dismissViewControllerAnimated(true, completion: nil)
self.tableView.reloadData()
})
})
This way above can temporary solve your problem, but, it will reload you table many times because you create many tasks in loop. You should think another way to improve this.
I am making a webservice call to get a list of data. After I have all of that data I would like to make a web call for every item on that list.
This sort of situation is exactly the subject of the WWDC 2015 video on Advanced NSOperations. By using an NSOperation to encapsulate each your network "tasks", you can create the dependencies you need (the web service call must complete successfully before anything else can happen) and queue up the other "tasks" (the web call for each item) so that they are all performed independently and in good order. You'll find that the logic of expressing this sort of ordered dependency is much easier to express if you use NSOperations in this way. Watch the video, you'll see what I mean.
You used shared NSURLSession and it seems to that session queued what you requested. The secondary queries are not executed in same time, I think this is the reason for slow performance.
You can change max concurrent connection count for each host by below code:
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.HTTPMaximumConnectionsPerHost = 20;
Above codes may have to be called before any shared NSURLSession is created. To make it sure, I suggest that you should create a custom NSURLSession that uses explicitly defined configuration.
ps. IMO, changing serve scripts so the sever can provide results with just 1 call is Best Solution if You can.
use, dispatch_sync with background thread.
You can find solution, in my post, there: Swift Serial Dispatch Block only finish after delegate

Work with multi-diminutional array dynamically as UIImage array

I'm working on app to do paged photos inside scrollViews like in the photos app to swipe right to get the old photos and swipe left to get the new photos until the end of photos.
Alright,so i am getting the photos as a multi-diminutional from the web :
imageArray[indexPath.row][0] as? String
Thats in the ViewController1 to show all the images in CollectionView .
When the user press on a photo i do segue and show the image larger in ViewController2 so if swipe left it show the new photos and right to show the old photos which is stored in the array.
but i need to covert my two-dimensional array to one and use it dynamically to be something like this :
pageImages = [UIImage(named:"photo1.png")!,
UIImage(named:"photo2.png")!,
UIImage(named:"photo3.png")!,
UIImage(named:"photo4.png")!,
UIImage(named:"photo5.png")!]
how is it possible to do ?
i could say like :
pageImages = [UIimage(named:thewholearray)] ?
i tried first to convert to one-diminutional array but i failed :
var imageArray : NSArray = []
var mybigarray : NSArray = []
for (var i=0; i<=self.imageArray.count; i++) {
self.mybigarray = self.imageArray[i][0] as! NSArray
}
which generate this casting error :
Could not cast value of type '__NSCFString' (0x196806958) to 'NSArray' (0x196807308).
You can use the map-function to extract the image names from your multi-dimensional array.
var imageNames:[String] = imageArray.map({
$0[0] as! String
})
The map function iterates through all array entries like a for-in-loop.
The statement in the closure determines the new entry of your imageNames array.
EDIT:
If you don't use Swift-Arrays:
var imageNames:NSMutableArray = []
for image in imageArray{
imageNames.addObject(image[0] as! String)
}
I looked at the tutorial and I think this will help with the answer from Daniel. Keep Daniel's Answer and use the URLs from the array with the extension.
Add this to create you images from a URL
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if data != nil {
self.image = UIImage(data: data)
}
else {
self.image = UIImage()
}
}
}
}
}
Use this in the PagedImageScrollViewController:
newPageView.imageFromUrl(pageImages[page])
pageImages is your array of Strings.

Refreshing UITableView Asynchronously after Core Data Loaded Swift

I have a UITableView, tViewNews
I have a refresh function which downloads data from my server, stores it into core data, and then the table view loads this data from core data.
It works perfectly
func refresh(refreshControl: UIRefreshControl) {
self.newsArray = [NewsItem]()
self.newslists = [[NewsItem]]()
self.getNewsFromServer()
self.getNewsFromCoreData()
self.tViewNews.reloadData()
refreshControl.endRefreshing()
}
Now, when the user first opens the news viewDidLoad(), I would like for the table view to first load from the core data, then asynchronously populate my table view array from the server (using precisely the same method as the refresh function) and then reload the table view.
So I have tried the following code in my viewDidLoad() function.
override func viewDidLoad() {
super.viewDidLoad()
// Getting news from Core Data
getNewsFromCoreData()
// Updating core data from server and populating table view from core data
dispatch_async(dispatch_get_main_queue()) {
self.newsArray = [NewsItem]()
self.newslists = [[NewsItem]]()
self.getNewsFromServer()
self.getNewsFromCoreData()
self.tViewNews.reloadData()
}
As you can see the functions being run inside the async request are identical to those inside the refresh function.
But!, the refresh function functions properly, populating and reloading the table view. But the async request does absolutely nothing. The table view remains empty. But the functions are running, as tested with print statements.
Anyone know why this is the case?
EDIT - Added extra fucntions
Get Core Data
func getNewsFromCoreData() {
let temp = StaffCoreData()
temp.getAllNews()
newsDates = Dictionary<Int, [NewsItem]>()
for newsobj in temp.newsArray{
if var curdate = newsDates[toNewsItem(newsobj).age]{
curdate.append(toNewsItem(newsobj))
newsDates[toNewsItem(newsobj).age] = curdate
}else{
newsDates[toNewsItem(newsobj).age] = [toNewsItem(newsobj)]
}
}
for var i = 0; i<50; ++i{if let curdate = newsDates[i]{newslists.append(curdate)}}
Get Server Data
runs instance with following method:
func NewsCallReturnsWithSuccess(data: NSData) {
if let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) {
if let statusesArray = jsonObject as? NSArray{
newsArray.removeAll(keepCapacity: true)
for item in statusesArray {
let datacon : NSData = item.dataUsingEncoding(NSUTF8StringEncoding)!
let jsonObject : NSDictionary! = NSJSONSerialization.JSONObjectWithData(datacon, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
let title : NSString = jsonObject.objectForKey("title") as! NSString
let content : NSString = jsonObject.objectForKey("content") as! NSString
let publishedby : NSString = jsonObject.objectForKey("publishedby") as! NSString
let modified : NSString = jsonObject.objectForKey("modified") as! NSString
let imageurl : NSString = jsonObject.objectForKey("imageurl") as! NSString
let category : NSString = jsonObject.objectForKey("category") as! NSString
let newsItem = NewsItem(title: title as String, content: content as String, category: category as String, imageURL: imageurl as String, publishedBy: publishedby as String, modified: modified as String)
newsArray.append(newsItem)
}
//add complete array to CoreData
let temp = StaffCoreData()
temp.addArrayOfNew(newsArray)
}
}
}
When you are using a different thread for loading your data, you have to return to the main thread when finished loading to update the UI.
Like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// load data
dispatch_async(dispatch_get_main_queue()) {
// update ui
}
}
UPDATE
Pls show us your getNewsFromCoreData() and getNewsFromServer() methods and maybe your cellForRowAtIndexPath, too.
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// load data
self.newsArray = [NewsItem]()
self.newslists = [[NewsItem]]()
self.getNewsFromServer()
self.getNewsFromCoreData()
dispatch_async(dispatch_get_main_queue()) {
// update ui
self.tViewNews.reloadData()
}
}
}

Resources