核心数据和Swift:异步获取

在前几期中,我们讨论了批处理更新和批处理删除。 在本教程中,我们将仔细研究如何实现异步提取以及在什么情况下您的应用程序可以从此新API中受益。

1.问题

像批处理更新一样,异步获取已在许多开发人员的心愿单上出现了一段时间。 提取请求可能很复杂,需要很短的时间才能完成。 在这段时间内,获取请求将阻塞正在其上运行的线程,因此,将阻止对执行获取请求的托管对象上下文的访问。 问题很容易理解,但是Apple的解决方案是什么样的。

2.解决方案

苹果对这个问题的答案是异步获取。 异步获取请求在后台运行。 这意味着它在执行时不会阻止其他任务,例如更新主线程上的用户界面。

异步获取还具有其他两个方便的功能,即进度报告和取消。 可以在任何时候取消异步获取请求,例如,当用户确定获取请求花费太长时间才能完成时。 进度报告是一项有用的功能,可以向用户显示获取请求的当前状态。

异步获取是一种灵活的API。 不仅可以取消异步获取请求,还可以在执行异步获取请求时更改托管对象的上下文。 换句话说,当应用程序在后台执行异步获取请求时,用户可以继续使用您的应用程序。

3.它如何运作?

与批处理更新一样,异步获取请求也作为NSPersistentStoreRequest对象( NSAsynchronousFetchRequestNSAsynchronousFetchRequest类的实例)传递到托管对象上下文。

NSAsynchronousFetchRequest实例使用NSFetchRequest对象和完成块初始化。 当异步获取请求完成其获取请求时,将执行完成块。

让我们重新访问本系列前面创建的待办事项应用程序 ,并用异步获取请求替换NSFetchedResultsController类的当前实现。

步骤1:专案设定

GitHub下载或克隆该项目,然后在Xcode 7中打开它。在开始使用NSAsynchronousFetchRequest类之前,我们需要进行一些更改。 由于NSFetchedResultsController类旨在在主线程上运行,因此我们将无法使用NSFetchedResultsController类来管理表视图的数据。

步骤2:替换“提取的结果”控制器

首先,如下所示更新ViewController类。 我们删除fetchedResultsController属性,并创建一个类型为[Item]的新属性items ,用于存储待办事项。 这也意味着ViewController类不再需要符合NSFetchedResultsControllerDelegate协议。

import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    let ReuseIdentifierToDoCell = "ToDoCell"
    
    @IBOutlet weak var tableView: UITableView!
    
    var managedObjectContext: NSManagedObjectContext!
    
    var items: [NSManagedObject] = []

    ...

}

在重构viewDidLoad()方法之前,我首先要更新UITableViewDataSource协议的实现。 看看我所做的更改。

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
}
func configureCell(cell: ToDoCell, atIndexPath indexPath: NSIndexPath) {
    // Fetch Record
    let record = items[indexPath.row]
    
    // Update Cell
    if let name = record.valueForKey("name") as? String {
        cell.nameLabel.text = name
    }
    
    if let done = record.valueForKey("done") as? Bool {
        cell.doneButton.selected = done
    }
    
    cell.didTapButtonHandler = {
        if let done = record.valueForKey("done") as? Bool {
            record.setValue(!done, forKey: "done")
        }
    }
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if (editingStyle == .Delete) {
        // Fetch Record
        let record = items[indexPath.row]
        
        // Delete Record
        managedObjectContext.deleteObject(record)
    }
}

我们还需要在prepareForSegue(_:sender:)方法中更改一行代码,如下所示。

// Fetch Record
let record = items[indexPath.row]

最后但并非最不重要的一点是,删除NSFetchedResultsControllerDelegate协议的实现,因为我们不再需要它。

步骤3:创建异步提取请求

