Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Core Data
Code

Core Data y Swift: recibir asíncronamente

by
Difficulty:AdvancedLength:LongLanguages:
This post is part of a series called Core Data and Swift.
Core Data and Swift: Batch Deletes

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

En las entregas anteriores, discutimos las actualizaciones por lotes y las eliminaciones por lotes. En este tutorial, veremos más de cerca cómo implementar la búsqueda asíncrona y en qué situaciones su aplicación se puede beneficiar de esta nueva API.

1. El problema

Al igual que las actualizaciones por lotes, la búsqueda asincrónica ha estado en la lista de deseos de muchos desarrolladores durante bastante tiempo. Las solicitudes de búsqueda pueden ser complejas, lo que requiere una cantidad de tiempo no trivial para completar. Durante ese tiempo, la solicitud de búsqueda bloquea el hilo en el que se está ejecutando y, como resultado, bloquea el acceso al contexto del objeto gestionado que ejecuta la solicitud de búsqueda. El problema es simple de entender, pero ¿cómo se ve la solución de Apple?

2. La solución

La respuesta de Apple a este problema es la búsqueda asincrónica. Una solicitud de recuperación asíncrona se ejecuta en segundo plano. Esto significa que no bloquea otras tareas mientras se está ejecutando, como la actualización de la interfaz de usuario en el hilo principal.

La búsqueda asincrónica también tiene otras dos características convenientes, informes de progreso y cancelación. Una solicitud de búsqueda asíncrona se puede cancelar en cualquier momento, por ejemplo, cuando el usuario decide que la solicitud de búsqueda tarda demasiado en completarse. El informe de progreso es una adición útil para mostrar al usuario el estado actual de la solicitud de búsqueda.

La recuperación asincrónica es una API flexible. No solo es posible cancelar una solicitud de recuperación asíncrona, sino que también es posible realizar cambios en el contexto del objeto gestionado mientras se ejecuta la solicitud de recuperación asincrónica. En otras palabras, el usuario puede continuar utilizando su aplicación mientras la aplicación ejecuta una solicitud de búsqueda asíncrona en segundo plano.

3. ¿Cómo funciona?

Al igual que las actualizaciones por lotes, las solicitudes de recuperación asíncrona se entregan al contexto del objeto gestionado como un objeto NSPersistentStoreRequest, una instancia de la clase NSAsynchronousFetchRequest para ser precisos.

Se inicializa una instancia NSAsynchronousFetchRequest con un objeto NSFetchRequest y un bloque de finalización. El bloque de finalización se ejecuta cuando la solicitud de captación asíncrona ha completado su solicitud de recibir.

Revisemos la aplicación para tareas pendientes que creamos anteriormente en esta serie y reemplazamos la implementación actual de la clase NSFetchedResultsController con una solicitud de búsqueda asincrónica.

Paso 1: Configuración del proyecto

Descargue o clone el proyecto desde GitHub y ábralo en Xcode 7. Antes de que podamos comenzar a trabajar con la clase NSAsynchronousFetchRequest, debemos hacer algunos cambios. No podremos usar la clase NSFetchedResultsController para administrar los datos de la vista de tabla ya que la clase NSFetchedResultsController fue diseñada para ejecutarse en el hilo principal.

Paso 2: Reemplazar el controlador de resultados obtenidos

Comience por actualizar la clase ViewController como se muestra a continuación. Eliminamos la propiedad fetchedResultsController y creamos una nueva propiedad, items, de tipo [Item] para almacenar los elementos pendientes. Esto también significa que la clase ViewController ya no necesita cumplir con el protocolo NSFetchedResultsControllerDelegate.

Antes de refactorizar el método viewDidLoad(), primero quiero actualizar la implementación del protocolo UITableViewDataSource. Echa un vistazo a los cambios que he hecho.

También necesitamos cambiar una línea de código en el método prepareForSegue(_:sender:) como se muestra a continuación.

Por último, elimine la implementación del protocolo NSFetchedResultsControllerDelegate porque ya no lo necesitamos.

Paso 3: crear la solicitud asincrónica

Como puede ver a continuación, creamos la solicitud de búsqueda asincrónica en el método viewDidLoad() del controlador de vista. Tomemos un momento para ver qué está pasando.

Comenzamos creando y configurando una instancia NSFetchRequest para inicializar la solicitud de búsqueda asincrónica. Es esta solicitud de recuperación que la solicitud asíncrona se ejecutará en segundo plano.

