英文:
Array filled in dispatch queue but when I try and use contents in method its empty
问题
我已经从API中提取了JSON数据并使用JSON解码器进行解码。然后,我尝试在调度队列中用JSON数据填充我的数组数据结构,以便在其他方法中使用,但是当我尝试在其他方法或函数中访问数据时,它显示数组为空。
这是我如何解码JSON数据并尝试在调度队列中填充数组的方式:
if let url = URL(string:"https://parseapi.back4app.com/classes/restaurants"){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("app id", forHTTPHeaderField: "X-Parse-Application-Id")
request.setValue("REST API key", forHTTPHeaderField: "X-Parse-REST-API-Key")
let session = URLSession.shared
session.dataTask(with: request) { [self] (data, response, err) in
guard let jsonData = data else {
return
}
do {
let decoder = JSONDecoder()
let restaurantList = try decoder.decode(restaurants.self, from: jsonData)
self.restaurantStruct = restaurantList
DispatchQueue.main.async {
self.fillArray()
}
}
catch let jsonErr {
print("Error decoding JSON", jsonErr)
}
}
.resume()
}
这是我编写的fillArray
方法:
func fillArray(){
var counter = 0
for anitem in restaurantStruct!.results{
//print("\(counter) : \(anitem)")
restaurantArray.insert(["restaurant" : anitem.restaurant, "name" : anitem.name, "description" : anitem.description , "calories" : anitem.calories , "price" : anitem.price , "ImageUrl" : anitem.ImageUrl , "type" : anitem.type], at: counter)
counter += 1
}
}
在collectionView
的cellForItemAt
方法中,我使用了getRestaurantMenu
方法来获取餐厅菜单:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(collectionView == categoryCollectionView){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! CategoryCollectionViewCell
cell.label.text = categories[indexPath.row]["Category"]
cell.imageView.image = UIImage(named:categories[indexPath.row]["Image"]!)
return cell
}
else{
let menu = getRestaurantMenu("TFC")
print(menu)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell2", for: indexPath) as! CollectionViewCell
return cell
}
}
希望这些信息对你有所帮助。如果你有其他问题,请随时提出。
英文:
I have pulled JSON data from an API and decoded using the JSON decoder.I have then tried to populate my array data structure with the JSON data in the dispatch queue so I can use it in other methods but when I try to access the data in other methods or functions it states the array is empty
This is how I have decoded the JSON data and tried filling the array in the dispatch queue
if let url = URL(string:"https://parseapi.back4app.com/classes/restaurants"){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("app id", forHTTPHeaderField: "X-Parse-Application-Id")
request.setValue("REST API key", forHTTPHeaderField: "X-Parse-REST-API-Key")
let session = URLSession.shared
session.dataTask(with: request) { [self] (data, response, err) in
guard let jsonData = data else {
return
}
do {
let decoder = JSONDecoder()
let restaurantList = try decoder.decode(restaurants.self, from: jsonData)
self.restaurantStruct = restaurantList
DispatchQueue.main.async {
self.fillArray()
}
}
catch let jsonErr {
print("Error decoding JSON", jsonErr)
}
}
.resume()
}
This is the fillArray method I wrote
func fillArray(){
var counter = 0
for anitem in restaurantStruct!.results{
//print("\(counter) : \(anitem)")
restaurantArray.insert(["restaurant" : anitem.restaurant, "name" : anitem.name, "description" : anitem.description , "calories" : anitem.calories , "price" : anitem.price , "ImageUrl" : anitem.ImageUrl , "type" : anitem.type], at: counter)
counter += 1
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(collectionView == categoryCollectionView){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! CategoryCollectionViewCell
cell.label.text = categories[indexPath.row]["Category"]
cell.imageView.image = UIImage(named:categories[indexPath.row]["Image"]!)
return cell
}
else{
let menu = getRestaurantMenu("TFC")
print(menu)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell2", for: indexPath) as! CollectionViewCell
return cell
}
}
答案1
得分: 0
我没有(目前)解决方案,但在评论中讨论后,我认为提供一些可能有助于调试过程的代码可能会有所帮助,而评论的字数限制不够用。
首先,您需要确定您的集合视图是否按您的预期工作。为了确定这一点,我们需要一些本地数据,将这些数据添加到您的ViewController
源文件中。它们可以是全局变量。
// debugRestuarantsJSON 应该与您从 REST 调用中期望的格式完全相同。
fileprivate let debugRestaurantsJSON = "在此粘贴有效的餐馆 JSON 文本"
fileprivate let debugRestaurantsData = debugRestaurantsJSON.data(using: .utf8)
然后,让我们将大部分dataTask
完成处理程序提取到其自己的方法中,以便我们可以用于调试数据:
private func setRestaurantData(from restaurantJSONData: Data?)
{
guard let jsonData = restaurantJSONData else { return }
do {
let decoder = JSONDecoder()
let restaurantList = try decoder.decode(Restaurants.self, from: jsonData)
self.restaurantStruct = restaurantList
self.fillArray()
}
catch let jsonErr {
print("解码 JSON 出错", jsonErr)
}
}
请注意,我从对fillArray()
的调用中删除了DispatchQueue.main.async
,因此如果您更改了fillArray
以进行UI调用,比如我在评论中建议的调用setNeedsDisplay()
,请删除这些调用。
然后,让我们将您的dataTask
调用包装在一个方法中:
private func requestRestaurantData(from urlString: String)
{
if let url = URL(string: urlString)
{
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("应用程序ID", forHTTPHeaderField: "X-Parse-Application-Id")
request.setValue("REST API密钥", forHTTPHeaderField: "X-Parse-REST-API-Key")
let session = URLSession.shared
session.dataTask(with: request)
{ (data, response, err) in
if let err = err {
print("URL 请求失败:\(err.localizedDescription)")
}
else { self.setRestaurantData(from: data) }
}
.resume()
}
}
我添加了一个检查,用于处理网络/HTTP错误的情况,它期望URL作为String
参数。除此之外,它与您的代码相同。
不要直接在您最初调用dataTask
的地方调用requestRestaurantData(from:)
,让我们提供更多的间接性:
enum RestaurantRequestType {
case normal, debugSync, debugAsync
}
func requestRestaurantData(_ requestType: RestaurantRequestType = .normal)
{
switch requestType
{
case .normal:
requestRestaurantData(
from: "https://parseapi.back4app.com/classes/restaurants"
)
case .debugSync:
setRestaurantData(from: debugRestaurantsData)
case .debugAsync:
DispatchQueue.main.async {
self.setRestaurantData(from: debugRestaurantsData)
}
}
}
现在,在您最初调用dataTask
的代码中,将其替换为:
requestRestaurantData(.normal)
这应该会产生与您已经具有的完全相同的行为。只需确保在进行这些更改时没有破坏任何内容。换句话说,您的UICollectionView
仍然不会包含餐厅数据。这是可以预期的,但在继续之前,应该先修复其他一切。
然后,从.normal
更改为.debugSync
。这将在requestRestaurantData(_:)
返回之前同步填充数组。如果唯一的问题是从异步任务设置视图的数据,那么您的视图现在应该显示调试数据。如果不显示,您很可能没有正确设置视图或其数据,必须在继续之前进行修复。
假设您的视图显示调试数据。现在从.debugSync
更改为.debugAsync
。这将以异步任务方式填充数组,因此它的行为将更像dataTask
,但不涉及网络访问。它的延迟也会更短,因为它不需要等待网络响应。它将在主运行循环的下几次迭代中之一执行,如果没有其他任务排队,可能会在下一次迭代中执行。
如果您的视图不是使用.debugAsync
更新的,我怀疑这将是情况,您会知道您没有通知视图、其委托或者因为它是UICollectionView
,所以没有通知其dataSource
,有新数据,因此它需要更新/重新绘制。我目前的想法是,您需要调用reloadData
方法来重新加载您的UICollectionView
:
private func setRestaurantData(from restaurantJSONData: Data?)
{
guard let jsonData = restaurantJSONData else { return }
do {
let decoder = JSONDecoder()
let restaurantList = try decoder.decode(Restaurants.self, from: jsonData)
self.restaurantStruct = restaurantList
self.fillArray()
// 添加以下行 - 适当替换yourUICollectionView
DispatchQueue.main.async { self.yourUICollectionView.reloadData() }
}
catch let jsonErr {
print("解码 JSON 出错", jsonErr)
}
}
如果这不起作用,那么还需要进行更多的调试。请评论让我知道。
最终,当您让它工作后,从.debugAsync
更改为.normal
。假设有网络连接并且服务器正在运行,您的视图应该正常工作。如果不工作,那么问题可能完全不同于我所想的。但我相当确定,如果它与.debugAsync
一起工作,它将与.normal
一起工作。
一旦您让它工作,就可以删除调试代码。但我建议保留非调试提取的方法。
英文:
I don't (yet) have a solution, but after discussion in comments, I thought it would be helpful to provide some code that might help in the debugging process, and comments are too limited for that.
First you need to determine if your collection view is even working as you expect. To determine that we'll need some local data, add these to your ViewController
source file. They can be globals
// debugRestuarantsJSON should be exactly the same format you're
// expecting from your REST call.
fileprivate let debugRestaurantsJSON = "Paste valid restaurants JSON text here"
fileprivate let debugRestaurantsData = debugRestaurantsJSON.data(using: .utf8)
Then let's extract the bulk of your dataTask
completion handler into it's own method so we can use it for the debugging data:
private func setRestaurantData(from restaurantJSONData: Data?)
{
guard let jsonData = restaurantJSONData else { return }
do {
let decoder = JSONDecoder()
let restaurantList = try decoder.decode(restaurants.self, from: jsonData)
self.restaurantStruct = restaurantList
self.fillArray()
}
catch let jsonErr {
print("Error decoding JSON", jsonErr)
}
}
Note that I removed DispatchQueue.main.async
from the call to fillArray()
, so if you altered fillArray
to make UI calls, such as the one I suggested in comments to call setNeedsDisplay()
, remove those.
And let's wrap your dataTask
call inside a method too:
private func requestRestaurantData(from urlString: String)
{
if let url = URL(string: urlString)
{
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("app id", forHTTPHeaderField: "X-Parse-Application-Id")
request.setValue("REST API key", forHTTPHeaderField: "X-Parse-REST-API-Key")
let session = URLSession.shared
session.dataTask(with: request)
{ (data, response, err) in
if let err = err {
print("URL request failed: \(err.localizedDescription)")
}
else { self.setRestaurantData(from: data) }
}
.resume()
}
}
I added a check for the case when err
is not nil
to handle network/HTTP errors, and it expects the URL as a String
parameter. It also uses the setRestuarantData(from:)
method we created earlier. Otherwise it's the same as your code.
Instead of calling requestRestaurantData(from:)
directly where you originally called your dataTask
in your code, let's provide one more level of indirection:
enum RestaurantRequestType {
case normal, debugSync, debugAsync
}
func requestRestaurantData(_ requestType: RestaurantRequestType = .normal)
{
switch requestType
{
case .normal:
requestRestaurantData(
from: "https://parseapi.back4app.com/classes/restaurants"
)
case .debugSync:
setRestaurantData(from: debugRestaurantsData)
case .debugAsync:
DispatchQueue.main.async {
self.setRestaurantData(from: debugRestaurantsData)
}
}
}
Now in your code where you originally called dataTask
, replace it with
requestRestaurantData(.normal)
That should give exactly the same behavior you already have. Just make sure you didn't break anything in making those changes. In other words, your UICollectionView
will still not contain the restaurant data. That's to be expected, but everything else that was working before should still work.
Then change from .normal
to .debugSync
. That will synchronously fill the array before requestRestaurantData(_:)
returns. If the only problem is setting the view's data from an asynchronous task, your view should now display the debug data. If it doesn't, you're fundamentally not setting up the view or its data correctly, and that has to be fixed before proceeding.
Let's assume that your view does show the debug data. Now change from .debugSync
to .debugAsync
. That will fill the array in an asynchronous task, so it will behave more like dataTask
, but without the network access. It will also have a shorter latency, because it won't need to wait for network response. It'll just execute in one of the next few iterations of the main runloop, probably on the very next one if no other tasks are queued up.
If your view isn't updated using .debugAsync
, which I suspect will be the case, you know that you're not informing the view, its delegate, or since it's a UICollectionView
, its dataSource
that there's new data, so it needs to update/redraw. My current thought is that is you'll need to call the reloadData
method for your UICollectionView
:
private func setRestaurantData(from restaurantJSONData: Data?)
{
guard let jsonData = restaurantJSONData else { return }
do {
let decoder = JSONDecoder()
let restaurantList = try decoder.decode(restaurants.self, from: jsonData)
self.restaurantStruct = restaurantList
self.fillArray()
// ADD THE FOLLOWING LINE - replace yourUICollectionView appropriately
DispatchQueue.main.async { self.yourUICollectionView.reloadData() }
}
catch let jsonErr {
print("Error decoding JSON", jsonErr)
}
}
If that doesn't work, then there will be more debugging to do. Comment to let me know.
Eventually when you get that working, change from .debugAsync
to .normal
. Assuming network connectivity and the server is running, your view should work. If not, there is some completely different problem than I'm thinking of. But I'm pretty sure that if it works with .debugAsync
it will work with .normal
.
Once you get it working, you can remove the debugging code. I would leave the non-debugging extracted methods in place though.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论