如下所示,我们在视图控制器的viewDidLoad()方法中创建异步获取请求。 让我们花一点时间看看发生了什么。

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Initialize Fetch Request
    let fetchRequest = NSFetchRequest(entityName: "Item")
    
    // Add Sort Descriptors
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
    
    // Initialize Asynchronous Fetch Request
    let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { (asynchronousFetchResult) -> Void in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            self.processAsynchronousFetchResult(asynchronousFetchResult)
        })
    }
    
    do {
        // Execute Asynchronous Fetch Request
        let asynchronousFetchResult = try managedObjectContext.executeRequest(asynchronousFetchRequest)
        
        print(asynchronousFetchResult)
        
    } catch {
        let fetchError = error as NSError
        print("\(fetchError), \(fetchError.userInfo)")
    }
}

我们首先创建并配置NSFetchRequest实例以初始化异步提取请求。 异步获取请求将在后台执行此获取请求。

// Initialize Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Item")

// Add Sort Descriptors
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]

要初始化NSAsynchronousFetchRequest实例,我们调用init(request:completionBlock:) ,传入fetchRequest和一个完成块。

// Initialize Asynchronous Fetch Request
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { (asynchronousFetchResult) -> Void in
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.processAsynchronousFetchResult(asynchronousFetchResult)
    })
}

当异步获取请求完成执行其获取请求时,将调用完成块。 完成块采用NSAsynchronousFetchResult类型的一个参数,该参数包含查询结果以及对原始异步获取请求的引用。

在完成模块中,我们调用processAsynchronousFetchResult(_:) ,并传入NSAsynchronousFetchResult对象。 稍后,我们将介绍这种辅助方法。

执行异步提取请求几乎与我们执行NSBatchUpdateRequest相同。 我们在托管对象上下文上调用executeRequest(_:) ,传入异步获取请求。

do {
    // Execute Asynchronous Fetch Request
    let asynchronousFetchResult = try managedObjectContext.executeRequest(asynchronousFetchRequest)
    
    print(asynchronousFetchResult)
    
} catch {
    let fetchError = error as NSError
    print("\(fetchError), \(fetchError.userInfo)")
}

即使异步获取请求是在后台执行的,也请注意executeRequest(_:)方法会立即返回,从而为我们提供了一个NSAsynchronousFetchResult对象。 异步获取请求完成后,将使用获取请求的结果填充相同的NSAsynchronousFetchResult对象。

请记住,在上一教程中executeRequest(_:)是一种抛出方法。 我们在do-catch语句的catch子句中捕获任何错误,并将它们打印到控制台进行调试。

步骤4:处理异步提取结果

processAsynchronousFetchResult(_:)方法只不过是一个帮助器方法,在该方法中我们处理异步提取请求的结果。 我们使用结果的finalResult属性的内容设置视图控制器的items属性,然后重新加载表视图。

func processAsynchronousFetchResult(asynchronousFetchResult: NSAsynchronousFetchResult) {
    if let result = asynchronousFetchResult.finalResult {
        // Update Items
        items = result as! [NSManagedObject]
        
        // Reload Table View
        tableView.reloadData()
    }
}

步骤5:建立并执行

生成项目并在iOS Simulator中运行应用程序。 如果您的应用程序在尝试执行异步获取请求时崩溃,那么您可能正在使用从iOS 9(和OS X El Capitan)开始不推荐使用的API。

Core Data和Swift:并发中 ,我解释了托管对象上下文可以具有的不同并发类型。 从iOS 9(和OS X El Capitan)开始,不建议使用ConfinementConcurrencyTypeNSManagedObjectContext类的init()方法也是如此,因为它创建并发类型为ConfinementConcurrencyType的实例。

如果您的应用程序崩溃,则很可能使用托管对象上下文和ConfinementConcurrencyType并发类型,该类型不支持异步获取。 幸运的是,解决方案很简单。 使用指定的初始化程序init(concurrencyType:)创建托管对象上下文,并传入MainQueueConcurrencyTypePrivateQueueConcurrencyType作为并发类型。

4.显示进度

NSAsynchronousFetchRequest类增加了对监视提取请求进度的支持,甚至有可能取消异步提取请求,例如,如果用户认为要花费太长时间才能完成。