Para inicializar una instancia NSAsynchronousFetchRequest, invocamos init(request:completionBlock:), pasando fetchRequest y un bloque de finalización.

El bloque de finalización se invoca cuando la solicitud de recuperación asíncrona ha completado la ejecución de su solicitud de búsqueda. El bloque de finalización toma un argumento de tipo NSAsynchronousFetchResult, que contiene el resultado de la consulta, así como una referencia a la solicitud de recuperación asincrónica original.

En el bloque de finalización, invocamos processAsynchronousFetchResult(_:), pasando el objeto NSAsynchronousFetchResult. Echaremos un vistazo a este método de ayuda en unos momentos.

La ejecución de la solicitud de recuperación asincrónica es casi idéntica a la forma en que ejecutamos un NSBatchUpdateRequest. Llamamos executeRequest(_:) al contexto del objeto gestionado, pasando la solicitud de búsqueda asincrónica.

Aunque la solicitud de búsqueda asíncrona se ejecuta en segundo plano, tenga en cuenta que el método executeRequest(_:) lo devuelve inmediatamente y nos proporciona un objeto NSAsynchronousFetchResult. Una vez que se complete la solicitud de recuperación asincrónica, ese mismo objeto NSAsynchronousFetchResult se rellena con el resultado de la solicitud de recuperación.

Recuerda del tutorial anterior que executeRequest(_:) es un método de lanzamiento. Capturamos cualquier error en la cláusula catch de la declaración do-catch y los imprimimos en la consola para la depuración.

Paso 4: procesamiento del resultado de la solicitud asíncrona

El método processAsynchronousFetchResult(_:) no es más que un método auxiliar en el que procesamos el resultado de la solicitud de recuperación asíncrona. Establecemos la propiedad items del controlador de vista con el contenido de la propiedad finalResult del resultado y volvemos a cargar la vista de tabla.

Paso 5: compilar y ejecutar

Cree el proyecto y ejecute la aplicación en el simulador de iOS. Si su aplicación falla cuando intenta ejecutar la solicitud asincrónica, entonces puede estar usando una API que está en desuso a partir de iOS 9 (y OS X El Capitan).

En Core Data y Swift: Concurrency, expliqué los diferentes tipos de concurrencia que puede tener un contexto de objeto administrado. A partir de iOS 9 (y OS X El Capitan), ConfinementConcurrencyType está en desuso. Lo mismo es cierto para el método init() de la clase NSManagedObjectContext, porque crea una instancia con un tipo de concurrencia de ConfinementConcurrencyType.

Si su aplicación falla, lo más probable es que utilice un contexto de objeto gestionado con un tipo de concurrencia ConfinementConcurrencyType, que no admite la recuperación asincrónica. Afortunadamente, la solución es simple. Cree un contexto de objeto gestionado utilizando el inicializador designado, init(concurrencyType:), pasando MainQueueConcurrencyType o PrivateQueueConcurrencyType como tipo de simultaneidad.

4. Mostrando progreso

La clase NSAsynchronousFetchRequest agrega soporte para monitorear el progreso de la solicitud de búsqueda e incluso es posible cancelar una solicitud asincrónica, por ejemplo, si el usuario decide que tarda demasiado en completarse.

La clase NSAsynchronousFetchRequest aprovecha la clase NSProgress para informes de progreso, así como para cancelar una solicitud de recuperación asincrónica. La clase NSProgress, disponible desde iOS 7 y OS X Mavericks, es una forma inteligente de supervisar el progreso de una tarea sin la necesidad de acoplarla estrechamente a la interfaz de usuario.

La clase NSProgress también admite la cancelación, que es cómo se puede cancelar una solicitud de recuperación asincrónica. Veamos qué debemos hacer para implementar informes de progreso para la solicitud  asincrónica.

Paso 1: Agregar SVProgressHUD

Le mostraremos al usuario el progreso de la solicitud de búsqueda asincrónica utilizando la biblioteca SVProgressHUD de Sam Vermette. La forma más fácil de lograr esto es a través de CocoaPods. Así es como se ve el archivo Podfile del proyecto.

Ejecute pod install desde la línea de comandos y no olvide abrir el espacio de trabajo que CocoaPods ha creado para usted en lugar del proyecto de Xcode.

Paso 2: Configurando NSProgress

En este artículo, no exploraremos la clase NSProgress con mucho detalle, pero no dudes en leer más sobre esto en la documentación de Apple. Creamos una instancia de NSProgress en el método viewDidLoad() del controlador de vista, antes de ejecutar la solicitud de búsqueda asincrónica.

