Post

Replies

Boosts

Views

Activity

Is NSAsynchronousFetchRequest suitable for production app?
NSAsynchronousFetchRequest seems like an answer to read large amount of data from CoreData, without blocking main thread. But, it is lacking some key features, provided by NSFetchedResultsController. The key features are :- In NSFetchedResultsControllerDelegate didChange and controllerDidChangeContent, we automatically receive a complete change information of persistence store. This enables us to implement the animation of add/delete/move/modify in collection view. Always listen to DB changes. Whenever there is changes being written into the persistence store, NSFetchedResultsControllerDelegate didChange and controllerDidChangeContent will be triggered automatically. This enables us to ensure our UI is in sync with DB. But, NSAsynchronousFetchRequest doesn't come with NSAsynchronousFetchedResultsController :( I was wondering, if you were using NSAsynchronousFetchRequest, how do you implement Animation changes on collection view? Always listen to DB change? My initial thought for animation changes on collection view, it seems we can utilise UICollectionViewDiffableDataSource. But, I notice it might be highly memory inefficient to do so. We need to keep ALL NSManagedObject in memory, fire all faults during comparison. It looks like we will run out of memory, if there are many rows. May I know, how do you achieve the following features, if you ever apply NSAsynchronousFetchRequest in production app? Animation changes on collection view? Always listen to DB change? Thanks.
1
0
811
Jul ’21
Why an invisible same cell is being updated when update is performing using NSFetchedResultsController and Diffable Data Source?
Introduction I expect when I perform "update" on 1 of the CoreData entity content, corresponding UICollectionViewCell should updated seamlessly. But, the thing doesn't work as expected. After debugging, I notice that instead of updating visible UICollectionViewCell on screen, an invisible offscreen UICollectionViewCell has been created and all update operations are performed on the invisible UICollectionViewCell?! Simple hookup between NSFetchedResultsController and Diffable Data Source The hookup is pretty straightforward extension ViewController: NSFetchedResultsControllerDelegate { func controller(_ fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshotReference: NSDiffableDataSourceSnapshotReference) { guard let dataSource = self.dataSource else { return } let snapshot = snapshotReference as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID> print("dataSource.apply") dataSource.apply(snapshot, animatingDifferences: true) { } } } Simple data structure import Foundation import CoreData extension NSTabInfo { @nonobjc public class func fetchRequest() -> NSFetchRequest<NSTabInfo> { return NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") } @NSManaged public var name: String? @NSManaged public var order: Int64 } extension NSTabInfo : Identifiable { } Simple data source to update cell private func initDataSource() { let dataSource = DataSource( collectionView: collectionView, cellProvider: { [weak self] (collectionView, indexPath, objectID) -> UICollectionViewCell? in guard let self = self else { return nil } guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell else { return nil } guard let nsTabInfo = self.getNSTabInfo(indexPath) else { return nil } cell.label.text = nsTabInfo.name print("Memory for cell \(Unmanaged.passUnretained(cell).toOpaque())") print("Content for cell \(cell.label.text)\n") return cell } ) self.dataSource = dataSource } Updating code doesn't work We perform update on the 1st cell using the following code @IBAction func updateClicked(_ sender: Any) { let backgroundContext = self.backgroundContext backgroundContext.perform { let fetchRequest = NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "order", ascending: true) ] do { let nsTabInfos = try fetchRequest.execute() if !nsTabInfos.isEmpty { // Perform update on the first cell nsTabInfos[0].name = "\(Int(nsTabInfos[0].name!)! + 1)" if backgroundContext.hasChanges { try backgroundContext.save() } } } catch { print("\(error)") } } } Nothing is changed on the screen. However, if we look at the print output. We can see the initial content ("0") of the 1st cell is updated to "1". But, all of these are being done in an invisible same instance cell. dataSource.apply Memory for cell 0x0000000138705cd0 Content for cell Optional("1") Memory for cell 0x0000000138705cd0 Content for cell Optional("1") Memory for cell 0x0000000138705cd0 Content for cell Optional("2") Memory for cell 0x0000000138705cd0 Content for cell Optional("3") Ordering work without issue Changing the ordering of the cell works as expected. The following is the code for charging ordering. @IBAction func moveClicked(_ sender: Any) { let backgroundContext = self.backgroundContext backgroundContext.perform { let fetchRequest = NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "order", ascending: true) ] do { let nsTabInfos = try fetchRequest.execute() for (index, element) in nsTabInfos.reversed().enumerated() { element.order = Int64(index) } if backgroundContext.hasChanges { try backgroundContext.save() } } catch { print("\(error)") } } } Do you have idea why such problem occur? I prefer not to have collectionView.reloadData as workaround, as it will create more issue (like resetting scroll position, cell press state, ...) I posted a complete demo at https://github.com/yccheok/problem-update-frc-diffable Thank you
1
0
1.3k
Jul ’21
Should I use weak to avoid from interferencing with CoreData memory management?
Currently, I have the following core data backed collection view, which enable users to perform add/ delete/ modify/ reordering. It will be much more convenient to achieve what I, if I let UICollectionViewCell, to hold its corresponding NSManagedObject. So that I can each perform add/ delete/ modify/ reordering. I was wondering, should I mark the NSManagedObject as weak, to avoid from interferencing with how CoreData manage its memory? Using weak class TabInfoSettingsCell: UICollectionViewCell { // NSManagedObject. Use weak, as we do not want to inteference with how CoreData manage its memory. private weak var nsTabInfo : NSTabInfo? Use strong Or, such concern is invalid. We can simply use strong class TabInfoSettingsCell: UICollectionViewCell { // NSManagedObject. private var nsTabInfo : NSTabInfo? Use struct Or, such concern is valid and using weak is unsafe (as it can become nil in some edge case?). We can use a struct, which contains everything including objectID, but excluding NSManagedObjectContext. class TabInfoSettingsCell: UICollectionViewCell { // struct. Contains everything including objectID, but excluding NSManagedObjectContext. private var tabInfo : TabInfo? This come with the cost of having to create a struct out from NSManagedObject each time, in cellForItemAt. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { ... tabInfoSettingsCell.update(nsTabInfo.toTabInfo()) ... } May I know, which is the correct design, as far as safe memory management is concerned?
1
0
708
Jun ’21
The following simple function will cause Xcode 12E262 to have "Abort: trap 6"
The following simple function will cause Xcode 12E262 to have "Abort: trap 6" during compilation. import UIKit import CoreData class ViewController: UIViewController {     func xyz() {         let container = NSPersistentContainer(name: "xyz")         let batchUpdateRequest = NSBatchUpdateRequest(entityName: "xyz")         let batchUpdateResult = try! container.viewContext.execute(batchUpdateRequest) as? NSBatchUpdateResult         guard let batchUpdateResult = batchUpdateResult else { return }     }     override func viewDidLoad() {         super.viewDidLoad()         // Do any additional setup after loading the view.     } } We will not observe "Abort: trap 6", if under Build Settings, we are using "Optimize for Speed" in Debug, instead of "No Optimization" We can also avoid "Abort: trap 6", if we change the following code guard let batchUpdateResult = batchUpdateResult else { return } to guard let batchUpdateResult2 = batchUpdateResult else { return } May I know, why is it so? A simpler code example to reproduce problem, without CoreData would be import UIKit class ViewController: UIViewController {     func getAny() throws -> Any? {         return nil     }     func xyz() {         let name = try! getAny() as? UIViewController         guard let name = name else { return }     }     override func viewDidLoad() {         super.viewDidLoad()         // Do any additional setup after loading the view.     } }
2
0
985
Jun ’21
CoreData common practice - Do you usually have a struct based data class, as the bridge between your UI layer, and the CoreData data layer?
I was wondering, when you use CoreData, do you usually create another equivalent struct based data class, to compliment the NSManagedObject? The struct based data class, will act as the bridge, between UI layer, and CoreData data layer. For instance, I have the following CoreData model data class. @objc(NSTabInfo) public class NSTabInfo: NSManagedObject { } extension NSTabInfo { @nonobjc public class func fetchRequest() -> NSFetchRequest<NSTabInfo> { return NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") } @NSManaged public var colorIndex: Int32 @NSManaged public var customColor: Int32 @NSManaged public var name: String? @NSManaged public var order: Int64 @NSManaged public var typeValue: Int32 @NSManaged public var syncedTimestamp: Int64 @NSManaged public var uuid: UUID } extension NSTabInfo : Identifiable { } We need a UI, to represent such model data object. Initially, we have the following UI class class TabInfoCell: UICollectionViewCell { private var tabInfo: NSTabInfo? func update(_ tabInfo: NSTabInfo) { self.tabInfo = tabInfo } } But, we just feel not comfortable with such design. Letting TabInfoCell (UI class) to hold NSTabInfo (CoreData model data class) doesn't feel right, as NSTabInfo contains CoreData's context. I do not feel comfortable, to expose CoreData's context to an UI. Will holding a class reference to NSTabInfo in UI, affect CoreData memory allocation/ deallocation strategy? Using weak reference might solve the issue. But, what should the UI do when the weak reference become nil? With such concern, We have the following design struct TabInfo { enum Kind: Int { case All = 0 case Calendar case Custom case Settings } let kind: Kind var name: String? var colorIndex: Int var customColor: Int var order: Int var syncedTimestamp: Int64 var uuid: UUID } @objc(NSTabInfo) public class NSTabInfo: NSManagedObject { convenience init(context: NSManagedObjectContext, tabInfo: TabInfo) { self.init(context: context) self.colorIndex = Int32(tabInfo.colorIndex) self.customColor = Int32(tabInfo.customColor) self.name = tabInfo.name self.order = Int64(tabInfo.order) self.typeValue = Int32(tabInfo.kind.rawValue) self.syncedTimestamp = tabInfo.syncedTimestamp self.uuid = tabInfo.uuid } func toTabInfo() -> TabInfo { return TabInfo( kind: TabInfo.Kind(rawValue: Int(self.typeValue))!, name: self.name, colorIndex: Int(self.colorIndex), customColor: Int(self.customColor), order: Int(self.order), syncedTimestamp: self.syncedTimestamp, uuid: uuid ) } } @nonobjc public class func fetchRequest() -> NSFetchRequest<NSTabInfo> { return NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") } @NSManaged public var colorIndex: Int32 @NSManaged public var customColor: Int32 @NSManaged public var name: String? @NSManaged public var order: Int64 @NSManaged public var typeValue: Int32 @NSManaged public var syncedTimestamp: Int64 @NSManaged public var uuid: UUID } extension NSTabInfo : Identifiable { } Then, in our UI class, it looks like class TabInfoCell: UICollectionViewCell { private var tabInfo: TabInfo? func update(_ tabInfo: TabInfo) { self.tabInfo = tabInfo } } I was wondering, does such design philosophy make sense? Do you usually have a struct based data class, as the bridge between your UI layer, and the CoreData data layer? Thanks.
0
0
725
Jun ’21
Is NSAsynchronousFetchRequest suitable for production app?
NSAsynchronousFetchRequest seems like an answer to read large amount of data from CoreData, without blocking main thread. But, it is lacking some key features, provided by NSFetchedResultsController. The key features are :- In NSFetchedResultsControllerDelegate didChange and controllerDidChangeContent, we automatically receive a complete change information of persistence store. This enables us to implement the animation of add/delete/move/modify in collection view. Always listen to DB changes. Whenever there is changes being written into the persistence store, NSFetchedResultsControllerDelegate didChange and controllerDidChangeContent will be triggered automatically. This enables us to ensure our UI is in sync with DB. But, NSAsynchronousFetchRequest doesn't come with NSAsynchronousFetchedResultsController :( I was wondering, if you were using NSAsynchronousFetchRequest, how do you implement Animation changes on collection view? Always listen to DB change? My initial thought for animation changes on collection view, it seems we can utilise UICollectionViewDiffableDataSource. But, I notice it might be highly memory inefficient to do so. We need to keep ALL NSManagedObject in memory, fire all faults during comparison. It looks like we will run out of memory, if there are many rows. May I know, how do you achieve the following features, if you ever apply NSAsynchronousFetchRequest in production app? Animation changes on collection view? Always listen to DB change? Thanks.
Replies
1
Boosts
0
Views
811
Activity
Jul ’21
Why an invisible same cell is being updated when update is performing using NSFetchedResultsController and Diffable Data Source?
Introduction I expect when I perform "update" on 1 of the CoreData entity content, corresponding UICollectionViewCell should updated seamlessly. But, the thing doesn't work as expected. After debugging, I notice that instead of updating visible UICollectionViewCell on screen, an invisible offscreen UICollectionViewCell has been created and all update operations are performed on the invisible UICollectionViewCell?! Simple hookup between NSFetchedResultsController and Diffable Data Source The hookup is pretty straightforward extension ViewController: NSFetchedResultsControllerDelegate { func controller(_ fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshotReference: NSDiffableDataSourceSnapshotReference) { guard let dataSource = self.dataSource else { return } let snapshot = snapshotReference as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID> print("dataSource.apply") dataSource.apply(snapshot, animatingDifferences: true) { } } } Simple data structure import Foundation import CoreData extension NSTabInfo { @nonobjc public class func fetchRequest() -> NSFetchRequest<NSTabInfo> { return NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") } @NSManaged public var name: String? @NSManaged public var order: Int64 } extension NSTabInfo : Identifiable { } Simple data source to update cell private func initDataSource() { let dataSource = DataSource( collectionView: collectionView, cellProvider: { [weak self] (collectionView, indexPath, objectID) -> UICollectionViewCell? in guard let self = self else { return nil } guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell else { return nil } guard let nsTabInfo = self.getNSTabInfo(indexPath) else { return nil } cell.label.text = nsTabInfo.name print("Memory for cell \(Unmanaged.passUnretained(cell).toOpaque())") print("Content for cell \(cell.label.text)\n") return cell } ) self.dataSource = dataSource } Updating code doesn't work We perform update on the 1st cell using the following code @IBAction func updateClicked(_ sender: Any) { let backgroundContext = self.backgroundContext backgroundContext.perform { let fetchRequest = NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "order", ascending: true) ] do { let nsTabInfos = try fetchRequest.execute() if !nsTabInfos.isEmpty { // Perform update on the first cell nsTabInfos[0].name = "\(Int(nsTabInfos[0].name!)! + 1)" if backgroundContext.hasChanges { try backgroundContext.save() } } } catch { print("\(error)") } } } Nothing is changed on the screen. However, if we look at the print output. We can see the initial content ("0") of the 1st cell is updated to "1". But, all of these are being done in an invisible same instance cell. dataSource.apply Memory for cell 0x0000000138705cd0 Content for cell Optional("1") Memory for cell 0x0000000138705cd0 Content for cell Optional("1") Memory for cell 0x0000000138705cd0 Content for cell Optional("2") Memory for cell 0x0000000138705cd0 Content for cell Optional("3") Ordering work without issue Changing the ordering of the cell works as expected. The following is the code for charging ordering. @IBAction func moveClicked(_ sender: Any) { let backgroundContext = self.backgroundContext backgroundContext.perform { let fetchRequest = NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "order", ascending: true) ] do { let nsTabInfos = try fetchRequest.execute() for (index, element) in nsTabInfos.reversed().enumerated() { element.order = Int64(index) } if backgroundContext.hasChanges { try backgroundContext.save() } } catch { print("\(error)") } } } Do you have idea why such problem occur? I prefer not to have collectionView.reloadData as workaround, as it will create more issue (like resetting scroll position, cell press state, ...) I posted a complete demo at https://github.com/yccheok/problem-update-frc-diffable Thank you
Replies
1
Boosts
0
Views
1.3k
Activity
Jul ’21
Should I use weak to avoid from interferencing with CoreData memory management?
Currently, I have the following core data backed collection view, which enable users to perform add/ delete/ modify/ reordering. It will be much more convenient to achieve what I, if I let UICollectionViewCell, to hold its corresponding NSManagedObject. So that I can each perform add/ delete/ modify/ reordering. I was wondering, should I mark the NSManagedObject as weak, to avoid from interferencing with how CoreData manage its memory? Using weak class TabInfoSettingsCell: UICollectionViewCell { // NSManagedObject. Use weak, as we do not want to inteference with how CoreData manage its memory. private weak var nsTabInfo : NSTabInfo? Use strong Or, such concern is invalid. We can simply use strong class TabInfoSettingsCell: UICollectionViewCell { // NSManagedObject. private var nsTabInfo : NSTabInfo? Use struct Or, such concern is valid and using weak is unsafe (as it can become nil in some edge case?). We can use a struct, which contains everything including objectID, but excluding NSManagedObjectContext. class TabInfoSettingsCell: UICollectionViewCell { // struct. Contains everything including objectID, but excluding NSManagedObjectContext. private var tabInfo : TabInfo? This come with the cost of having to create a struct out from NSManagedObject each time, in cellForItemAt. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { ... tabInfoSettingsCell.update(nsTabInfo.toTabInfo()) ... } May I know, which is the correct design, as far as safe memory management is concerned?
Replies
1
Boosts
0
Views
708
Activity
Jun ’21
The following simple function will cause Xcode 12E262 to have "Abort: trap 6"
The following simple function will cause Xcode 12E262 to have "Abort: trap 6" during compilation. import UIKit import CoreData class ViewController: UIViewController {     func xyz() {         let container = NSPersistentContainer(name: "xyz")         let batchUpdateRequest = NSBatchUpdateRequest(entityName: "xyz")         let batchUpdateResult = try! container.viewContext.execute(batchUpdateRequest) as? NSBatchUpdateResult         guard let batchUpdateResult = batchUpdateResult else { return }     }     override func viewDidLoad() {         super.viewDidLoad()         // Do any additional setup after loading the view.     } } We will not observe "Abort: trap 6", if under Build Settings, we are using "Optimize for Speed" in Debug, instead of "No Optimization" We can also avoid "Abort: trap 6", if we change the following code guard let batchUpdateResult = batchUpdateResult else { return } to guard let batchUpdateResult2 = batchUpdateResult else { return } May I know, why is it so? A simpler code example to reproduce problem, without CoreData would be import UIKit class ViewController: UIViewController {     func getAny() throws -> Any? {         return nil     }     func xyz() {         let name = try! getAny() as? UIViewController         guard let name = name else { return }     }     override func viewDidLoad() {         super.viewDidLoad()         // Do any additional setup after loading the view.     } }
Replies
2
Boosts
0
Views
985
Activity
Jun ’21
CoreData common practice - Do you usually have a struct based data class, as the bridge between your UI layer, and the CoreData data layer?
I was wondering, when you use CoreData, do you usually create another equivalent struct based data class, to compliment the NSManagedObject? The struct based data class, will act as the bridge, between UI layer, and CoreData data layer. For instance, I have the following CoreData model data class. @objc(NSTabInfo) public class NSTabInfo: NSManagedObject { } extension NSTabInfo { @nonobjc public class func fetchRequest() -> NSFetchRequest<NSTabInfo> { return NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") } @NSManaged public var colorIndex: Int32 @NSManaged public var customColor: Int32 @NSManaged public var name: String? @NSManaged public var order: Int64 @NSManaged public var typeValue: Int32 @NSManaged public var syncedTimestamp: Int64 @NSManaged public var uuid: UUID } extension NSTabInfo : Identifiable { } We need a UI, to represent such model data object. Initially, we have the following UI class class TabInfoCell: UICollectionViewCell { private var tabInfo: NSTabInfo? func update(_ tabInfo: NSTabInfo) { self.tabInfo = tabInfo } } But, we just feel not comfortable with such design. Letting TabInfoCell (UI class) to hold NSTabInfo (CoreData model data class) doesn't feel right, as NSTabInfo contains CoreData's context. I do not feel comfortable, to expose CoreData's context to an UI. Will holding a class reference to NSTabInfo in UI, affect CoreData memory allocation/ deallocation strategy? Using weak reference might solve the issue. But, what should the UI do when the weak reference become nil? With such concern, We have the following design struct TabInfo { enum Kind: Int { case All = 0 case Calendar case Custom case Settings } let kind: Kind var name: String? var colorIndex: Int var customColor: Int var order: Int var syncedTimestamp: Int64 var uuid: UUID } @objc(NSTabInfo) public class NSTabInfo: NSManagedObject { convenience init(context: NSManagedObjectContext, tabInfo: TabInfo) { self.init(context: context) self.colorIndex = Int32(tabInfo.colorIndex) self.customColor = Int32(tabInfo.customColor) self.name = tabInfo.name self.order = Int64(tabInfo.order) self.typeValue = Int32(tabInfo.kind.rawValue) self.syncedTimestamp = tabInfo.syncedTimestamp self.uuid = tabInfo.uuid } func toTabInfo() -> TabInfo { return TabInfo( kind: TabInfo.Kind(rawValue: Int(self.typeValue))!, name: self.name, colorIndex: Int(self.colorIndex), customColor: Int(self.customColor), order: Int(self.order), syncedTimestamp: self.syncedTimestamp, uuid: uuid ) } } @nonobjc public class func fetchRequest() -> NSFetchRequest<NSTabInfo> { return NSFetchRequest<NSTabInfo>(entityName: "NSTabInfo") } @NSManaged public var colorIndex: Int32 @NSManaged public var customColor: Int32 @NSManaged public var name: String? @NSManaged public var order: Int64 @NSManaged public var typeValue: Int32 @NSManaged public var syncedTimestamp: Int64 @NSManaged public var uuid: UUID } extension NSTabInfo : Identifiable { } Then, in our UI class, it looks like class TabInfoCell: UICollectionViewCell { private var tabInfo: TabInfo? func update(_ tabInfo: TabInfo) { self.tabInfo = tabInfo } } I was wondering, does such design philosophy make sense? Do you usually have a struct based data class, as the bridge between your UI layer, and the CoreData data layer? Thanks.
Replies
0
Boosts
0
Views
725
Activity
Jun ’21