Actualmente, tengo una aplicación de notas, que estoy usando la siguiente estructura CoreData.
extension Note { @nonobjc public class func fetchRequest() -> NSFetchRequest<Note> { return NSFetchRequest<Note>(entityName: "Note") } @NSManaged public var heavy_body: String? @NSManaged public var lite_title: String? @NSManaged public var uuid: UUID? } Al mostrar cientos o miles de notas simultáneamente en una UICollectionView , solo necesito mostrar lite_title . Esto es para minimizar el uso de la memoria. Aquí está el código para lograr tal objetivo.
El siguiente código solo cargará todos los lite_title de Notes en la memoria, pero NO heavy_body .
private lazy var fetchedResultsController: NSFetchedResultsController<Note> = { let fetchRequest: NSFetchRequest<Note> = Note.fetchRequest() // We will NOT fetch "heavy_body" explicitly during app start, because it is a heavy resource. fetchRequest.propertiesToFetch = [ "lite_title" ] fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "lite_title", ascending: false) ] // Create a fetched results controller and set its fetch request, context, and delegate. let controller = NSFetchedResultsController( fetchRequest: fetchRequest, managedObjectContext: CoreDataStack.INSTANCE.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil ) controller.delegate = self return controller }()Durante la edición de 1 nota, esto es lo que termino de lograr
lite_title no activará la operación SQLite DB, porque lite_title ya está en la memoriaheavy_body activará la operación SQLite DB, porque heavy_body aún no se recupera en la memoria debido a la exclusión de fetchRequest.propertiesToFetchheavy_body de la memoria, llame a note.managedObjectContext?.refresh(note, mergeChanges: false) para convertirlo en error. Con suerte, junto con la información de fetchRequest.propertiesToFetch , CoreData es lo suficientemente inteligente como para darse cuenta, solo necesita "fallar" heavy_body , pero dejar lite_title intacto.Escribimos el siguiente código para lograr los objetivos anteriores.
@IBAction func readData(_ sender: Any) { guard let sections = self.fetchedResultsController.sections else { return } guard let note = sections[0].objects?[0] as? Note else { return } if note.isFault { print(">>>> This is a fault.\n") } else { print(">>>> This is NOT a fault.\n") } print(">>>> Read lite_title\n") let lite_title = note.lite_title print(">>>> After accessing lite_title, isFault is \(note.isFault)\n") print(">>>> Read heavy_body\n") let heavy_body = note.heavy_body print(">>>> After accessing heavy_body, isFault is \(note.isFault)\n") // Move the object back to fault. note.managedObjectContext?.refresh(note, mergeChanges: false) print(">>>> After moving object back to fault, isFault is \(note.isFault)\n") }Nuestra primera ejecución parece funcionar como se esperaba. Obtenemos el siguiente registro
>>>> This is a fault. >>>> Read lite_title >>>> After accessing lite_title, isFault is true >>>> Read heavy_body CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZHEAVY_BODY, t0.ZLITE_TITLE, t0.ZUUID FROM ZNOTE t0 WHERE t0.Z_PK = ? CoreData: details: SQLite bind[0] = (int64)1 CoreData: annotation: sql connection fetch time: 0.0003s CoreData: annotation: fetch using NSSQLiteStatement <0x600002151040> on entity 'Note' with sql text 'SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZHEAVY_BODY, t0.ZLITE_TITLE, t0.ZUUID FROM ZNOTE t0 WHERE t0.Z_PK = ? ' returned 1 rows CoreData: annotation: with values: ( "<NSSQLRow: 0x600000c00b40>{Note 1-1-5 heavy_body=\"the body, which is a heavy resource\" lite_title=\"the title\" uuid=F80CE382-0345-49A0-AD34-2BAF317AB0C0 and to-manys=0x0}" ) CoreData: annotation: total fetch execution time: 0.0006s for 1 rows. CoreData: annotation: fault fulfilled from database for : 0xbee59927ca0b4c5b <x-coredata://DBF3953E-6ABA-4542-9F79-89822F537A93/Note/p1> with row values: <NSSQLRow: 0x600000c00b40>{Note 1-1-5 heavy_body="the body, which is a heavy resource" lite_title="the title" uuid=F80CE382-0345-49A0-AD34-2BAF317AB0C0 and to-manys=0x0} >>>> After accessing heavy_body, isFault is false >>>> After moving object back to fault, isFault is trueParece lograr nuestro objetivo
lite_title no activará la operación SQLite DB.heavy_body activará la operación SQLite DB. Sin embargo, no estamos seguros después de note.managedObjectContext?.refresh(note, mergeChanges: false) , ¿se descarta heavy_body de la memoria? Aunque isFault devuelve verdadero, solo podemos confirmarlo ejecutando la misma función por segunda vez
Ejecutamos la misma función por segunda vez. Este es nuestro registro
>>>> This is a fault. >>>> Read lite_title >>>> After accessing lite_title, isFault is false >>>> Read heavy_body >>>> After accessing heavy_body, isFault is false >>>> After moving object back to fault, isFault is true Aunque isFault es verdadero, pero no se observó ninguna operación de SQLite DB, cuando leemos heavy_body . ¡Parece que heavy_body todavía permanece en la memoria caché de CoreData!
Nos gustaría eliminar heavy_body de la memoria y dejar lite_title intacto para este caso.
O, incluso si lite_title se elimina de la memoria, al acceder a note.lite_title y desencadenar la activación de fallas, solo lite_title debe recuperarse en SQLite (como se indica en propertiesToFetch , solo queremos que se lite_title ). heavy_body NO debe buscarse en SQLite a lo largo del costado.
¿Puedo saber cómo puedo lograrlo?
El código de muestra completo para demostrar el problema está en https://github.com/yccheok/faulting-example
Referencia: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/FaultingandUniquing.html (Devolver el objeto a la falla)
Según la sugerencia de @Eugene Dudnyk, pude desactivar el mecanismo de almacenamiento en caché de CoreData usando
container.viewContext.stalenessInterval = 0.1Con dicho código, puedo observar la recuperación de SQLite en el segundo intento.
>>>> This is a fault. >>>> Read lite_title CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZHEAVY_BODY, t0.ZLITE_TITLE, t0.ZUUID FROM ZNOTE t0 WHERE t0.Z_PK = ? CoreData: details: SQLite bind[0] = (int64)1 CoreData: annotation: sql connection fetch time: 0.0002s CoreData: annotation: fetch using NSSQLiteStatement <0x600002b80320> on entity 'Note' with sql text 'SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZHEAVY_BODY, t0.ZLITE_TITLE, t0.ZUUID FROM ZNOTE t0 WHERE t0.Z_PK = ? ' returned 1 rows CoreData: annotation: with values: ( "<NSSQLRow: 0x6000006991a0>{Note 1-1-6 heavy_body=\"the body, which is a heavy resource\" lite_title=\"the title\" uuid=F80CE382-0345-49A0-AD34-2BAF317AB0C0 and to-manys=0x0}" ) CoreData: annotation: total fetch execution time: 0.0008s for 1 rows. CoreData: annotation: fault fulfilled from database for : 0x9aff7ac6eceb6a90 <x-coredata://DBF3953E-6ABA-4542-9F79-89822F537A93/Note/p1> with row values: <NSSQLRow: 0x6000006991a0>{Note 1-1-6 heavy_body="the body, which is a heavy resource" lite_title="the title" uuid=F80CE382-0345-49A0-AD34-2BAF317AB0C0 and to-manys=0x0} >>>> After accessing lite_title, isFault is false >>>> Read heavy_body >>>> After accessing heavy_body, isFault is false >>>> After moving object back to fault, isFault is trueSin embargo, todavía hay una trampa.
Al leer lite_title , esperaba que la obtención de datos de SQLite respetara fetchRequest.propertiesToFetch , solo lite_title .
Sin embargo, parece que los datos de SQLite están obteniendo todo el objeto Note , al obtener heavy_body también.
¿Cómo puedo evitar obtener heavy_body si solo estoy interesado en lite_title ?
Gracias.
CoreData tiene almacenamiento en caché subyacente. ManagedObjectContext tiene una propiedad stalenessInterval para controlar durante cuánto tiempo se debe considerar actualizada la información almacenada en caché. Puedes leer más sobre esto aquí .
El valor predeterminado de la propiedad define la obsolescencia infinita.
Para probar que marca la diferencia, agregue a CoreDataStack.swift:30 esta línea:
container.viewContext.stalenessInterval = 0.1Parece que todo este trabajo adicional se puede evitar si Note almacena el texto del heavy_body en un archivo por separado y solo necesita saber esa filePath del archivo.
Siempre que la aplicación necesite cargar el heavy_body completo (por ejemplo, abrir una pantalla de detalles de la Note ), lea/cargue el contenido del archivo ubicado en Note.filePath .
Tan pronto como termine de lidiar con el heavy_body , aléjese de la pantalla de detalles de la Note y el sistema se encargará de limpiarlo automáticamente.
En caso de que desee mostrar algunos metadatos sobre heavy_body en la misma UICollectionViewCell (por ejemplo, tamaño/longitud), puede almacenar metadatos adicionales en el objeto Note y mantenerlos sincronizados con los datos de heavy_body configurando/actualizando los valores apropiados en willSave() devolución de llamada.
CoreData administra su almacenamiento en caché por razones válidas y luchar contra él para que funcione en este caso podría crear más trabajo de contabilidad del necesario.