I'm quite new to multi-threading in iOS, and have some difficulties with the implementation of my task. I'd really appreciate any help from more experienced programmers.
I have one URL address, as a starting point, to which I make a request, check some info, and retrieve all the URL addresses contained on that web-page. After this, I need to do the same with new URL addresses again and again, until a specific number of URLs is checked.
I decided to go with Operations and OperationQueues, because I also need to be able to choose a maximum number of concurrent operations.
I created a custom async operation LoadOperation and applied it to each URL stored in an array. In the completion block, I add new URLs to the array, and call the same function, kind of recursively.
Also I created two queues:
A concurrent one for requests,
and
A serial one for accessing shared properties as follows:
class Queue<Item: Equatable> {
// concurrent queue for URL-requests
let loadOperationQueue = OperationQueue() // qos: .utilities
// serial operation queue for accessing shared properties
let serialOperationQueue = OperationQueue() // qos: .utilities
// maximum number of URLs that need to be checked ultimately
var maxNumberOfItems: Int = 1
var pendingItems: [Item] = [] // newly added URLs
var loadingItems: [Item] = [] // loading URLs
var processedItems: [Item] = [] // URLs that were checked
// all URLs for tableView dataSource
var allItems: [Item] {
return processedItems + loadingItems + pendingItems
}
func addToPending(_ item: Item) {
guard allItems.count < maxNumberOfItems else {return}
pendingItems.append(item)
}
func removeFromPending() -> Item? {
guard !self.pendingItems.isEmpty else { return nil }
let item = pendingItems.removeFirst()
loadingItems.append(item)
return item
}
func addToProcessed(_ item: Item) {
loadingItems = loadingItems.filter{$0 != item}
processedItems.append(item)
}
func isFull() -> Bool {
return allItems.count >= maxNumberOfItems
}
}
This is my searching function:
func startSearching() {
// iterate over array of URLs creating async operation
while let currentWebPage = queue.removeFromPending() {
let loadOperation = LoadOperation(currentWebPage, searchString: searchString)
loadOperation.completionBlock = {
let webPage = loadOperation.output!
self.queue.serialOperationQueue.addOperation {
// add checked URL to processed
self.queue.addToProcessed(webPage)
// add new urls to an array of pending URLs
for urlString in webPage.containedLinks {
//check if the limit of checked urls is not exceeded
guard !self.queue.isFull() else { return }
//check if this url is already in queue
guard !self.queue.allItems.contains(where: {$0.url == urlString}) else { continue }
self.queue.addToPending(WebPage(url: urlString, containedLinks: [], status: .pending))
}
// update UI
OperationQueue.main.addOperation {
self.viewDelegate?.reloadTable()
}
// repeat searching process with new urls
self.startSearching()
}
}
queue.loadOperationQueue.addOperation(loadOperation)
}
}
I can't figure out why:
When I run the app, the screen freezes. Even though all my queues are in the background (qos: utilities).
Sometimes when I try to scroll UITableView, I get sigabort due to index out of range, even though I try to access all properties in serial queue.
This is a data source code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter.getNumberOfRows()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "WebPageTableViewCell", for: indexPath) as! WebPageTableViewCell
let (url, status) = presenter.getCellContent(at: indexPath.row)
cell.addressLabel.text = url
cell.statusLabel.text = status
return cell
}
And functions from presenter:
func getNumberOfRows() -> Int {
return queue.allItems.count
}
func getCellContent(at index: Int) -> (url: String, status: String) {
return (url: queue.allItems[index].url, status: queue.allItems[index].status.description)
}
Related
I'm new to Swift and generally lack of experience in programming. Currently I'm working on a project trying to display a list of star war characters on view controller, but I'm having some issues in passing data through networking Manager. When I ran the program, I couldn't get the name label displayed on the screen.
I have checked the tableView cell and the label is connected with viewController. I feel that the problem is somewhere related with networking manager but couldn't figure out by myself.
var charactersArray: [Characters] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
getCharacters(){
DispatchQueue.main.async {
self.starWarTableViewController.reloadData()
}
}
self.title = "Star War Characters"
// print(charactersArray), returns an empty array
}
private func getURL() -> String {
return "https://swapi.dev/api/people/"
}
func getCharacters(completion: #escaping () -> Void) {
self.starWarTableViewController.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
self.starWarTableViewController.dataSource = self
self.starWarTableViewController.delegate = self
DispatchQueue.global(qos: .background).async {
for i in 1...20 {
NetworkingManager.shared.getDecodedObject(from: self.getURL() + "\(i)"){
(characters: Characters?, error) in
guard let characters = characters else{ return }
self.charactersArray.append(characters)
print(self.charactersArray) //this will return an array list of characters names
}
}
print(self.charactersArray) // here the characterArray is empty
}
completion()
}
For what I found, the issue seems in my networking manager or the table view cell
enum NetworkError: Error {
case invalidURLString
}
final class NetworkingManager{
static let shared = NetworkingManager()
private init(){
}
func getDecodedObject <T: Decodable> (from urlString: String, completion: #escaping (T?, Error?) -> Void){
guard let url = URL(string: urlString) else {
completion(nil, NetworkError.invalidURLString)
return
}
URLSession.shared.dataTask(with: url){
(data, response, error) in
guard let data = data else{
completion(nil, error)
return }
guard let characters = try? JSONDecoder().decode(T.self, from: data) else{ return }
completion(characters, nil)
}.resume()
}
}
class TableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
func configure (with characters: Characters) {
self.nameLabel.text = characters.name
}
}
Here is Characters
struct Characters: Decodable {
let name: String
enum CodingKeys: String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
// print(name), this will return a list of character names
}
}
This is the tableview extension
extension ViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.charactersArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.configure(with: self.charactersArray[indexPath.row])
// cell.nameLabel.text = "Hi"
return cell
}
}
Right now, you are initiating a series of asynchronous network requests, but calling the completion handler after the requests are initiated, rather than waiting for when them to finish. Thus, when you call completion, the results have not been received.
When you start a bunch of asynchronous tasks and you want to know when they are done, a common pattern is to use DispatchGroup, calling enter before the asynchronous call, calling leave in the completion handler, and then specifying what you want to do when they are all done in a closure supplied to notify. If you put your call to your own completion handler in notify, then it will not get called until the requests are done and you have data to present in the UI.
A few other observations:
getDecodedObject is already asynchronous method, so there's no need to dispatch it to a background queue.
You will want to update both the model and the UI from the main queue. (URLSession calls its completion handlers on a serial background queue.) To accomplish this can basically tell the aforementioned notify to run its closure on the .main queue. I would also not update the model object until you are ready to update the UI.
When performing a series of asynchronous network requests in parallel, you have no assurances of the order in which they may complete. So, you would generally want to use some structure that is independent of the order that the tasks finish, such as a dictionary. Then, when they are done, if you want to build a sorted array of results, then build the array of results from that.
Thus:
func getCharacters(completion: #escaping ([Characters]) -> Void) {
// to keep track of when the 20 requests finish
let group = DispatchGroup()
// temporary structure to keep track of the results
var charactersDictionary: [Int: Characters] = [:]
// perform the requests
for i in 1...20 {
group.enter()
NetworkingManager.shared.getDecodedObject(from: getURL() + "\(i)") { (characters: Characters?, error) in
defer { group.leave() }
guard let characters = characters else { return }
charactersDictionary[i] = characters
}
}
// when the group tells us that all 20 are done, let's build array of the
// results and pass it back to the main queue via the completion handler parameter.
group.notify(queue: .main) {
let results = (1...20).compactMap { charactersDictionary[$0] }
print(results)
completion(results)
}
}
Note, by the way, I have pulled the table configuration code out of this routine (as you really don't want to intermingle network code with UI code):
func configureTableView() {
starWarTableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
starWarTableView.dataSource = self
starWarTableView.delegate = self
}
I've also renamed that outlet to be starWarTableView, because it is not a view controller, but a table view:
Anyway, you would then call it like so:
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
getCharacters { characters in
self.charactersArray = characters
self.starWarTableView.reloadData()
}
}
Recently got stuck on a problem of assigning freshly downloaded JSON data to table view datasource variable. I suppose the problem is something obvious but my skill is not enough to gather the big picture. Let me share a bunch of code.
(1) A function retrieves the data from Open Weather Map API (defined in the separate class 'GetWeather').
func getMowForecast(completion: #escaping ((WeatherForecast?, Bool)) -> Void) {
let url = URL(string: "http://api.openweathermap.org/data/2.5/forecast?id=524901&APPID=b3d57a41f87619daf456bfefa990fce4&units=metric")!
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let json = try JSONDecoder().decode(WeatherForecast.self, from: data)
completion((json, true))
} catch {
print(error)
completion((nil, false))
}
} else {
print(error)
}
}
task.resume()
}
Everything works fine here. JSON loads correctly and fits the data model.
Here's a link to JSON data to be displayed in tableView: https://pastebin.com/KkXwxYgS
(2) A controller handles the display of retrieved JSON data in tableView format
import UIKit
class ForecastViewController: UITableViewController {
#IBOutlet weak var tableV: UITableView! // tableView outlet in the IB
let weatherGetter = GetWeather() // object to handle the JSON retrieval
var tableData: WeatherForecast? // tableView data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData?.list.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableVCCell
cell.dateLabel.text = "\(self.tableData?.list[indexPath.row].dt)"
cell.tempLabel.text = "\(self.tableData?.list[indexPath.row].main.temp)"
cell.feelsLikeLabel.text = "\(self.tableData?.list[indexPath.row].main.feels_like)"
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
tableV.delegate = self
tableV.dataSource = self
weatherGetter.getMowForecast { (data, status) in
if let data = data, status {
} else if status {
print("-------- Ошибка разбора данных прогноза погоды --------")
} else {
print("-------- Ошибка получения данных прогноза погоды --------")
}
self.tableData = data
print(self.tableData)
}
print(self.tableData?.list.count) // returns nil
self.tableData = weatherGetter.getMowForecast(completion: ((tableData, true))) // error - Cannot convert value of type '(WeatherForecast?, Bool)' to expected argument type '((WeatherForecast?, Bool)) -> Void'
}
}
The problem is - the table view gets nil datasource so it is unable to load the data and shows the blank screen.
I suppose the mistake is in scope - I try to retrieve the JSON data inside a function and it does not go anywhere else. What I am wondering about is - how comes that assigning the data to self.tableData does not makes any effect?
Could you please help.
Thank you!
Regards
First of all delete
print(self.tableData?.list.count) // returns nil
self.tableData = weatherGetter.getMowForecast(completion: ((tableData, true))) // error - Cannot convert value of type '(WeatherForecast?, Bool)' to expected argument type '((WeatherForecast?, Bool)) -> Void'
The error occurs because the method does not return anything and the completion handler syntax is wrong. Both lines are pointless anyway due to the asynchronous behavior of getMowForecast
Secondly I recommend to declare the data source array as a non-optional array of the type which represents List. Then you get rid of all those unnecessary optionals.
var tableData = [List]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableVCCell
let weatherData = self.tableData[indexPath.row]
cell.dateLabel.text = "\(weatherData.dt)"
cell.tempLabel.text = "\(weatherData.main.temp)"
cell.feelsLikeLabel.text = "\(weatherData.main.feels_like)"
return cell
}
To be able to display the data – as already mentioned by others – you have to reload the table view in the completion handler. And assign the data only if status is true.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
tableV.delegate = self
tableV.dataSource = self
weatherGetter.getMowForecast { [weak self] (data, status) in
if let data = data, status {
self?.tableData = data.list
DispatchQueue.main.async {
self?.tableV.reloadData()
}
} else if status {
print("-------- Ошибка разбора данных прогноза погоды --------")
} else {
print("-------- Ошибка получения данных прогноза погоды --------")
}
}
}
And consider that the message Ошибка разбора данных прогноза погоды will be never displayed.
You need to reload the table inside the callback as it's asynchronous
self.tableData = data
print(self.tableData)
DispatchQueue.main.async { self.tableV.reloadData() }
I'm loading my UITableView from an Api call but although the data is retrieved fairly quickly, there is a significant time delay before it is loaded into the table. The code used is below
import UIKit
class TrackingInfoController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table : UITableView?
#IBOutlet var indicator : UIActivityIndicatorView?
#IBOutlet var spinnerView : UIView?
var tableArrayList = Array<TableData>()
struct TableData
{
var dateStr:String = ""
var nameStr:String = ""
var codeStr:String = ""
var regionStr:String = ""
init(){}
}
override func viewDidLoad() {
super.viewDidLoad()
table!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
spinnerView?.hidden = false
indicator?.bringSubviewToFront(spinnerView!)
indicator!.startAnimating()
downloadIncidents()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func BackToMain() {
performSegueWithIdentifier("SearchToMainSegue", sender: nil)
}
//#pragma mark - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1 //BreakPoint 2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableArrayList.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomTableViewCell
cell.incidentDate.text = tableArrayList[indexPath.row].dateStr
cell.incidentText.text = tableArrayList[indexPath.row].nameStr
cell.incidentCode.text = tableArrayList[indexPath.row].codeStr
cell.incidentLoctn.text = tableArrayList[indexPath.row].regionStr
return cell //BreakPoint 4
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
AppDelegate.myGlobalVars.gIncName = tableArrayList[indexPath.row].nameStr
AppDelegate.myGlobalVars.gIncDMA = tableArrayList[indexPath.row].codeStr
performSegueWithIdentifier("SearchResultsToDetailSegue", sender: nil)
}
func alertView(msg: String) {
let dialog = UIAlertController(title: "Warning",
message: msg,
preferredStyle: UIAlertControllerStyle.Alert)
dialog.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
presentViewController(dialog,
animated: false,
completion: nil)
}
func downloadIncidents()
{
var event = AppDelegate.myGlobalVars.gIncName
var DMA = AppDelegate.myGlobalVars.gIncDMA
if event == "Enter Event Name" {
event = ""
}
if DMA == "Enter DMA" {
DMA = ""
}
let request = NSMutableURLRequest(URL: NSURL(string: "http://incident-tracker-api-uat.herokuapp.com/mobile/events?name=" + event)!,
cachePolicy: .UseProtocolCachePolicy,
timeoutInterval: 10.0)
request.HTTPMethod = "GET"
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if error != nil {
self.alertView("Error - " + error!.localizedDescription)
}
else {
do {
var incidentList: TableData
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? Array<Dictionary<String, AnyObject>> {
for item in json {
if let dict = item as? Dictionary<String, AnyObject> {
incidentList = TableData()
if let nameStr = dict["name"] as? String {
incidentList.nameStr = nameStr
}
if let codeStr = dict["dma"] as? String {
incidentList.codeStr = codeStr
}
if let dateStr = dict["supplyOutageStart"] as? String {
let tmpStr = dateStr
let index = tmpStr.startIndex.advancedBy(10)
incidentList.dateStr = tmpStr.substringToIndex(index)
}
if let regionStr = dict["region"] as? String {
incidentList.regionStr = regionStr
}
self.tableArrayList.append(incidentList)
}
}
self.spinnerView?.hidden = true
self.indicator?.stopAnimating()
self.table?.reloadData() //BreakPoint 3
}
}catch let err as NSError
{
self.alertView("Error - " + err.localizedDescription)
}
}
})
task.resume() //BreakPoint 1
}
When the class is run, it hits BreakPoint 1 first and then hits BreakPoint 2 and then quickly goes to BreakPoint 3, it then goes to BreakPoint 2 once more. Then there is a delay of about 20 to 30 seconds before it hits Breakpoint 4 in cellForRowAtIndexPath() and the data is loaded into the UITableView. The view is displayed quickly afterwards.
The data is retrieved quite quickly from the Web Service so why is there a significant delay before the data is then loaded into the tableView? Is there a need to thread the Web Service method?
You are getting server response in a background thread so you need to call the reloadData() function on the UI thread. I am suspecting that the wait time can vary depending on whether you interact with the app, which effectively calls the UI thread, and that's when the table actually displays the new data.
In a nutshell, you need to wrap the self.table?.reloadData() //BreakPoint 3 with
dispatch_async(dispatch_get_main_queue()) {
// update some UI
}
The final result would be
Pre Swift 3.0
dispatch_async(dispatch_get_main_queue()) {
self.table?.reloadData()
}
Post Swift 3.0
DispatchQueue.main.async {
print("This is run on the main queue, after the previous code in outer block")
}
The table view should begin to reload in a fraction of a second after you call tableView.reloadData().
If you make UI calls from a background thread, however, the results are "undefined". In practice, a common effect I've seen is for the UI changes to take an absurdly long time to actually take effect. The second most likely side-effect is a crash, but other, strange side-effects are also possible.
The completion handler for NSURLSession calls is run on a background thread by default. You therefore need to wrap all your UI calls in a call to dispatch_async(dispatch_get_main_queue()) (which is now DispatchQueue.main.async() in Swift 3.)
(If you are doing compute-intensive work like JSON parsing in your closure it's best to do that from the background so you don't block the main thread. Then make just the UI calls from the main thread.)
In your case you'd want to wrap the 3 lines of code marked with "breakpoint 3" (all UI calls) as well as the other calls to self.alertView()
Note that if you're sure the code in your completion closure is quick you can simply wrap the whole body of the closure in a call to dispatch_async(dispatch_get_main_queue()).
Just make sure you reload your tableview in inside the Dispatch main async, just immediately you get the data
I'm currently in the process of creating an app to display the latest football scores. I've connected to an API through a URL and pulled back the team names for the english premier league into an array of strings.
The problem seems to come from populating the iOS table view that I intend to display the list of teams with. The data appears to be pulled from the API fine, but for some reason the TableView method which creates a cell and returns it doesn't seem to be called. The only time I can get the method to be called is when I actually hard code a value into the array of team names.
Here is my code:
class Main: UIViewController {
var names = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let URL_String = "https://football-api.com/api/?Action=standings&APIKey=[API_KEY_REMOVED]&comp_id=1204"
let url = NSURL(string: URL_String)
let urlRequest = NSURLRequest(URL: url!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
if let teams = json["teams"] as? [[String : AnyObject]] {
for team in teams {
if let name = team["stand_team_name"] as? String {
self.names.append(name)
}
}
}
} catch {
print("error serializing JSON: \(error)")
}
})
task.resume()
}
// Number of Sections In Table
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// Number of Rows in each Section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
// Sets the content of each cell
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
Just wondering if anyone can point me in the right direction here. This code doesn't crash or throw any errors, it just refuses to load a table view. The only reason I can possibly think of is that the array of team names is empty after completing a request to the API. However I've set breakpoints throughout and checked the values of local variables and the desired information is being pulled from the API as intended...
you are in the correct way , just refresh the table using reloadData once you got the new data from API
if let teams = json["teams"] as? [[String : AnyObject]] {
for team in teams {
if let name = team["stand_team_name"] as? String {
self.names.append(name)
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.yourtableViewname.reloadData()
})
}
I am storing the category name from a JSON in an Array using alamofire .
The array has values only when it is called from this Method CategoryNameFunc.
If i call the the array from the tableview or any other method it always returns 0
CODE
var CategoryNameArray : [String] = []
override func viewDidLoad() {
super.viewDidLoad()
Network()
tester()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CategoryNameArray.count // This returns 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : UITableViewCell = self.TableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
println(self.CategoryNameArray[indexPath.row])
cell.textLabel?.text = "Hello"
return cell
}
func Network(){
Alamofire.request(.GET, "http://www.wive.com/index.php/capp/category_list")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let count = json.count
self.CategoryNameFunc(json, Count: count) }
}
func CategoryNameFunc(Json: JSON, Count: Int)
{
for index in 0...Count-1 {
let name = Json[index]["CATEGORY_NAME"].string
CategoryNameArray.append(name!)
}
// This returns 23 (The correct value)
println(self.CategoryNameArray.count)
}
When you called Network() function it creates a new thread (Alamofire start an asynchronous request) and your tester() function is not waiting for your Network() function to finish before you count your CategoryNameArray().But your CategoryNameFunc() function waits for network operation to finish.
I am not sure (didn't use Almofire) but it think, this happens because the method Network, more precisly the Almofire request is fired asynchronously.
So, the methods Network() and tester() are running simultaneously, but because Network() needs to fetch data first, tester() is faster and is executed first.
The proper way to execute tester() and Network() one after another would be:
func CategoryNameFunc(Json: JSON, Count: Int)
{
for index in 0...Count-1 {
let name = Json[index]["CATEGORY_NAME"].string
CategoryNameArray.append(name!)
}
// run tester, AFTER you have the data.
tester()
}