12/08/2018, 15:25

Core Data Asynchronous Fetching

Việc fetch dữ liệu không đồng bộ cho phép các developers thực hiiện fetch request mà không chặn Managed Object Context trong suốt quá trình fetch. Là một tính năng bổ sung, việc fetch data không đồng bộ có thể bị cancel bởi người dùng và cung cấp report về tiến độ thông qua NSProgress. Khi bạn thực ...

Việc fetch dữ liệu không đồng bộ cho phép các developers thực hiiện fetch request mà không chặn Managed Object Context trong suốt quá trình fetch. Là một tính năng bổ sung, việc fetch data không đồng bộ có thể bị cancel bởi người dùng và cung cấp report về tiến độ thông qua NSProgress. Khi bạn thực hiện một fetch request chuẩn sử dụng method executeFetchRequest:error:, Việc gọi method này sẽ không return ngay lập tức. Thay vào đó, thread sẽ bị blocked cho đến khi hoàn thành việc lấy dưc liệu (nghĩa là các đối tượng fetch nằm trong Managed Object Context). Việc fetch data không đồng bộ thì được thực hiện theo một cách khác. Developers sẽ thực hiện fetch data không đồng bộ bằng cách sử dụng method executeRequest:error: . Method này ngay lập tức trả về một NSAsynchronousFetchResult. Sau khi hoàn tất việc fetch data, completion block sẽ được thực hiện. Fetch data không đồng bộ được hỗ trợ bởi Managed Object Contexts sử dụng NSPrivateQueueConcurrencyType hoặc NSMainQueueConcurrencyType. Ngoài ta, để Managed Object Context được cập nhật ở cuối của quá trình fetch data không đồng bộ, bạn phải chỉ định nó làm như sau. Đây là cách nó hoạt động. Đầu tiên, tạo ra một NSFetchRequest như bạn làm trong một standard fetch. Bạn có thể sử dụng sort descriptors và predicates để filter dữ liệu của mình. Sau đó, tạo một NSAsynchronousFetchRequest sử dụng standard fetch request. Sau đó, truyền fetch request bất đồng bộ đến Managed Object Context thực hiện method executeRequest:error: trên context đó. Context này lập tức trả về một NSAsynchronousFetchResult. Đồng thời, Context gửi fetch này đến store. Bây giờ, bạn có thể tiếp tục sử dụng Managed Object Context để thực hiện fetches, faults, vv. NSAsynchronousFetchRequest là một subclass mới của NSPersistentStoreRequest. Bạn có thể tạo ra một instance NSAsynchronousFetchRequest khởi tạo nó với một instance của NSFetchRequest và một completion block. NSAsynchronousFetchResult là một subclass mới của NSPersistentStoreResult. Nó cung cấp kết quả hoặc error sau khi hoàn thành. Nó được trả về ngay lập tức bởi Managed Object Context khi bạn gọi executeRequest:error.

Bây giờ ta sẽ tìm hiểu làm thế nào để thực hiện một fetch request bất đồng bộ. Bắt đầu bằng cách tạo một project mới sử dụng Single-View Application template. Đặt tên là AsynchronousFetch. Chọn Swift làm ngôn ngữ và dĩ nhiên là chọn core data. Để ngắn gọn, tôi sẽ đơn giản hoá một số công việc phụ bạn sẽ làm trong một ứng dụng thực tế. Trước tiên, tôi sẽ tạo dữ liệu giả, tạo một file Asynchronous_Fetch.xcdatamodeld và sau đó add một entity mới Person với hai thuộc tính:

  • name: string
  • age: int16 Generate class file cho Person entity. Để tạo ra dữ liệu giả ta add đoạn code sau vào application:didFinishLaunchingWithOptions: trong class appdelegate và chạy nó một lần.
for _ in (0..<900000) {
    var person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: self.managedObjectContext!) as Person
    person.name = "Mary"
    person.age = Float(arc4random() % 100)
}
 
var error : NSError? = nil
if !self.managedObjectContext!.save(&error) {
    NSLog("Unresolved error (error), (error!.userInfo)")
    abort()
}

Sau khi run app một lần, the persistent store đã chứa 900 ngàn records. Bây giờ, ta cần comment các dòng code bên trên để tránh phải thực hiện lại. Trong cùng một method của lớp AppDelegate, hãy thêm các dòng code sau:

