Swift中如何让CollectionView更快加载JSON中的图片URL
Hey there! I see exactly why your CollectionView is loading images so slowly—let's break down the issues and fix them step by step.
What's Causing the Slowdown?
- Synchronous Image Loading: You're using
NSData(contentsOf:)directly incellForItemAt, which blocks the main thread while downloading images. This means your UI freezes until each image finishes loading, even for just 10 items. - No Caching: Every time a cell is reused (like when scrolling), you re-download the same image again. This wastes bandwidth and adds unnecessary delay.
Step-by-Step Fixes
1. Add an Async Image Loader with Caching
First, create a reusable image loader that handles async downloads and caches images in memory (so we don't re-download the same image over and over):
import UIKit class ImageLoader { static let shared = ImageLoader() private let imageCache = NSCache<NSString, UIImage>() func loadImage(from urlString: String, completion: @escaping (UIImage?) -> Void) { // Validate the URL first guard let url = URL(string: urlString) else { completion(nil) return } // Check memory cache first if let cachedImage = imageCache.object(forKey: urlString as NSString) { completion(cachedImage) return } // Async download from network URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, let image = UIImage(data: data), error == nil else { completion(nil) return } // Cache the downloaded image self?.imageCache.setObject(image, forKey: urlString as NSString) // Update UI on main thread DispatchQueue.main.async { completion(image) } }.resume() } }
2. Update Your CollectionView Cell Setup
Modify cellForItemAt to use the async loader, add a placeholder, and prevent image misalignment from cell reuse:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell let aData = itemsArray[indexPath.row] // Set label text first cell.paymentLabel.text = aData.typeName // Set placeholder to avoid empty cells or old images cell.paymentImage.image = UIImage(named: "placeholder-image") // Replace with your own placeholder // Tag the cell to prevent image misalignment on reuse cell.tag = indexPath.row let imageUrl = aData.iconHome ?? "" // Load image asynchronously ImageLoader.shared.loadImage(from: imageUrl) { [weak cell] image in // Make sure we're updating the correct cell (not a reused one) guard let cell = cell, cell.tag == indexPath.row else { return } cell.paymentImage.image = image ?? UIImage(named: "placeholder-image") } return cell }
3. Clean Up Your Service Call
Your service call has some unsafe force unwraps that could cause crashes. Let's make it more robust:
func homeServiceCall(){ let urlStr = "https://dev.com/webservices" guard let url = URL(string: urlStr) else { print("Invalid URL for service call") return } URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } guard let respData = data else { print("No data received from service") return } guard error == nil else { print("Service error: \(error!)") return } do { let jsonObj = try JSONSerialization.jsonObject(with: respData) as? [String: Any] ?? [:] guard let financerArray = jsonObj["financer"] as? [[String: Any]] else { print("Invalid financer array format in JSON") return } // Use temp arrays to avoid modifying UI-related arrays on background thread var tempItems = [JsonData]() var tempIds = [Int]() for financer in financerArray { let id = financer["id"] as? Int ?? 1 let pic = financer["icon"] as? String ?? "" guard let typeName = financer["tpe"] as? String else { continue } tempIds.append(id) tempItems.append(JsonData(icon: pic, tpe: typeName)) } // Update UI on main thread DispatchQueue.main.async { self.idArray = tempIds self.itemsArray = tempItems self.collectionView.reloadData() } } catch { print("JSON parsing error: \(error)") } }.resume() }
Bonus: Use a Third-Party Library (Even Easier!)
If you want to skip writing custom caching logic, use a battle-tested library like Kingfisher or SDWebImage. For example, with Kingfisher:
- Add Kingfisher to your project via CocoaPods or Swift Package Manager
- Replace your image loading code in
cellForItemAtwith:
import Kingfisher // ... cell.paymentImage.kf.setImage( with: URL(string: aData.iconHome ?? ""), placeholder: UIImage(named: "placeholder-image") )
Kingfisher handles async loading, memory/disk caching, placeholder images, and error handling out of the box—super convenient!
内容的提问来源于stack exchange,提问作者Swift