NSAsynchronousFetchRequest类利用NSProgress类进行进度报告以及取消异步获取请求。 自iOS 7和OS X Mavericks以来可用的NSProgress类是一种监视任务进度的聪明方法,而无需将任务紧密耦合到用户界面。

NSProgress类还支持取消,这是可以取消异步获取请求的方式。 让我们找出实现异步获取请求的进度报告所需要做的事情。

步骤1:添加SVProgressHUD

我们将使用Sam VermetteSVProgressHUD库向用户显示异步获取请求的进度 。 完成此操作的最简单方法是通过CocoaPods 。 这就是项目的Podfile的样子。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

pod 'SVProgressHUD', '~> 1.1'

从命令行运行pod install ,不要忘记打开CocoaPods为您创建的工作区,而不是打开Xcode项目。

步骤2:设定NSProgress

在本文中,我们不会详细探讨NSProgress类,但可以随时在Apple文档中阅读有关它的更多信息。 在执行异步提取请求之前,我们在视图控制器的viewDidLoad()方法中创建一个NSProgress实例。

// Create Progress
let progress = NSProgress(totalUnitCount: 1)

// Become Current
progress.becomeCurrentWithPendingUnitCount(1)

我们将总单位数设置为1可能会让您感到惊讶。 原因很简单。 当Core Data执行异步提取请求时,它不知道它将在持久性存储中找到多少记录。 这也意味着我们将无法向用户显示相对进度-一个百分比。 相反,我们将向用户显示绝对进度-找到的记录数。

您可以通过在执行异步获取请求之前执行获取请求以获取记录数来解决此问题。 我不愿意这样做,因为这也意味着从持久性存储中获取记录需要花费更长的时间才能完成,因为开始时会有额外的获取请求。

步骤3:添加观察者

当我们执行异步提取请求时,我们将立即获得一个NSAsynchronousFetchResult对象。 该对象具有一个progress属性,其类型为NSProgress 。 如果要接收进度更新,则需要观察此progress属性。

// Create Progress
let progress = NSProgress(totalUnitCount: 1)

// Become Current
progress.becomeCurrentWithPendingUnitCount(1)

// Execute Asynchronous Fetch Request
let asynchronousFetchResult = try managedObjectContext.executeRequest(asynchronousFetchRequest) as! NSAsynchronousFetchResult

if let asynchronousFetchProgress = asynchronousFetchResult.progress {
    asynchronousFetchProgress.addObserver(self, forKeyPath: "completedUnitCount", options: NSKeyValueObservingOptions.New, context: nil)
}

// Resign Current
progress.resignCurrent()

请注意,我们在progress对象上调用了resignCurrent ,以平衡更早的becomeCurrentWithPendingUnitCount:调用。 请记住,这两个方法都需要在同一线程上调用。

步骤4:移除观察者

在异步获取请求的完成块中,我们删除观察者并关闭进度HUD。

// Initialize Asynchronous Fetch Request
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { (asynchronousFetchResult) -> Void in
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        // Dismiss Progress HUD
        SVProgressHUD.dismiss()
        
        // Process Asynchronous Fetch Result
        self.processAsynchronousFetchResult(asynchronousFetchResult)
        
        if let asynchronousFetchProgress = asynchronousFetchResult.progress {
            // Remove Observer
            asynchronousFetchProgress.removeObserver(self, forKeyPath: "completedUnitCount")
        }
    })
}

在实现observeValueForKeyPath(_:ofObject:change:context:) ,我们需要在创建异步获取请求之前显示进度HUD。

// Show Progress HUD
SVProgressHUD.showWithStatus("Fetching Data", maskType: .Gradient)

步骤5:进度报告

剩下要做的就是实现observeValueForKeyPath(_:ofObject:change:context:)方法。 我们检查context是否等于ProgressContext ,通过从change字典中提取完成的记录数来创建status对象,并更新进度HUD。 请注意,我们在主线程上更新了用户界面。

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "completedUnitCount" {
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            if let changes = change, number = changes["new"] {
                // Create Status
                let status = "Fetched \(number) Records"
                
                // Show Progress HUD
                SVProgressHUD.setStatus(status)
            }
        })
    }
}