let vc = self.window!.rootViewController as ViewController
vc.moc = self.managedObjectContext

Thao tác này sẽ truyền managed object context instance đến viewcontroller. Bạn có thể chỉnh sửa dòng code này bằng cách tạo ra một Managed Object Context. Tìm method managedObjectContext trong class AppDelegate và sửa dòng:

var managedObjectContext = NSManagedObjectContext()

bằng:

var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)

Bây giờ, chúng ta hãy chuyển sang class ViewController. Đầu tiên, chúng ta cần một property kiểu Managed Object Context được tạo trong AppDelegate.

var moc: NSManagedObjectContext?

Tiếp theo, ta override phương thức viewDidAppear: bằng cách thêm phương thức sau vào lớp ViewController:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
}

Sau khi gọi super, chúng ta tạo một fetch request cho thực thể Person:

let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: self.moc!)
fetchRequest.entity = entity

Sau đó, chúng ta có thể tạo ra một yêu cầu tìm kiếm không đồng bộ:

let asyncFetch = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { (result:NSAsynchronousFetchResult!) -> Void in
 
    if result.finalResult.count > 0 {
        // do here what you want with the data  
    }
    NSLog("Done.")
}

Như bạn thấy, chúng ta sử dụng fetch request đã tạo trước đó để tạo ra asynchronous fetch request object. Closure sẽ được thực hiện khi hoàn tất việc nạp không đồng bộ. Vì chúng ta muốn biết được tiến độ của fetch data nên ta sẽ thêm một UIProgressView vào view của view controller. Mở Storyboard và thêm một progress view. Đừng quên thêm autolayout. Tạo một outlet cho progress view:

1
@IBOutlet var progressView: UIProgressView!

Connect progress view và outlet. Progress view sẽ hiển thị tiến độ của việc fetch data. Để làm được như vậy, chúng ta cần tạo một instance cho NSProgress Trước khi tạo constant asynchFetch (xem ở trên), hãy thêm những dòng code sau:

var progress = NSProgress(totalUnitCount: 1)
progress.addObserver(self, forKeyPath: "fractionCompleted", options: NSKeyValueObservingOptions.Initial, context: ProgressObserverContext)

Dòng đầu tiên tạo một instance cho NSProgress. Tổng số đơn vị đếm là 1 vì fetch data là một stream operation nên bạn không thể biết được có bao nhiêu đối tượng bạn sẽ truy xuất được, vì vậy bạn cần phải đặt giá trị là 1. Dòng thứ hai giúp view controller theo dõi progress instance. Thuộc tính fractionCompleted là thuộc tính được theo dõi. ProgressObserverContext đại diện cho context được sử dụng bởi observer. Bạn có thể định nghĩa nó trước khi định nghĩa class ViewController.

let ProgressObserverContext = UnsafeMutablePointer<Void>()

Sau asyncFetch constant, ta thêm các dòng code sau:

progress.becomeCurrentWithPendingUnitCount(1)
self.moc!.performBlock { // 1
    var error : NSError?
    var results = self.moc!.executeRequest(asyncFetch, error: &error) as NSAsynchronousFetchResult
}
progress.resignCurrent()

Lưu ý rằng dòng 1 (method performBlock:) không cần ở đây vì chúng ta đang sử dụng một managed context duy nhất. Ở đây, tôi thêm dòng này, để đảm bảo rằng, nếu bạn sử dụng hai hay nhiều managed contexts trong ứng dụng của mình, bạn sẽ không quên sử dụng nó Cuối cùng, chúng ta cần thực hiện method observeValueForKeyPath:ofObject:change:context:

override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<Void>) {
    if context == ProgressObserverContext {
        NSOperationQueue.mainQueue().addOperationWithBlock() {
            var progress = object as NSProgress
            self.progressView.progress = Float(progress.fractionCompleted)
            NSLog("%f", self.progressView.progress)
        }
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}

Asynchronous fetching là một tính năng rất mạnh. Chủ yếu bởi vì nó cho phép developer tiếp tục sử dụng managed object context trong khi Asynchronous fetch đang thực hiện. Cùng với chức năng batch updates, asynchronous fetching sẽ giúp ta có thêm 1 cách tốt để làm việc với managed object contexts and persistent stores.

0