我正在使用CoreData在加载新数据然后将其更新到tableView时显示缓存的数据
缓存的数据可以很好地加载,但是问题在于,一旦调用API来加载新数据,tableView就变得无响应,即用户无法在表上滚动或单击任何内容,并且在新数据完全加载后,它将更新tableView,它再次变得敏感
我要实现的是该应用程序立即显示缓存数据,并调用api并在后台加载数据。在加载数据时以及从API加载数据完成时,tableView不应无响应单击刷新按钮或向上滑动以更新数据
// DID LOAD
override func viewDidLoad() {
super.viewDidLoad()
print("did load")
getAvatar()
tableView.separatorStyle = .none
updateTableContents()
self.tableView.delegate = self
self.tableView.dataSource = self
}
显示缓存数据并调用API的函数
func updateTableContents()
{
do {
try self.fetchedhResultController.performFetch()
print("COUNT FETCHED FIRST: \(self.fetchedhResultController.sections?[0].numberOfObjects)")
} catch let error {
print("ERROR: \(error)")
}
print("function called")
let retrievedToken: String? = KeychainWrapper.standard.string(forKey: "acessTokenKey")
let headers = [
"Authorization" : "Bearer "+retrievedToken!,
"Content-Type" : "application/json"
]
let url = "someURL"
Alamofire.request(url, method: .get , headers: headers).responseJSON { response in
switch response.result {
case .success:
let json = response.result.value as! [String:Any]
let data = json["data"] as! [[String : Any]]
self.clearData()
self.saveInCoreDataWith(array: data)
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
for dic in data{
self.news.append(News(dictionary: dic))
print(self.news.count)
}
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
self.tableView.reloadData()
}
case .failure: break
}
}
}
我的桌子查看代码
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = fetchedhResultController.sections?.first?.numberOfObjects {
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell") as! NewsCell
if let fetchedNews = fetchedhResultController.object(at: indexPath) as? NewsObject {
cell.test(object : fetchedNews)
print(self.count)
self.count+=1;
}
以及所有核心数据存储和提取功能
// Creating an Object
private func createNewsEntityFrom(dictionary: [String: Any]) -> NewsObject {
let context = CoreDataStack.sharedInstance.managedObjectContext
let newsEntity = NewsObject(context: context)
newsEntity.newsAuthor = dictionary["author"] as? String ?? "default"
newsEntity.newsTitle = dictionary["title"] as? String ?? "default"
let images = dictionary["image"] as? [String: Any]
newsEntity.newsImageURL = images?["link"] as? String ?? "default"
newsEntity.newsID = dictionary["_id"] as? String ?? "default"
newsEntity.newsPublisher = dictionary["publisher"] as? String ?? "default"
newsEntity.newsPublishorIconURL = dictionary["shortenedLogo"] as? String ?? "default"
newsEntity.liked = dictionary["liked"] as? Bool ?? false
newsEntity.bookmarked = dictionary["bookmarked"] as? Bool ?? false
return newsEntity
}
// Saving Data in Core Data
private func saveInCoreDataWith(array: [[String: Any]]) {
for dict in array {
_ = self.createNewsEntityFrom(dictionary: dict)
}
do {
try CoreDataStack.sharedInstance.persistentContainer.viewContext.save()
} catch let error {
print(error)
}
}
// Fetching Data from Core Data
lazy var fetchedhResultController: NSFetchedResultsController<NSFetchRequestResult> = {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NewsObject")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "newsID", ascending: true)]
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataStack.sharedInstance.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
// Function used to Clear Data from Core Data
private func clearData() {
do {
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NewsObject")
do {
let objects = try context.fetch(fetchRequest) as? [NSManagedObject]
_ = objects.map{$0.map{context.delete($0)}}
CoreDataStack.sharedInstance.saveContext()
} catch let error {
print("ERROR DELETING : \(error)")
}
}
}
我一直在遵循本教程,以了解如何实现CoreData(如果有帮助的话https://medium.com/@jamesrochabrun/parsing-json-response-and-save-it-in-coredata-step-by-step-fb58fc6ce16f
编辑:
尝试在后台线程中调用Alamofire完成处理程序
func updateTableContents()
{
do {
try self.fetchedhResultController.performFetch()
print("COUNT FETCHED FIRST: \(self.fetchedhResultController.sections?[0].numberOfObjects)")
} catch let error {
print("ERROR: \(error)")
}
print("function called")
let retrievedToken: String? = KeychainWrapper.standard.string(forKey: "acessTokenKey")
let headers = [
"Authorization" : "Bearer "+retrievedToken!,
"Content-Type" : "application/json"
]
let url = "https://api.tapin.news/v1/posts/home"
Alamofire.request(url, method: .get , headers: headers).responseJSON { response in
DispatchQueue.global(qos: .background).async {
switch response.result {
case .success:
let json = response.result.value as! [String:Any]
let data = json["data"] as! [[String : Any]]
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
self.clearData()
self.saveInCoreDataWith(array: data)
case .failure: break
}
}
self.loadingIndicator.stopAnimating()
self.tableView.reloadData()
}
}
编辑2:所以我进行了一些测试,并且我认为这不是后台任务的问题,因为我将获取代码从updateTableContents移到了viewDidLoad并从viewDidLoad中删除了该函数。因此,甚至没有执行后台API调用并保存到coreData
用户界面仍然需要几秒钟才能响应,在此期间我看不到任何图像,然后在图像加载后立即响应
override func viewDidLoad() {
super.viewDidLoad()
print("did load")
getAvatar()
tableView.separatorStyle = .none
self.tableView.delegate = self
self.tableView.dataSource = self
do {
try self.fetchedhResultController.performFetch()
print("COUNT FETCHED FIRST: \(self.fetchedhResultController.sections?[0].numberOfObjects)")
} catch let error {
print("ERROR: \(error)")
}
}
// Fetching Data from Core Data
lazy var fetchedhResultController: NSFetchedResultsController<NSFetchRequestResult> = {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NewsObject")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "newsID", ascending: true)]
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataStack.sharedInstance.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
编辑3:这是我的CoreDataStack
import UIKit
import CoreData
class CoreDataStack: NSObject {
static let sharedInstance = CoreDataStack()
private override init() {}
lazy var managedObjectContext : NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyAppName")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func applicationDocumentsDirectory() {
if let url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).last {
print(url.absoluteString)
}
}
}
最佳答案
在Alamofire的完成处理程序中,假定它正在后台线程中运行,然后调用主线程并更新tableView。
现实情况是:Alamofire的完成处理程序已经在主线程中运行。
这导致您的UI阻塞,因为那里有昂贵的方法,例如:self.clearData()
self.saveInCoreDataWith(array: data)
另外,这可能很昂贵:
for dic in data {
self.news.append(News(dictionary: dic))
print(self.news.count)
}
尝试将Alamofire完成处理程序包装在后台线程中:
DispatchQueue.global(qos: .background).async {
<#code#>
}