5.虚拟数据

如果我们想正确地测试我们的应用程序,我们需要更多的数据。 尽管我不建议在生产应用程序中使用以下方法,但这是一种用数据填充数据库的快速简便的方法。

打开AppDelegate.swift并更新application(_:didFinishLaunchingWithOptions:)方法,如下所示。 populateDatabase()方法是一种简单的帮助程序方法,其中我们将虚拟数据添加到数据库中。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Populate Database
    populateDatabase()

    ...
    
    return true
}

实现很简单。 因为我们只想插入一次伪数据,所以我们在用户默认数据库中检查键"didPopulateDatabase" 。 如果未设置密钥,我们将插入伪数据。

private func populateDatabase() {
    // Helpers
    let userDefaults = NSUserDefaults.standardUserDefaults()
    guard userDefaults.objectForKey("didPopulateDatabase") == nil else { return }
    
    // Create Entity
    let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: self.managedObjectContext)
    
    for index in 0...1000000 {
        // Initialize Record
        let record = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
        
        // Populate Record
        record.setValue(NSDate(), forKey: "createdAt")
        record.setValue("Item \(index)", forKey: "name")
    }
    
    // Save Changes
    saveManagedObjectContext()
    
    // Update User Defaults
    userDefaults.setBool(true, forKey: "didPopulateDatabase")
}

记录数很重要。 如果您打算在iOS Simulator上运行该应用程序,则可以插入100,000或1,000,000条记录。 这在物理设备上效果不佳,将花费很长时间才能完成。

for循环中,我们创建一个托管对象并向其中填充数据。 请注意,在for循环的每次迭代期间,我们不会保存托管对象上下文的更改。

最后,我们更新用户默认数据库,以确保下次启动应用程序时不会填充该数据库。

大。 在iOS模拟器中运行应用程序以查看结果。 您会注意到,异步获取请求需要一些时间才能开始获取记录并更新进度HUD。

显示异步获取请求的进度

6.重大变化

通过用异步获取请求替换获取的结果控制器类,我们破坏了应用程序的几部分。 例如,点按待办事项的复选标记似乎不再起作用。 在更新数据库时,用户界面不会反映出更改。 该解决方案很容易修复,我将由您自己来实施解决方案。 您现在应该拥有足够的知识来理解问题并找到合适的解决方案。

结论

我确信您同意异步获取非常容易使用。 繁重的工作由Core Data完成,这意味着无需手动将异步获取请求的结果与托管对象上下文合并。 当异步获取请求将结果交给您时,您唯一的工作就是更新用户界面。

翻译自: https://code.tutsplus.com/tutorials/core-data-and-swift-asynchronous-fetching--cms-25123

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
接着分析 (result (type_ident (component id='Bool' bind=Swift.(file).Bool))) (brace_stmt range=[re.swift:1:59 - line:14:1] (pattern_binding_decl range=[re.swift:2:5 - line:2:33] (pattern_named type='[UInt8]' 'b') Original init: (call_expr type='[UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:33] nothrow (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:19] nothrow (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:2:19 range=[re.swift:2:19 - line:2:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single) (argument_list implicit (argument (type_expr type='[UInt8].Type' location=re.swift:2:13 range=[re.swift:2:13 - line:2:19] typerepr='[UInt8]')) )) (argument_list (argument (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8 (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied))) )) Processed init: (call_expr type='[UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:33] nothrow (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:19] nothrow (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:2:19 range=[re.swift:2:19 - line:2:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single) (argument_list implicit (argument (type_expr type='[UInt8].Type' location=re.swift:2:13 range=[re.swift:2:13 - line:2:19] typerepr='[UInt8]')) )) (argument_list (argument (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8 (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied))) ))) (var_decl range=[re.swift:2:9 - line:2:9] "b" type='[UInt8]' interface type='[UInt8]' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)
06-10

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值