my use case is very simple. I need to
create a request NSManagedObject, ✓ works
pass it to e.g. postObject: method from Restkit, ✓ works
receive a response NSManagedObject in the completion block, ✓ works
process it and, ✓ works
delete both the request and the response objects using MR_deleteEntity, ✘ does not work
I'd like to use just MagicalRecord to create/delete/manage the entities.
The issue:
When I call the asynchronous save method from the MagicalRecord toolkit and after I exit the app I can still see the entities in the sqlite db file. After restart of the app new objects are added in the db without deleting a single instance although I explicitly call MR_deleteEntity on the objects. I admit the context management is something I yet have not grasped fully.
I am using the following setup to connect MagicalRecord with Restkit:
// 1. Setup the core data stack with the automigration
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:[GVUserDefaults standardUserDefaults].applicationStoreName];
// 2. Initialize managed object store
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
// 3. create the persistentStoreManagedObjectContext and the mainQueueManagedObjectContext:
[managedObjectStore createManagedObjectContexts];
// 4. set the default and the root saving context:
[NSManagedObjectContext MR_defaultStoreCoordinator:managedObjectStore.mainQueueManagedObjectContext];
[NSManagedObjectContext MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObjectContext];
// 5. create RestKit manager:
self.rkManager = [TSNRKObjectManager managerWithBaseURL:[NSURL URLWithString:[self serverURL]]];
self.rkManager.requestSerializationMIMEType = RKMIMETypeJSON;
self.rkManager.managedObjectStore = managedObjectStore;
Question
I am trying to delete the request and the response objects this way:
[self saveWithBlock:^(NSManagedObjectContext *localContext) { // calls [MagicalRecord saveWithBlock:block completion:completion]
// some additional processing, getting data from the response
[loginResponse MR_deleteEntity];
[loginRequest MR_deleteEntity];
} completion:^(BOOL success, NSError *error) {
// some additional processing
}];
But I always get this message in the log:
-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x110906470) NO CHANGES IN ** UNNAMED ** CONTEXT - NOT SAVING
I am using this page as reference https://gist.github.com/tonyarnold/4694673. The contexts for the creation of the request and the response entity are the same? What context is Restkit using for creation of the entities? Should I create the request entity also within the block in [MagicalRecord saveWithBlock:block completion:completion]? The example from https://github.com/blakewatters/RKMagicalRecord does not include the automigration setup and the asynchronous saving methods.
UPDATE:
Is this an acceptable solution? (I mean clean, graceful deletion):
[self.loginRequest MR_deleteEntity];
[self.loginRequest.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
// log
}];
[self.loginResponse MR_deleteEntity];
[self.loginResponse.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
// log
}];
I have tested this and it works.
UPDATE 2
In the completion block triggered from Restkit's postObject:... call, I can cleanup the entities from the persistent store this way:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[[self.loginRequest MR_inContext:localContext] MR_deleteEntity];
[[self.loginResponse MR_inContext:localContext] MR_deleteEntity];
} completion:^(BOOL success, NSError *error) {
if(success) {
self.loginRequest = nil;
self.loginResponse = nil;
// log, update ui
} else {
// log error
}
}];
You misunderstand the saving semantics for your multiple contexts. When you call saveWithBlock: you need to transfer all of your managed objects to that new local context that is created for you before you operate on them, otherwise the context has no changes and isn't saved. To transfer you need to get the managed object ids and find the existingObjectWithID:error:.
In this case, for 2 object deletion a you are better off deleting the objects directly from the main context (which is the one they belong to) and saving it up to the persistent store.
Related
I am evaluating Mikro-Orm for a future project. There are several questions I either could not find an answer in the docs or did not fully understand them.
Let me describe a minimal complex example (NestJS): I have an order processing system with two entities: Orders and Invoices as well as a counter table for sequential invoice numbers (legal requirement). It's important to mention, that the OrderService create method is not always called by a controller, but also via crobjob/queue system. My questions is about the use case of creating a new order:
class OrderService {
async createNewOrder(orderDto) {
const order = new Order();
order.customer = orderDto.customer;
order.items = orderDto.items;
const invoice = await this.InvoiceService.createInvoice(orderDto.items);
order.invoice = invoice;
await order.persistAndFlush();
return order
}
}
class InvoiceService {
async create(items): Invoice {
const invoice = new Invoice();
invoice.number = await this.InvoiceNumberService.getNextInSequence();
// the next two lines are external apis, if they throw, the whole transaction should roll back
const pdf = await this.PdfCreator.createPdf(invoice);
const upload = await s3Api.uplpad(pdf);
return invoice;
}
}
class InvoiceNumberService {
async getNextInSequence(): number {
return await db.collection("counter").findOneAndUpdate({ type: "INVOICE" }, { $inc: { value: 1 } });
}
}
The whole use case of creating a new order with all subsequent service calls should happen in one Mikro-Orm transaction. So if anything throws in OrderService.createNewOrder() or one one of the subsequently called methods, the whole transaction should be rolled back.
Mikro-Orm does not allow the atomic update-increment shown in InvoiceNumberService. I can fall back to the native mongo driver. But how do I ensure the call to collection.findOneAndUpdate() shares the same transaction as the entities managed by Mikro-Orm?
Mikro-Orm needs a unique request context. In the examples for NestJS, this unique context is created at the controller level. In the example above the service methods are not necessarily called by a controller. So I would need a new context for each call to OrderService.createNewOrder() that has a lifetime scoped to the function call, correct? How can I acheive this?
How can I share the same request context between services? In the example above InvoiceService and InvoiceNumberService would need the same context as OrderService for Mikro-Orm to work properly.
I will start with the bad news, mongodb transactions are not yet supported in MikroORM (athough they will land within weeks probably, already got the PoC implemented). You can subscribe here for updates: https://github.com/mikro-orm/mikro-orm/issues/34
But let me answer the rest as it will then apply:
You can use const collection = (em as EntityManager<MongoDriver>).getConnection().getCollection('counter'); to get the collection from the internal mongo connection instance. You can also use orm.em.getTransactionContext() to get the current trasaction context (currently implemented only in sql drivers, but in future this will probably return the session object in mongo).
Also note that in mongo driver, implicit transactions won't be enabled by default (it will be configurable though), so you will need to use explicit transaction demarcation via em.transactional(...).
The RequestContext helper works automatically. You just register it as a middleware (done automatically in the nestjs orm adapter) and then your request handler (route/endpoint/controller method) is ran inside a domain that shares the context. Thanks to this, all services in the DI can share singleton instances of repositories, but they will automatically pick the right context from the domain.
You basically have this automatic request context, and then you can create new (nested) contexts manually via em.transactional(...).
https://mikro-orm.io/docs/transactions/#approach-2-explicitly
I am trying to perform history tracking in my CoreData+CloudKit project which uses NSPersistentCloudKitContainer. I have been following along with Apple's sample project
I want to perform certain task when the remote store has been updated. For this apple recommends enabling remote notification in the Signing & capabilities's Background Mode section of the app.
I have enabled History Tracking for my project as shown in Apple's sample project.
// turn on persistent history tracking
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// ...
Also I have registered my store to listen for store changes.
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// ...
Observer is also added to listen for NSPersistentStoreRemoteChangeNotification.
However there is no NSPersistentStoreRemoteChangeNotification being fired. To make sure there is no mistake in my implementation, I am have simply put breakpoints in #objc func storeRemoteChange(_ notification: Notification) the Apple's provided sample code but still I can not see any notification being fired and no breakpoints are activated.
I have understood the deduplication of the Tags done in the sample project and also tried testing it but without any success. Is it a bug in the Apple's implementation or am I missing any setup which is required?
My guess is you are observing the container instead of the store coordinator, add your observer like this:
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
Note the last param container.persistentStoreCoordinator
And a warning, this notification comes in on all different threads so you be careful with concurrency. Just put a 5 second sleep in the method and you'll see on app launch 3 different threads call it. This is likely why in the example there is a historyQueue with maxOperationCount 1 to handle it.
Some notifications have NSPersistentHistoryTokenKey in the userInfo not sure why.
Debugging the sample app mentioned by the OP, I observed the following:
As of XCode Version 11.3 (11C29), there are SDK constants both for the option key (NSPersistentStoreRemoteChangeNotificationPostOptionKey) and for the notification name (.NSPersistentStoreRemoteChange), and these are reflected in the latest download of the sample code.
The sample app registers for the remote change notifications on the wrong object, so it never receives any. Changing the sender as per the accepted answer fixes this.
The app UI always updates to reflect changes received from the cloud, but those updates are prompted not by remote change notifications but by the app's NSFetchedResultsController delegate using the controllerDidChangeContent callback to refresh the UI.
The standard NSPersistentCloudKitContainer used by the sample app is doing automatic imports into the local persistent store of all the cloud-sent updates and, because the persistentStore is set up for history tracking and the viewContext is set up to auto-update to the latest generation of data, each import triggers a UI update.
Based on these observations, I wrote a small app from scratch based on the XCode template you get by specifying use of CoreData, CloudKit, and SwiftUI. I set up its persistent container and view context the same way they are set up in the sample app, and used SwiftUI's #FetchRequest wrapper to obtain the data in the master view display. Sure enough, I saw the exact same remote import behavior without using any remote change notifications, and the UI updated after each import.
I then confirmed that, as per the accepted answer, if I registered for remote change notifications correctly, they would be received. They seem to be sent after each receive and import operation in the NSPersistentCloudKit completes. Observing them is not needed to get notifications of the local data changes initiated by those imports.
I was able to reliably echo Core Data changes via iCloud between two devices in my project. But I reached a point where I needed access to the change history. Apple has nicely described the steps to set it up in Consuming Relevant Store Changes
I followed along and happily copy and pasted the relevant code into my app. But the NSPersistentStoreRemoteChange notification was not coming through. As in comedy, timing is everything. Per the documentation for persistentStoreDescriptions I
If you will be configuring custom persistent store descriptions, you
must set this property before calling
loadPersistentStores(completionHandler:)
I was configuring persistentStoreDescriptions inside of loadPersistentStores(completionHandler:) So the painfully obvious way to do it is setup the following code in the AppDelegate.
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentCloudKitContainer(name: "yourProjectNameGoesHere")
// turn on persistent history tracking
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// this will make background updates from iCloud available to the context.
container.viewContext.automaticallyMergesChangesFromParent = true
// call this LAST, after the persistentStoreDescriptions configuration.
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
Catch the notification from your view controller or model.
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(fetchChanges),
name: .NSPersistentStoreRemoteChange,
object: pc.persistentStoreCoordinator)
}
#objc func fetchChanges(note: Notification) {
print("Just received a NSPersistentStoreRemoteChange notification")
}
I don't know whether it's a bug. Simply downloading and running the Apple's Sample Project but the NSPersistentStoreRemoteChangeNotification is never fired.
I added one more observer for the same NSPersistentStoreRemoteChangeNotification in my AppDelegate and it is firing.
I added notification observer in AppDelegate and then simply call the StoreRemoteChange(_:) of the CoreDataStack. Also, Tag deduplication logic works properly.
Here is the code which I added in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// The view controller hierarchy is defined in the main storyboard.
guard let splitViewController = window?.rootViewController as? UISplitViewController,
let navController = splitViewController.viewControllers[splitViewController.viewControllers.count - 1] as? UINavigationController,
let topViewController = navController.topViewController else {
return false
}
// Configure the splitViewController.
topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate = self
splitViewController.preferredDisplayMode = .allVisible
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: nil)
return true
}
#objc
func storeRemoteChange(_ notification: Notification) {
coreDataStack.storeRemoteChange(notification)
}
SwiftUI
Here's a way to be notified of CloudKit remote changes in a SwiftUI view, and, say, update the contents of a List that would depend on a #FetchRequest--not shown in the code for simplicity:
struct MyView: View {
#State var refresh = UUID()
var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)
var body: some View {
List {
// ...
}
.id(refresh)
.onReceive(self.didRemoteChange) { _ in
self.refresh = UUID()
}
}
}
Note: .receive(on: RunLoop.main) is necessary in order to avoid modifying the UI from a background thread, as the remote event could (and will) otherwise fire from a background thread. Alternatively, .receive(on: DispatchQueue.main) can also be used.
For that to work, the NSPersistentCloudKitContainer needs to be set up to fire events when remote changes occur:
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "YourApp")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
//
// Generate notifications upon remote changes
//
container.persistentStoreDescriptions.forEach {
$0.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
}
I have an app already in the App Store that uses core data to save data.
Now, when iOS 8 is about to come out I wanna add a widget to it, thus I must use App Groups to share data between the binaries.
One problem though - I need to change the store location to support App Groups to all the existing users.
I wrote the following code, trying to move the store to the new path:
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *oldStoreURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
oldStoreURL = [oldStoreURL URLByAppendingPathComponent:#"Schooler.sqlite"];
NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.schooler.mycontainer"];
storeURL = [storeURL URLByAppendingPathComponent:#"Schooler.sqlite"];
if([[NSFileManager defaultManager] fileExistsAtPath:oldStoreURL.path] == YES && [[NSFileManager defaultManager] fileExistsAtPath:storeURL.path] == NO)
{
// Prior today extension - Need to move to new directory
NSError *error = nil;
if([[NSFileManager defaultManager] moveItemAtURL:oldStoreURL toURL:storeURL error:&error] == YES)
NSLog(#"Migrated successfully to new database location.");
else
NSLog(#"error: %#",error);
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
The output is always "Migrated successfully to new database location.", although all the data that was saved on the app before has been deleted, As if it created a new database instead of just moving it.
What causes the problem? How should I fix it?
Thank you.
A Core Data NSSQLiteStoreType store created with the default options is actually several files, as described in Technical Q&A 1809: New default journaling mode for Core Data SQLite stores in iOS 7 and OS X Mavericks. This is important to remember when attempting to move a store outside of a migration process, and is the source of your issue - you are moving one file when you need to be moving all of them. Moving the files individually outside of Core Data and without the benefits of a file coordinator is not recommended, however. It's much better to use a migration instead.
A migration will take the data from the source store and migrate it to your new store location, essentially replicating the old data at the new location. The old data will still exist on the filesystem. In your application, you should perform the migration as you are now, but do not attempt to move the old data to the new location yourself - that is where things are going wrong.
Instead of moving files around yourself, you can rely on a migration to move the data for you. First, add a store to the persistent store coordinator with the URL of the source data. Then you will perform a migration to move that data to the new URL
NSPersistentStore *sourceStore = nil;
NSPersistentStore *destinationStore = nil;
NSDictionary *storeOptions = #{ NSSQLitePragmasOption : #{ #"journal_mode" :
#"WAL" } };
// Add the source store
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldStoreURL options:storeOptions error:&error]){
// Handle the error
} else {
sourceStore = [coordinator persistentStoreForURL:oldStoreURL];
if (sourceStore != nil){
// Perform the migration
destinationStore = [coordinator migratePersistentStore:sourceStore toURL:storeURL options:storeOptions withType:NSSQLiteStoreType error:&error];
if (destinationStore == nil){
// Handle the migration error
} else {
// You can now remove the old data at oldStoreURL
// Note that you should do this using the NSFileCoordinator/NSFilePresenter APIs, and you should remove the other files
// described in QA1809 as well.
}
}
}
Once the migration has completed you can delete the old files. The example here explicitly specifies the SQLite journal options, this is to ensure that if the default options are changed in the future the code will still work. If you are using different options, you should use those instead.
In case having a version in Swift would be helpful:
let oldPersistentStoreURL: URL = ...
let sharedPersistentStoreURL: URL = ...
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true] // + any database-specific options
if FileManager.default.fileExists(atPath: oldPersistentStoreURL.path) {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: oldPersistentStoreURL, options: options)
if let sourceStore = coordinator.persistentStore(for: oldPersistentStoreURL) {
let _ = try coordinator.migratePersistentStore(sourceStore, to: sharedPersistentStoreURL, options: options, withType: NSSQLiteStoreType)
// If migration was successful then delete the old files
}
} catch {
error.logErrors()
}
}
I am looking to integrate in my new app the option to sync core data in iCloud and so share the information on users devices. I looked around on the web but haven't found a good example or tutorial on how to do this with iOS7.
The last that I have done is to analyze the Apple receipt demo app and included in my app. It seams to work, at least at first view. Adding a record on one device and after a short while, the other device show the data - so far I was happy.
BUT, after restoring the app, the information was gone, on both devices. So i looked into the app (iExplorer) and have found the local Core Data and all my data is there. The next that I have observed is that the debugger shows this: (XXX) are of course not the real values :-)
2014-07-09 19:40:12.830 XXX[199:3507] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity: mobile~XXXXX:XXX
Using local storage: 1
2014-07-09 19:40:12.837 XXX[199:60b] asynchronously added persistent store!
2014-07-09 19:40:13.478 XXX[199:1803] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity: mobile~XXXXX:XXX
Using local storage: 0
What means first it seams like to use the local storage but than change to local storage 0.
this is the code used from Apple's demo app:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
// assign the PSC to our app delegate ivar before adding the persistent store in the background
// this leverages a behavior in Core Data where you can create NSManagedObjectContext and fetch requests
// even if the PSC has no stores. Fetch requests return empty arrays until the persistent store is added
// so it's possible to bring up the UI and then fill in the results later
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
// prep the store path and bundle stuff here since NSBundle isn't totally thread safe
NSPersistentStoreCoordinator* psc = persistentStoreCoordinator;
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"XXX.sqlite"];
// do this asynchronously since if this is the first time this particular device is syncing with preexisting
// iCloud content it may take a long long time to download
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
// this needs to match the entitlements and provisioning profile
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:#"XXXXX"];
cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
// The API to turn on Core Data iCloud support here.
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:#"XXX", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,nil];
NSError *error = nil;
[psc lock];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[psc unlock];
// tell the UI on the main thread we finally added the store and then
// post a custom notification to make your views do whatever they need to such as tell their
// NSFetchedResultsController to -performFetch again now there is a real store
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"asynchronously added persistent store!");
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefetchAllDatabaseData" object:self userInfo:nil];
});
});
return persistentStoreCoordinator;
}
Could anyone help with tutorial or solution?
Try these sample apps for iOS and OSX.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
I have an enterprise application that I want to keep running, so it can call a webservice and inform the user when there is something they need to do.
So, it now runs in the background, it makes the calls, gets results, but informing the user is my problem.
When I fire off a UILocalNotification the alert doesn't call my UIAlertDelegate, and I don't see how to set that alert to do that.
Here is my code for how I am doing the notification, and this actually brings up an alert, but I need it to then bring up a View so they can see the table, but the View it opens isn't one of the two my application uses.
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif) {
localNotif.alertBody = [NSString stringWithFormat:NSLocalizedString(#"%# has a message for you.", nil), #"FYR"];
localNotif.alertAction = NSLocalizedString(#"Read Msg", nil);
localNotif.soundName = nil;
localNotif.applicationIconBadgeNumber = -1;
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:#"Your Background Task works", ItemListKey, #"Message from FYR", MessageTitleKey, nil];
localNotif.userInfo = infoDict;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotif];
[localNotif release];
}
Also, so I tried to have an alert come up that I can set the delegate, so in my controller, where I call the above code I am also calling this:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ShowAlert"
object:nil
userInfo:mydata];
This notification is then picked up and eventually this function is called, and this does work, but the alert isn't visible to the user, I expect because it is in the background.
- (void) _showAlert:(NSString*)pushmessage withTitle:(NSString*)title {
NSLog(#"%#", #"Attemping to show alert");
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:title
message:pushmessage
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes",nil];
[alertView show];
if (alertView) {
[alertView release];
}
}
In the main delegate, I have this defined, and the appropriate functions:
#interface FYRViewAppDelegate : NSObject <UIApplicationDelegate, UIAlertViewDelegate> {
my thought being that if I can get the alert that pops up to call this delegate then it can execute this code:
- (void)alertView:(UIAlertView *)alertview clickedButtonAtIndex:(NSInteger)buttonIndex {
alertview--;
if (buttonIndex == 0) {
} else {
[self.window addSubview:[tableController view]];
}
}
So, is there any way to get the UILocalNotification to use my delegate for the alert?
Or, is there a way to have an alert show up when the application is in the background?
Or, do I need to write two applications, so they communicate with remote notifications, so when the application is in the background one runs and then starts up the main application with a remote notification. I don't like this approach as it seems very messy, but would probably work.
Since the program never stops running
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
is only called at startup, the local notification never calls it and this also is never called:
- (void) application:(UIApplication*)application
didReceiveLocalNotification:(UILocalNotification *)localNotification {.
I suppose that you don't post notifications when the application is active. Couldn't you simply set a flag when you post a local notification and then check the flag when the application becomes active:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if (localNotificationPosted) {
// Do whatever you need to do.
}
}
This will likely be the correct behavior, since the application will become active unless the user dismisses the notification. Even if the user dismisses the notification, showing new messages the next time the app is opened probably isn't a bad thing.