Puede sorprenderse que establezcamos el recuento total de unidades en 1. La razón es simple. Cuando Core Data ejecuta la solicitud de búsqueda asíncrona, no sabe cuántos registros encontrará en el almacén persistente. Esto también significa que no podremos mostrar el progreso relativo al usuario, un porcentaje. En su lugar, le mostraremos al usuario el progreso absoluto: la cantidad de registros que ha encontrado.

Puede solucionar este problema realizando una solicitud de recuperación para recuperar el número de registros antes de ejecutar la solicitud de recuperación asincrónica. Sin embargo, prefiero no hacer esto, porque esto también significa que recuperar los registros de la tienda persistente tarda más en completarse debido a la solicitud extra al inicio.

Paso 3: Agregar un observador

Cuando ejecutamos la solicitud de búsqueda asíncrona, se nos entrega inmediatamente un objeto NSAsynchronousFetchResult. Este objeto tiene una propiedad progress, que es del tipo NSProgress. Es esta propiedad progress lo que debemos observar si queremos recibir actualizaciones de progreso.

Tenga en cuenta que llamamos a resignCurrent en el objeto progress para equilibrar el anterior becomeCurrentWithPendingUnitCount:. Tenga en cuenta que ambos métodos deben invocarse en el mismo hilo.

Paso 4: eliminar el observador

En el bloque de finalización de la solicitud de recuperación asincrónica, eliminamos el observador y descartamos el HUD de progreso.

Antes de implementar observeValueForKeyPath(_:ofObject:change:context:), debemos mostrar el HUD de progreso antes de crear la solicitud de búsqueda asincrónica.

Paso 5: Informes de progreso

Todo lo que nos queda por hacer es implementar el método observeValueForKeyPath(_:ofObject:change:context:). Comprobamos si context es igual a ProgressContext, creamos un objeto status extrayendo la cantidad de registros completados del diccionario change y actualizamos el HUD de progreso. Tenga en cuenta que actualizamos la interfaz de usuario en el hilo principal.

5. Datos ficticios

Si queremos probar nuestra aplicación correctamente, necesitamos más datos. Si bien no recomiendo usar el siguiente enfoque en una aplicación de producción, es una manera rápida y fácil de llenar la base de datos con datos.

Abra AppDelegate.swift y actualice el método de la application(_:didFinishLaunchingWithOptions:) como se muestra a continuación. El método populateDatabase() es un método de ayuda simple en el que agregamos datos ficticios a la base de datos.

La implementación es directa. Como solo queremos insertar datos ficticios una vez, verificamos la base de datos predeterminada del usuario para la clave "didPopulateDatabase". Si la clave no está configurada, insertamos datos ficticios.

La cantidad de registros es importante. Si planea ejecutar la aplicación en el simulador de iOS, está bien insertar 100,000 o 1,000,000 de registros. Esto no funcionará tan bien en un dispositivo físico y tardará demasiado en completarse.

En el ciclo for, creamos un objeto gestionado y lo llenamos con datos. Tenga en cuenta que no guardamos los cambios del contexto del objeto gestionado durante cada iteración del ciclo for.

Finalmente, actualizamos la base de datos predeterminada del usuario para asegurarnos de que la base de datos no se rellene la próxima vez que se inicie la aplicación.

Estupendo. Ejecute la aplicación en el simulador de iOS para ver el resultado. Notarás que lleva unos minutos que la solicitud de recuperación asíncrona comience a recuperar registros y actualice el HUD de progreso.

Showing The Progress of The Asynchronous Fetch Request

6. Cambios que rompen

Al reemplazar la clase de controlador de resultados obtenidos con una solicitud asincrónica, hemos roto algunas partes de la aplicación. Por ejemplo, tocar la marca de verificación de una tarea pendiente ya no parece funcionar. Mientras se actualiza la base de datos, la interfaz de usuario no refleja el cambio. La solución es bastante fácil de solucionar y dejaré que usted implemente una solución. Ahora debería tener los conocimientos suficientes para comprender el problema y encontrar una solución adecuada.

Conclusión

Estoy seguro de que acepta que la búsqueda asincrónica es sorprendentemente fácil de usar. Heavy Data realiza el trabajo pesado, lo que significa que no es necesario fusionar manualmente los resultados de la solicitud de recuperación asincrónica con el contexto del objeto gestionado. Su único trabajo es actualizar la interfaz de usuario cuando la solicitud de recuperación asíncrona le entregue los resultados.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.