为什么在Core Data container.performBackgroundTask()之后没有触发tableView.reloadData()

时间:2018-04-08 06:02:05

标签: swift uitableview core-data

我正在使用Swift 4构建单个视图iOS 11应用程序,其UITableViewController也被定义为delegate的{​​{1}}。

NSFetchedResultsController

如果我使用class MyTVC: UITableViewController, NSFetchedResultsControllerDeleagate { var container:NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer var frc : NSFetchedResultsController<Student>? override func viewDidLoad() { container?.performBackgroundTask { context in // adds 100 dummy records in background for i in 1...100 { let student = Student(context: context) student.name = "student \(i)" } try? context.save() // this works because count is printed below if let count = try? context.count(for: Student.fetchRequest()) { print("Number of students in core data: \(count)") // prints 100 } } // end of background inserting. // now defining frc: if let context = container?.viewContext { let request:NSFetchRequest<Student> = Student.fetchRequest() request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] frc = NSFetchedResultsController<Student> ( fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil ) try? frc?.performFetch() // this works and I get no errors tableView.reloadData() frc.delegate = self } // end of frc definition } } 添加一行StudentviewContext将触发所需的方法以在frc中显示它。但是,未示出100个虚拟行。事实上,如果我尝试在插入完成后告诉tableview重新加载,我的应用程序开始表现得很奇怪而且变得越来越错误,并且没有做它应该做的事情(即:不删除行,不编辑等)

但如果我重新启动我的应用程序,而不调用虚拟插入,我可以看到从上一次运行中插入的100行。

唯一的问题是我无法从后台线程调用tableView,所以我尝试这样做:

tableView.reloadData()

然后我尝试调用// after printing the count, I did this: DispatchQueue.main.async { [weak self] in self?.tableView.reloadData() // causes UI to behave weirdly } 在正确的线程中重新加载表视图

viewContext.perform

如何告诉我的tableview以线程安全的方式重新加载并显示100个虚拟行?

3 个答案:

答案 0 :(得分:1)

override func viewDidLoad() {
    super.viewDidLoad()

    //Always need your delegate for the UI to be set before calling the UI's delegate functions.
    frc.delegate = self

    //First we can grab any already stored values.
    goFetch()

    //This chunk just saves. I would consider putting it into a separate function such as "goSave()" and then call that from an event handler.
    container?.performBackgroundTask { context in
        //We are in a different queue than the main queue, hence "backgroundTask".
        for i in 1...100 {
            let student = Student(context: context)
            student.name = "student \(i)"
        }
        try? context.save()   // this works because count is printed below
        if let count = try? context.count(for: Student.fetchRequest()) {
            print("Number of students in core data: \(count)")  // prints 100
        }
        //Now that we are done saving its ok to fetch again.

        goFetch()

    }

    //goFetch(); Your other code was running here would start executing before the backgroundTask is done. bad idea.
    //The reason it works if you restart the app because that data you didn't let finish saving is persisted
    //So the second time Even though its saving another 100 in another queue there were still at least 100 records to fetch at time of fetch.

}


func goFetch() {

    if let context = container?.viewContext {
        let request:NSFetchRequest<Student> = Student.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        frc = NSFetchedResultsController<Student> (
            fetchRequest: request,
            managedObjectContext: context,
            sectionNameKeyPath: nil,
            cacheName: nil )

        try? frc?.performFetch()

        //Now that records are both stored and fetched its safe for our delegate to access the data on the main thread.
        //To me it would make sense to do a tableView reload everytime data is fetched so I placed this inside o `goFetch()`
        DispatchQueue.main.async { [weak self] in
            self?.tableView.reloadData()
        }
    }
}

答案 1 :(得分:1)

经过大量关于NSFetchedResultsController和NSPersistentContainer的阅读,最后在SO上找到了重要的信息,我想我有一个有效的例子。

我的代码略有不同,因为我使用了一个项目。无论如何,这就是我所做的:

在我的视图控制器中,我有一个容器的属性

import React, { Component } from 'react'

import ReactHighcharts from 'react-highcharts';

class Graph extends Component {
    constructor() {
        super();
        this.state = {
            config:  {
                chart: {
                    type: 'xrange'
                },
                title: {
                    text: 'Highcharts X-range'
                },
                xAxis: {
                    type: 'datetime'
                },
                yAxis: {
                    title: {
                        text: ''
                    },
                    categories: ['Prototyping', 'Development', 'Testing'],
                    reversed: true
                },
                series: [{
                    name: 'Project 1',
                    // pointPadding: 0,
                    // groupPadding: 0,
                    pointWidth: 20,
                    data: [{
                        x: Date.UTC(2014, 10, 21),
                        x2: Date.UTC(2014, 11, 2),
                        y: 0,
                        partialFill: 0.25
                    }, {
                        x: Date.UTC(2014, 11, 2),
                        x2: Date.UTC(2014, 11, 5),
                        y: 1
                    }, {
                        x: Date.UTC(2014, 11, 8),
                        x2: Date.UTC(2014, 11, 9),
                        y: 2
                    }, {
                        x: Date.UTC(2014, 11, 9),
                        x2: Date.UTC(2014, 11, 19),
                        y: 1
                    }, {
                        x: Date.UTC(2014, 11, 10),
                        x2: Date.UTC(2014, 11, 23),
                        y: 2
                    }],
                    dataLabels: {
                        enabled: true
                    }
                }]

            }
        }
    }
    render() {
        const config = this.state.config;
        return (
            <ReactHighcharts config={config}></ReactHighcharts>
        )
    }
}


export default Graph

在viewDidLoad中,我加载了持久存储并创建了我的100条记录。

private var persistentContainer = NSPersistentContainer(name: coreDataModelName)

下面是createFakeNotes(),其中我使用单独的上下文在后台线程中插入元素,这段代码几乎取自Apple's Core Data programming guide,但为了使UI更新,我需要将automaticMergesChangesFromParent设置为true,我在this SO answer

中找到了

我还首先删除旧笔记以使测试更容易。

persistentContainer.loadPersistentStores { persistentStoreDescription, error in
  if let error = error {
    print("Unable to add Persistent Store [\(error)][\(error.localizedDescription)]")
  } else {
    self.createFakeNotes() // Here 100 elements get created
    DispatchQueue.main.async {
      self.setupView() // other stuff, not relevant
      self.fetchNotes() // fetch using fetch result controller
      self.tableView.reloadData()
    }
  }
}

}

答案 2 :(得分:0)

您应该通过从viewwillappear调用数据来获取数据,然后尝试重新加载您的tableview。

override func viewWillAppear(_ animated: Bool) {
    getdata()
    tableView.reloadData()
}
 func getdata() {
    let context =  (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    do{
    persons = try context.fetch(Person.fetchRequest()) 
    }
    catch {
        print("fetching failed")
    }
}