Post

Replies

Boosts

Views

Activity

Have you ever encountered NSFetchedResultsController not able to properly "section" based on sectionNameKeyPath, randomly?
This problem has buzzed me quite a while. So far, I still haven't founded a good solution. Currently, I have an entity with a Bool column named pinned. I use it as the sectionNameKeyPath for NSFetchedResultsController. So, my UICollectionView will always have 1 sections (All pinned = true, or All pinned = false), or 2 sections (Some pinned = true, and some pinned = false) When I toggle the pinned value from true to false, or false to true, I expect FRC shall fire a "move" callback. (Due to item has moved to a new section) This happen most of the time. However, sometimes, randomly, instead of firing "move" callback, FRC will fire "update" callback. When such incident happen, I will exam the content of fetchedResultsController.sections. Then, I will notice the entity item stays in wrong section. My FRC looks pretty straightforward. lazy var fetchedResultsController: NSFetchedResultsControllerNSPlainNote = { // Create a fetch request for the Quake entity sorted by time. let fetchRequest = NSFetchRequestNSPlainNote(entityName: "NSPlainNote") fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "pinned", 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: "pinned", cacheName: nil ) controller.delegate = fetchedResultsControllerDelegate // Perform the fetch. do { try controller.performFetch() } catch { fatalError("Unresolved error \(error)") } return controller }() This is how I update the pinned column using background thread. func updatePinned(_ objectID: NSManagedObjectID, _ pinned: Bool) { let coreDataStack = CoreDataStack.INSTANCE let backgroundContext = coreDataStack.backgroundContext backgroundContext.perform { let nsPlainNote = try! backgroundContext.existingObject(with: objectID) as! NSPlainNote nsPlainNote.pinned = pinned RepositoryUtils.saveContextIfPossible(backgroundContext) } } I am not really sure, whether this can caused by my background thread. As, if I replace the backgroundContext with viewContext, I haven't observed the random problem so far. But, even so, I am not confident to conclude using backgroundContext is the culprit to this problem. The setup of my background thread is also pretty straightforward. I cannot see how it can went wrong. class CoreDataStack { public static let INSTANCE = CoreDataStack() private init() { } lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "xxx") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) // TODO: container.viewContext.automaticallyMergesChangesFromParent = true //container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy //container.viewContext.undoManager = nil //container.viewContext.shouldDeleteInaccessibleFaults = true return container }() lazy var backgroundContext: NSManagedObjectContext = { let backgroundContext = persistentContainer.newBackgroundContext() // TODO: //backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy //backgroundContext.undoManager = nil return backgroundContext }() } I was wondering, have anyone of you encounter similar problem when trying to utilize sectionNameKeyPath or FRC? Do you know what? Do you have any workaround/ solution for that? Thank you!
1
0
1.4k
Apr ’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.2k
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
673
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.
1
0
756
Jul ’21
May I know how can I use SF Symbols beta to create custom icon?
I am using "checkmark.circle.fill" I wish to have a blue filled circle with the checkmark being solid black. For the blue filled circle, I can choose the tinted color as blue. But, the default SF Symbols "checkmark.circle.fill", its checkmark is transparent. It looks as follow So, I try to use "SF Symbols beta", to create custom icon. During editing, the thing seems like what I want Blue filled circle Solid black color for checkmark Then, I use "Export Symbol...", using Template Version 2.0 (XCode cannot recognise Template Version 3.0) It seems like the checkmark fall back to transparent color. When I import it to XCode It seems like the "solid black color" on the checkmark is gone. When I use this custom icon in my app, it gave same result as "checkmark.circle.fill" (the checkmark still remain as transparent) Doesn't anyone know how to resolve this issue? Thanks.
Topic: Design SubTopic: General Tags:
1
0
4.1k
Jan ’22
Is it possible to make PHPickerViewController display multiple selection ordering number, instead of just an tick icon?
Currently, if we perform multiple selection on PHPickerViewController, a tick icon is being used to indicate selection. https://i.imgur.com/KCaSF9p.png However, such information doesn't convey information for the selection ordering. Is it every possible, to display the ordering number? This is an example from 3rd party library - https://github.com/mikaoj/BSImagePicker As you can see, the 3rd party library is using 1,2,3... to convey selection ordering information https://i.imgur.com/YoQVS4v.png Can we achieve the same behavior in PHPickerViewController? We prefer to use PHPickerViewController, because it doesn't need to request permission to access photo library. Thanks.
1
0
792
Aug ’21
“unsupported file format 'org.webmproject.webp'” while saving CGImage in webp format
Currently, we are using PHPickerViewController + CGImage for efficient memory usage consideration - https://christianselig.com/2020/09/phpickerviewcontroller-efficiently/ However, we are getting "unsupported file format 'org.webmproject.webp'" error, while trying to save CGImage in webp format. func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) guard !results.isEmpty else { return } for result in results { result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { (url, error) in guard let url = url else { return } let options: [CFString: Any] = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceThumbnailMaxPixelSize: 512 ] guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil) else { return } let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) // // No issue in dealing with UTType.jpeg.identifier and UTType.png.identifier. // destUrl is type URL. // guard let destination = CGImageDestinationCreateWithURL(destUrl as CFURL, UTType.webP.identifier, 1, nil) else { return } CGImageDestinationAddImage(destination, image!, nil) CGImageDestinationFinalize(destination) } } We face no issue in saving CGImage in UTType.jpeg.identifier format and UTType.png.identifier format. May I know how can we save CGImage in webp format without issue? Thank you.
1
1
986
Aug ’21
Why we need to use NSPersistentHistoryTransaction if NSFetchedResultController able to update UI correctly?
I still fail to understand, what is the problem NSPersistentHistoryTransaction is trying to solve, in the CoreDataCloudKitDemo WWDC 2019 "Using Core Data with CloudKit" https://github.com/software123inc/CoreDataCloudKitDemo/blob/master/CoreDataCloudKitDemo/DataProvider/CoreDataStack.swift#L161 I want to see, what problem will occur, if processPersistentHistory is not executed. By making the processPersistentHistory empty, I try to do the following testing. Run 2 simulators simultaneously in the same machine. Add an item to simulator A. Since, there is no way for simulator B to receive push notification, I press the home button for simulator B. In simulator B, I tap on the app icon to launch the app again. In simulator B, I can observe controllerDidChangeContent is being called. My guess is that, because the backed SQLite is seamlessly updated by CloudKit background task, NSFetchedResultController will be notified the SQLite DB change, and subsequently update the UI. Check the "Download CloudKit Changes into Core Data" of https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/syncing_a_core_data_store_with_cloudkit In simulator B, due to controllerDidChangeContent is being triggered correctly, I can observe the UI change perform by NSFetchResultController without issue. Hence, I am not clear, on what problem processPersistentHistory is trying to solve in the demo code. May I know what kind of test case I can perform, to understand the problem solved by processPersistentHistory? Based on "Integrate Store Changes Relevant to the Current View" https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/syncing_a_core_data_store_with_cloudkit Your app receives remote change notifications when the local store updates from CloudKit. However, it’s unnecessary to update your UI in response to every notification, because some changes may not be relevant to the current view. Analyze the persistent history to determine whether the changes are relevant to the current view before consuming them in the user interface. Inspect the details of each transaction, such as the entity name, its updated properties, and the type of change, to decide whether to act. For more information about persistent history tracking, see Consuming Relevant Store Changes. This part is getting confusing. Our NSFetchedResultController is receiving relevant entity change event due to SQLite, and subsequently able to update the UI correct. If that is so, why do we still need persistent history?
1
0
400
Apr ’22
Is restore button still required if we were using StoreKit2 Transaction.currentEntitlements
Since StoreKit2 Transaction.currentEntitlements will able to return us user current owned purchased, during app startup. If that is the case, is it still necessary for developer to provide a restore button? If we still need to provide a restore button, what should the restore button do and what API should it call? Thanks
1
0
1.5k
Apr ’22
AVFAudio - Is it safe to perform file copy immediately after AVAudioRecorder.stop()
We start a voice recording via self.avAudioRecorder = try AVAudioRecorder( url: self.recordingFileUrl, settings: settings ) self.avAudioRecorder.record() At certain point, we will stop the recording via self.avAudioRecorder.stop() I was wondering, is it safe to perform file copy on self.recordingFileUrl immediately, after self.avAudioRecorder.stop()? Is all recording data has been flushed to self.recordingFileUrl and self.recordingFileUrl file is closed properly?
1
0
1.4k
May ’22
What are some reliable mechanism to prevent data duplication in CoreData CloudKit?
Every of our data row, contains an unique uuid column. Previously, before adopting CloudKit, the uuid column has a unique constraint. This enables us to prevent data duplication. Now, we start to integrate CloudKit, into our existing CoreData. Such unique constraint is removed. The following user flow, will cause data duplication. Steps to cause data duplication when using CloudKit Launch the app for the first time. Since there is empty data, a pre-defined data with pre-defined uuid is generated. The pre-defined data is sync to iCloud. The app is uninstalled. The app is re-installed. Launch the app for the first time. Since there is empty data, a pre-defined data with pre-defined uuid is generated. Previous old pre-defined data from step 3, is sync to the device. We are now having 2 pre-defined data with same uuid! :( I was wondering, is there a way for us to prevent such duplication? In step 8, we wish we have a way to execute such logic before written into CoreData Check whether such uuid exists in CoreData. If not, write to CoreData. If not, we will pick the one with latest update date, then overwrite the existing data. I once try to insert the above logic into https://developer.apple.com/documentation/coredata/nsmanagedobject/1506209-willsave . To prevent save, I am using self.managedObjectContext?.rollback(). But it just crash. Do you have any idea, what are some reliable mechanism I can use, to prevent data duplication in CoreData CloudKit? Additional info: Before adopting CloudKit We are using using the following CoreData stack class CoreDataStack { static let INSTANCE = CoreDataStack() private init() { } private(set) lazy var persistentContainer: NSPersistentContainer = { precondition(Thread.isMainThread) let container = NSPersistentContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote) container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // This is a serious fatal error. We will just simply terminate the app, rather than using error_log. fatalError("Unresolved error \(error), \(error.userInfo)") } }) // So that when backgroundContext write to persistent store, container.viewContext will retrieve update from // persistent store. container.viewContext.automaticallyMergesChangesFromParent = true // TODO: Not sure these are required... // //container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy //container.viewContext.undoManager = nil //container.viewContext.shouldDeleteInaccessibleFaults = true return container }() Our CoreData data schema has Unique constraint. Deny deletion rule for relationship. Not having default value for non-null field. After adopting CloudKit class CoreDataStack { static let INSTANCE = CoreDataStack() private init() { } private(set) lazy var persistentContainer: NSPersistentContainer = { precondition(Thread.isMainThread) let container = NSPersistentCloudKitContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote) container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // This is a serious fatal error. We will just simply terminate the app, rather than using error_log. fatalError("Unresolved error \(error), \(error.userInfo)") } }) // So that when backgroundContext write to persistent store, container.viewContext will retrieve update from // persistent store. container.viewContext.automaticallyMergesChangesFromParent = true // TODO: Not sure these are required... // //container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy //container.viewContext.undoManager = nil //container.viewContext.shouldDeleteInaccessibleFaults = true return container }() We change the CoreData data schema to Not having unique constraint. Nullify deletion rule for relationship. Having default value for non-null field. Based on a feedback of a Developer Technical Support engineer from https://developer.apple.com/forums/thread/699634?login=true , hen mentioned we can Detecting Relevant Changes by Consuming Store Persistent History Removing Duplicate Data But, it isn't entirely clear on how it should be implemented, as the github link provided in broken.
1
1
1.3k
Jun ’22
Is there any special handling required in StoreKit2, to handle promo code redemption?
My users will redeem promo code via app store (outside my app) Currently, I am using StoreKit2 to Query Transaction.currentEntitlements during app startup Keep listening to Transaction.updates I was wondering, is that is so, is there any special code required, to handle Promo code redeem from user? I guess by keep listening to Transaction.updates, will able to handle such case? But, I am not able to test that, without rolling out my app to production. Thanks.
1
0
773
Sep ’22
Stability of XCode 14
My XCode 13+ was used at MacBook Pro (13-inch, M1, 2020, 16G RAM) without issue Even since updated XCode to 14 week ago, I experience slowness while typing in XCode, and other editing operation. and, this crash happens few times a day Does anyone experience the same thing as I do? Thanks.
1
0
662
Sep ’22
Is there any static analysis tool which can help us detect memory leak cause by missing [weak self]?
I was wondering, is there any tool, which can help to detect memory leak caused by missing [weak self]. For instance, the following code contains memory leak issue caused by lack of [weak self] class ImagePageViewController: UIPageViewController { lazy var memoryLeak = UIAction( title: "memory_leak", image: nil ) { _ in print(">>>> \(self)") } } Is there any tool which can help us to prevent such issue? I have tried  https://github.com/realm/SwiftLint but it is not able to detect such.
1
0
696
Sep ’22
How to avoid CoreData corruption when developing Share extension to CoreData in AppGroup?
We plan to develop a Share extension. The CoreData is storing its SQLite file in AppGroup folder. The share extension will run in a different process than main app's. Since share extension and main app are running in different process, both will have their own instance of CoreData. However, even there are multiple instances of CoreData in different processes, their underlying are pointing to a single same SQLite file. Under https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1 It mentions When you set up a shared container, the containing app—and each contained app extension that you allow to participate in data sharing—have read and write access to the shared container. To avoid data corruption, you must synchronize data accesses. Use Core Data, SQLite, or Posix locks to help coordinate data access in a shared container. But it isn't clear, what are detailed steps required to How can we synchronize access to CoreData among 2 processes? Who should responsible to consume relevant store change, and update the single token file? (https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes) Should it be main app, share extension or both? Thank you.
1
0
893
Nov ’22