@OOper I can't edit the question, so I added an answer to show the persistentstack code
Post
Replies
Boosts
Views
Activity
Here the PersistentStack implementation
PersistenStack.h:
@import Foundation;
@import CoreData;
@protocol PersistentStackDelegate<NSObject>
- (void)storeWillChange;
- (void)storeDidChange;
- (void)storeDidImport;
@end
@interface PersistentStack : NSObject
@property (nonatomic,strong,readwrite) NSManagedObjectContext* managedObjectContext;
@property (nonatomic,strong,readwrite) NSManagedObjectContext* privateQueueContext;
@property (assign, nonatomic) id<PersistentStackDelegate> delegate;
- (void)saveContext;
- (NSManagedObjectContext *)managedObjectContext;
- (void)savePrivateContext;
- (NSManagedObjectContext *)privateQueueContext;
- (id)initWithStoreURL:(NSURL *)storeURL modelURL:(NSURL *)modelURL;
+(BOOL)isCloudAvaible;
+(void)display_iCloudAlertIfNeeded;
+(NSString*)checkForiCloudString;
@end
PersistentStack.m:
#import "PersistentStack.h"
import "Home.h"
@interface PersistentStack ()
@property (nonatomic,strong) NSURL* modelURL;
@property (nonatomic,strong) NSURL* storeURL;
@property (strong,nonatomic) NSTimer *iCloudUpdateTimer;
@property (readonly,nonatomic) NSUserDefaults *defaults;
@end
@implementation PersistentStack
-(NSUserDefaults*)defaults{
NSUserDefaults *def = ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)?[[NSUserDefaults alloc] initWithSuiteName:@"group.myapp.com"]:[NSUserDefaults standardUserDefaults];
return def;
}
- (id)initWithStoreURL:(NSURL*)storeURL modelURL:(NSURL*)modelURL
{
self = [super init];
if (self) {
self.storeURL = storeURL;
self.modelURL = modelURL;
[self setupManagedObjectContext];
}
return self;
}
+(BOOL)isCloudAvaible{
NSURL *ubiquityURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
BOOL risp = (ubiquityURL == nil);
if(risp){
NSLog(@"ubiquityurl %@",ubiquityURL);
}
return risp;
}
+(NSString*)checkForiCloudString
{
if ([PersistentStack isCloudAvaible])
{
return NSLocalizedString(@"iCloud is not avalaible", nil);
}
return NSLocalizedString(@"iCloud is avalaible", nil);
}
+(void)display_iCloudAlertIfNeeded{
if ([PersistentStack isCloudAvaible]){
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"iCloud Not Configured" message:@"Open iCloud Settings, and make sure you are logged in." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alertView show];
}
}
- (void)setupManagedObjectContext
{
_managedObjectContext = nil;
_privateQueueContext = nil;
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
_privateQueueContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_privateQueueContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
__weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
NSError* error;
NSMutableDictionary *option = [NSMutableDictionary dictionaryWithDictionary:@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}];
if([self.defaults boolForKey:AGGIORNAMENTO_2015]){
[NSThread sleepForTimeInterval:0.2];
[option addEntriesFromDictionary:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }];
}
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:option error:&error];
if(![self.defaults boolForKey:AGGIORNAMENTO_2015]){
NSArray *messe = [Messa getMesseIn:self.managedObjectContext];
NSLog(@"Messe:%@",messe);
if(messe.count>0){
[AppDelegate saveContext];
[NSThread sleepForTimeInterval:0.5];
NSArray *messepostSave = [Messa getMesseIn:self.managedObjectContext];
NSLog(@"Messe:%@",messepostSave);
}
}
else{
[AppDelegate saveContext];
[NSThread sleepForTimeInterval:0.5];
NSLog(@"DopoCloud");
}
[self.privateQueueContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:option error:&error];
NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
[dc removeObserver:self];
[dc addObserver:self selector:@selector(storesWillChange:) name:NSPersistentStoreCoordinatorStoresWillChangeNotification object:psc];
[dc addObserver:self selector:@selector(storesDidChange:) name:NSPersistentStoreCoordinatorStoresDidChangeNotification object:psc];
[dc addObserver:self selector:@selector(persistentStoreDidImportUbiquitousContentChanges:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:psc];
[dc addObserver:self selector:@selector(contextDidSaveMainQueueContext:) name:NSManagedObjectContextDidSaveNotification object:_managedObjectContext];
[dc addObserver:self selector:@selector(contextDidSavePrivateQueueContext:) name:NSManagedObjectContextDidSaveNotification object:_privateQueueContext];
[dc addObserver:self selector:@selector(setupManagedObjectContext) name:AGGIORNAMENTO2015_TERMINATO object:nil];
if (error) {
NSLog(@"error: %@", error);
}
}
- (NSManagedObjectModel*)managedObjectModel
{
return [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
}
- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note
{
NSLog(@"%s", __PRETTY_FUNCTION__);
NSLog(@"%@", note);
if(!note){
[[Home sharedInstance] alert:@"iCLoud Debug" andMex:[NSString stringWithFormat:@"%@",note] icon:nil okAction:nil withCancel:YES];
return;
}
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
[managedObjectContext performBlockAndWait:^{
[managedObjectContext mergeChangesFromContextDidSaveNotification:note];
NSDictionary *changes = note.userInfo;
NSMutableSet *allChanges = [NSMutableSet new];
[allChanges unionSet:changes[NSInsertedObjectsKey]];
[allChanges unionSet:changes[NSUpdatedObjectsKey]];
[allChanges unionSet:changes[NSDeletedObjectsKey]];
for (NSManagedObjectID *objID in allChanges) {
NSLog(@"inporting:%@",objID);
}
}];
[self createCloudTimer];
if(self.delegate){
[self.delegate storeDidImport];
}
}
#pragma mark -
#pragma mark Utility method
void runOnMain(void (^block)(void))
{
if ([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
};
- (void)storesWillChange:(NSNotification *)note {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if ([managedObjectContext hasChanges]) {
[managedObjectContext save:&error];
}
[managedObjectContext reset];
}];
if(self.delegate){
[self.delegate storeWillChange];
}
[self createCloudTimer];
}
- (void)storesDidChange:(NSNotification *)note {
[self createCloudTimer];
if(self.delegate){
[self.delegate storeDidChange];
}
}
-(void)createCloudTimer{
if (self.iCloudUpdateTimer != nil){
[self.iCloudUpdateTimer invalidate];
}
self.iCloudUpdateTimer = nil;
self.iCloudUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(notifyOfCoreDataUpdates) userInfo:nil repeats:NO];
}
-(void)notifyOfCoreDataUpdates{
//aggiorno notifiche lette
runOnMain(^{
[[NSNotificationCenter defaultCenter] postNotificationName:AGGIORNAMENTO_ICLOUD_NOTIFICA object:nil];
});
}
#pragma mark -
#pragma mark CORE-DATA SAVE
- (void)saveContext{
runOnMain(^{
[self MainsaveContext];
});
}
- (void)MainsaveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if (![managedObjectContext save:&error]) {
NSLog(@"PersistenStack: Errore saving:%@",[NSString stringWithFormat:@"%@, %@\n%@", error, [error localizedDescription],[error userInfo]]);
[[[UIAlertView alloc] initWithTitle:@"Errore saving" message:[NSString stringWithFormat:@"%@, %@\n%@", error, [error localizedDescription],[error userInfo]] delegate:nil cancelButtonTitle:@"abort" otherButtonTitles:nil] show];
}
}
}
- (void)savePrivateContext{
runOnMain(^{
[self MainsavePrivateContext];
});
}
- (void)MainsavePrivateContext
{
NSError *error = nil;
@synchronized(self) {
NSManagedObjectContext *managedObjectContext = self.privateQueueContext;
if (managedObjectContext != nil) {
if (![managedObjectContext save:&error]) {
NSLog(@"AppdelegatePrivate: Errore saving:%@",[NSString stringWithFormat:@"%@, %@\n%@", error, [error localizedDescription],[error userInfo]]);
abort();
}
}
}
}
#pragma mark CORE-DATA NOTIFACTION
- (void)contextDidSavePrivateQueueContext:(NSNotification *)notification
{
@synchronized(self) {
if([self.privateQueueContext hasChanges]){
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
}
}
- (void)contextDidSaveMainQueueContext:(NSNotification *)notification
{
if([self.managedObjectContext hasChanges]){
[self.privateQueueContext performBlock:^{
[self.privateQueueContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
}
@end
@OOPER I've simplified the objective-c code when porting to swift to remove legacy work and testing pieces.
So PersistentStack.swift now appear like this:
class PersistentStack {
var modelURL: URL
var storeURL: URL
var managedObjectContext: NSManagedObjectContext
init(storeURL: URL, modelURL: URL) {
self.storeURL = storeURL
self.modelURL = modelURL
var managedObjectModel: NSManagedObjectModel {
return NSManagedObjectModel(contentsOf: modelURL)!
}
self.managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
self.managedObjectContext.persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
weak var psc = self.managedObjectContext.persistentStoreCoordinator
let option = [
NSMigratePersistentStoresAutomaticallyOption: NSNumber(value: true),
NSInferMappingModelAutomaticallyOption: NSNumber(value: true),
NSPersistentStoreUbiquitousContentNameKey: "iCloudStore"
] as [String: Any]
do {
try self.managedObjectContext.persistentStoreCoordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: option)
self.saveContext()
Thread.sleep(forTimeInterval: 0.5)
print("DopoCloud")
let dc = NotificationCenter.default
dc.removeObserver(self)
dc.addObserver(self, selector: #selector(self.storesWillChange), name: .NSPersistentStoreCoordinatorStoresWillChange, object: psc)
dc.addObserver(self, selector: #selector(self.persistentStoreDidImportUbiquitousContentChanges), name: .NSPersistentStoreDidImportUbiquitousContentChanges, object: psc)
} catch {
print("error: \(error)")
}
}
@objc func persistentStoreDidImportUbiquitousContentChanges(_ note: Notification?) {
print("\(#function)")
if let note = note {
print("\(note)")
}
self.managedObjectContext.performAndWait {
self.managedObjectContext.mergeChanges(fromContextDidSave: note!)
if let changes = note?.userInfo {
for objID in changes {
guard let objID = objID as? NSManagedObjectID else {
continue
}
print("importing:\(objID)")
}
}
}
}
@objc func storesWillChange(_ note: Notification?) {
self.managedObjectContext.performAndWait {
if self.managedObjectContext.hasChanges {
do {
try self.managedObjectContext.save()
} catch {}
}
self.managedObjectContext.reset()
}
}
func saveContext() {
do {
try self.managedObjectContext.save()
} catch {
print("PersistenStack: Errore saving:\("\(error), \(error.localizedDescription)\n\((error as NSError).userInfo)")")
}
}
}
and this is the modified Home.swift
struct Home: View {
private let persistentStack: PersistentStack
private var notes: [Nota]?
init() {
let storeUrl = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("iBreviary.sqlite")
let modelURL = Bundle.main.url(forResource: "iBreviary", withExtension: "momd")
self.persistentStack = PersistentStack(storeURL: storeUrl!, modelURL: modelURL!)
let fetchRequest = NSFetchRequest<Nota>()
fetchRequest.returnsObjectsAsFaults = false
let entity = NSEntityDescription.entity(forEntityName: "Nota", in: self.persistentStack.managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor1 = NSSortDescriptor(key: "timestamp", ascending: false)
let sortDescriptors = [sortDescriptor1]
fetchRequest.sortDescriptors = sortDescriptors
do {
self.notes = try self.persistentStack.managedObjectContext.fetch(fetchRequest)
print(self.notes?.count)
} catch {
fatalError("Failed to fetch notes: \(error)")
}
}
var body: some View {
Text("Hello")
}
}
But count keep returning 0. Any Idea?
Answering my own post.
This problem appears to happen on the simulator only. Using a real device shows no problem at all!
Thank you very much!
Here the .ips crash report, with .txt extension as suggested in your post.
IBreviary-2024-09-27-092222.txt
Here' also the crash report
https://drive.google.com/file/d/1knyD5oMX46gnsxNY3RjFWH5iuXkeRlsg/view?usp=sharing
I can confirm that your workaround is working and the app is not crashing anymore.
I'll check for future resolution of the problem.
Thanks Quinn!
Hello Quinn!
setMetadata(...) is set inside the
@MainActor class AudioPlayerProvider: ObservableObject
along AVPlayer and some @Published properties.
The function itself can be called inside the action of a SwiftUI button with:
Task {
try? await self.audioPlayerProvider.setMetadata(title: ..., artist: ..., artwork: ...)
}
or inside a .onChange event triggered by a metadata ObservableObject class, when needed.
So always on the thread the enclosing view runs.
Thanks again
After you explained it, it makes sense.
Thanks Quinn!
Man, that's an explanation!
Thanks for sharing the insight of the problem, so me and everyone that will hit this post can understand and fix its own code.
Thanks again Quinn