How can I initialize CloudantToolkit and create local datastore? - ibm-mobilefirst

MFP Foudantion provides CloudantToolkit SDK. The manual tells us how to create local datastore(Creating databases).But it uses IMFData SDK.
I think Cloudant toolkit doesn't depend on IMFData SDK.
//Get reference to data manager
IMFDataManager *manager = [IMFDataManager sharedInstance];
NSString *name = #"automobiledb";
NSError *error = nil;
//Create local store
CDTStore *store = [manager localStore:name error:&error];

You are correct. CloudantToolkit does not depend on IMFData. IMFData depends on CloudantToolkit.
You can create a local store with CloudantToolkit. CloudantToolkit does not have an initialization call. Here is sample code to create a local store with just CloudantToolkit.
//Use CDTDatastoreManager to create CDTDatastore
CDTDatastoreManager *datastoreManager = existingDatastoreManager;
NSError *error = nil;
NSString *name = #"automobiledb";
CDTDatastore *cdtdatastore = [datastoreManager datastoreNamed:name error:&error];
if(error){
NSLog(#"Could not create cdtdatastore: %#", error);
}
//Create local store
CDTStore *store = [CDTStore localStoreWithDatastore:cdtdatastore];
You will need to create a CDTDatastoreManager as shown in the documentation for CDTDatastore. See https://github.com/cloudant/CDTDatastore

Related

Renamed Core Data Transformable

I'm using CoreData to manage my offline storage in my app. The data of the offline storage is saved in an custom NSObject as a transformable in the xcdatamodel.
My current version in the app (v1.0) is storing the Navigation class.
I needed to rename the Navigation class to fight some name space problems in another target of my code base.
In Version 2.0 the app is crashing because when I'm searching the CoreData store the NSKeyedUnarchiver is failing because of the missing Navigation class.
What's the best approach to migrate my current CoreData store to fight this issue?
I tried something like this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"OfflineStorage"];
request.fetchLimit = 1;
request.predicate = [NSPredicate predicateWithFormat:#"storageID = 1"];
NSError *error;
NSArray *result;
#try {
result = [context executeFetchRequest:request error:&error];
}
#catch (NSException *exception) {
if([exception.name isEqualToString:NSInvalidUnarchiveOperationException]) {
NSFetchRequest *deleteRequest = [request copy];
deleteRequest.resultType = NSManagedObjectIDResultType;
deleteRequest.includesPropertyValues = NO;
deleteRequest.propertiesToFetch = #[];
NSArray *deleteResult = [context executeFetchRequest:request error:&error];
if(deleteResult.count) {
[context performBlockAndWait:^{
[context deleteObject:deleteResult.firstObject];
[context save:nil];
}];
}
}
}
I though I would be smart to catch the exception and try only to fetch the ObjectID to delete the corrupt data in my store. But it's not working…
Did you add a new version to your model where you renamed that transformable class as well?
Did you try to do your own migration and rename the transformer for the transformable?
Here's some documentation: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html#//apple_ref/doc/uid/TP40004399-CH1-SW1
I didn't do that myself, yet. As auto migration was enough for me by now.
Update
I'm sorry, I misunderstood your question.
The transformable is transformed by your own value transformer? If so, you could use this in the NSKeyedUnarchiver to replace the class with the old name by the new class: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSKeyedUnarchiver_Class/index.html#//apple_ref/occ/clm/NSKeyedUnarchiver/setClass:forClassName:

How to re-use Core Data in Extension code in Objective-C? The managedObjectContext can't be created by UIApplicationDelegate as we use to do

The problem I meet is we can't get the managedObjectContext by this way:
[((MDAppDelegate*)appController) mainQueueContext] ;
Because the error message is:
'sharedApplication' is unavailable: not available on iOS (App
Extension) - Use view controller based solutions where appropriate
instead.
My QUESTION is:
Is there any existing example to help us connect to Core Data through Extension (Today/Watch)?
P.S. I have read following questions, none of them help. I just need an example:
App and Extension - Use Core data == error : sharedApplication()' is unavailable
WatchKit : 'sharedApplication' is unavailable: not available on iOS (App Extension) - Use view controller based solutions where appropriate instead
Use AppDelegate in today extension
We found and fix the problem now. The problem is caused by my misunderstanding of Core Data. We used to re-use tutorial's source code. When the system is work, we have no time to get deep understand of it.
The problem is we can't use Container App's managed object context. To fix it we use following code:
replace
[((MDAppDelegate*)appController) mainQueueContext] ;
to
[self mainQueueContext] ;
Then, add following...
- (NSManagedObjectContext *)mainQueueContext {
if (_mainQueueContext != nil) {
return _mainQueueContext;
}
_mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainQueueContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[_mainQueueContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
return _mainQueueContext; }
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL;
NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.<your project group ID>"].path;
NSString *sqlitePath = [NSString stringWithFormat:#"file://%#/%#", containerPath, #"<Your database file>.sqlite"];
storeURL = [NSURL URLWithString:sqlitePath];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
abort();
}
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:storeURL.path error:&error]) {
// Handle error
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL;
NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.<your project group ID>"].path;
NSString *modelPath = [NSString stringWithFormat:#"file://%#/%#", containerPath, #"<Your database file>.momd"];
modelURL = [NSURL URLWithString:modelPath];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
Another Important issue is the Container App is still using the Core Data store located in application's Documents directory. The extension can't access it. So, we migrate the store to group shared folder using below lines:
NSString *directoryShared = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.<your project group ID>"].path;
NSString *storePathShared = [NSString stringWithFormat:#"file://%#/%#", directoryShared, #"<Your database file>.sqlite"];
NSURL *storeUrlShared = [NSURL URLWithString:storePathShared];
[_persistentStoreCoordinator migratePersistentStore:store
toURL:storeUrlShared
options:options
withType:NSSQLiteStoreType
error:&error];
if (error != nil) {
NSLog(#"Error when migration to groupd url %#, %#", error, [error userInfo]);
}
We just leave the original database alone, let the container app keep using it. there are some better way to achieve it. like:
Migrating NSPersistentStore from application sandbox to shared group container

Returning an object from inside block within category class method implementation

I have run into a certain problem with my implementation which I don't really know how to solve. Could You please advise.
I'm trying to implement an NSManagedObject category class Photo+Flickr.m with one class method +(void)photoWithFlickrData:inManagedObjectContext:
What I would like to do is download data from Flickr API using NSURLSessionDownloadTask and then create Photo object and insert this new created object into database (if it's not already there). This part works fine.
And at the end I would like to return new created (or object that was found in db) Photo object. And this is where I run into problem. Since I'm using category I can't use instance variables. I can't really find any good solution to get this Photo object from inside this completionHandler block.
My code:
#implementation Photo (Flickr)
+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
inManagedObjectContext:(NSManagedObjectContext *)context
{
NSString *placeId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_PLACE_ID];
NSURL *urlInfoAboutPlace = [FlickrFetcher URLforInformationAboutPlace:placeId];
NSURLRequest *request = [NSURLRequest requestWithURL:urlInfoAboutPlace];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *task =
[session downloadTaskWithRequest:request
completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
if(!error) {
NSData *json = [NSData dataWithContentsOfURL:localfile];
NSDictionary *flickrPlaceDictionary = [NSJSONSerialization JSONObjectWithData:json
options:0
error:NULL];
dispatch_async(dispatch_get_main_queue(), ^{
Photo *photo = nil;
// flickr photo unique id
NSString *uniqueId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_ID];
NSFetchRequest *dbRequest = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
dbRequest.predicate = [NSPredicate predicateWithFormat:#"uniqueId = %#", uniqueId];
NSError *error;
NSArray *reqResults = [context executeFetchRequest:dbRequest error:&error];
if (!reqResults || error || [reqResults count] > 1) {
//handle error
} else if ([reqResults count]) {
//object found in db
NSLog(#"object found!");
photo = [reqResults firstObject];
} else {
//no object in db so create a new one
NSLog(#"object not found, creating new one");
photo = [NSEntityDescription insertNewObjectForEntityForName:#"Photo"
inManagedObjectContext:context];
//set its properties
photo.uniqueId = uniqueId;
photo.title = [photoDictionary valueForKey:FLICKR_PHOTO_TITLE];
photo.region = [FlickrFetcher extractRegionNameFromPlaceInformation:flickrPlaceDictionary];
NSLog(#"title: %#", photo.title);
NSLog(#"ID: %#", photo.uniqueId);
NSLog(#"region: %#", photo.region);
}
});
}
}];
[task resume];
//how to get Photo *photo object???
//return photo;
}
I would really appreciate any suggestions on how to implement this.
Since you have async operations happening inside your blocks, you'll need to pass a completion handler (block) to your photoWithFlickrData:inManagedObjectContext: method and call it when you have valid photo data.
You'll need to add a new parameter to your method so you can pass in the completion handler. I'd do something like this:
+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
inManagedObjectContext:(NSManagedObjectContext *)context
withCompletionHandler:(void(^)(Photo *photo))completionHandler
Then, when you have a valid photo object, call completionHandler like so:
completionHandler(photo);
It looks like you'd want to put that at the very end of the block you're passing to dispatch_async:
/* ... */
dispatch_async(dispatch_get_main_queue(), ^{
Photo *photo = nil;
/* ... */
completionHandler(photo);
});
/* ... */
Then, you can call your method like so:
[Photo photoWithFlickrData:photoDictionary
inManagedObjectContext:context
withCompletionHandler:^(Photo* photo) {
/* use your valid photo object here */
}
];
Outside of your block before you call [session downloadTaskWithRequest:.....] define a variable like this
__block Photo *photoObject = nil;
Then inside the block after you finish setting its properties, set
photoObject = photo;
Now you can do whatever you want with the photoObject variable outside of the block.
Check out this Apple developer documentation on Blocks and Variables.

How to tell if an iOS application has been newly installed or updated?

I have an application currently on the app store which I intend to submit an update for soon.
With this update I want to add code which will tell the app when it first runs application:didFinishLaunchingWithOptions whether it is:
A new install from the app store.
Newly updated from a previous version
There is no code in the app currently in the app store to handle this.
The application uses a SQLite database, but for reasons I won't go into here I don't want to use a check for its existence as a solution to this problem.
As a side question, without storing the data manually, is there an SDK I can use to query when an app was installed onto a device? (Preferably iOS 3.0 compatible)
I have seen a similar question, but none of the answers apply to working with existing app store code.
The following code may help to answer your side question about when an app was installed. I am unsure if the app bundle create date is the XCode build date or the download date as this is untested from app store.
NSString *bundleRoot = [[NSBundle mainBundle] bundlePath]; // e.g. /var/mobile/Applications/<GUID>/<AppName>.app
NSFileManager *manager = [NSFileManager defaultManager];
NSDictionary* attrs = [manager attributesOfItemAtPath:bundleRoot error:nil];
NSLog(#"Build or download Date/Time of first version to be installed: %#", [attrs fileCreationDate]);
NSLog(#"Date/Time of last install (unless bundle changed by code): %#", [attrs fileModificationDate]);
NSString *rootPath = [bundleRoot substringToIndex:[bundleRoot rangeOfString:#"/" options:NSBackwardsSearch].location]; // e.g /var/mobile/Applications/<GUID>
attrs = [manager attributesOfItemAtPath:rootPath error:nil];
NSLog(#"Date/Time first installed (or first reinstalled after deletion): %#", [attrs fileCreationDate]);
You could save a version number to NSUserDefaults, and update it accordingly.
If that won't work, you may be able to release an intermediate version which introduces the versioning scheme.
If that's not an option, you may be able to check for traces of previous runs from files you create, or preferences which you set conditionally or lazily.
try this code, i know i am too late for this answer but for knwoldge sharing here i go.
-(void) checkIsAppUpdated
{
NSString *urlString = #"http://itunes.apple.com/lookup?id=995558215";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:#"GET"];
NSError *error = nil;
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//NSString *stringReply = (NSString *)[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (error)
{
// NSLog(#"Error: %#", stringReply);
//error reviced
}
else
{
//The response is in data
//NSLog(#"Success: %#", stringReply);
NSDictionary *dictResponse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
float appStoreVersion=[[[[dictResponse objectForKey:#"results"] firstObject] objectForKey:#"version"] floatValue];
NSLog(#"app stroe version=%f",appStoreVersion);
NSString *strLocalVersion=[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
float localAppVersion=[strLocalVersion floatValue];
if (localAppVersion!=appStoreVersion)
{
//open app store url
// NSString *iTunesLink = #"itms://itunes.apple.com/us/app/apple-store/id375380948?mt=8";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"https://itunes.apple.com/app/washthenfold/id995558215?mt=8"]];
}
}
}
note
id:-replace id with your own app id. you can get app id from itune connect selected app->more info->meta data
since simulator doesn't have app store app, this code won't work on simulator.
GBVersionTracking is good pod to track all version history.
[GBVersionTracking isFirstLaunchEver];
Here is a simple code to know if the current version is different (this code work on simulator too.)
-(BOOL) needsUpdate
{
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString* appID = infoDictionary[#"CFBundleIdentifier"];
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"http://itunes.apple.com/lookup?bundleId=%#", appID]];
NSData* data = [NSData dataWithContentsOfURL:url];
NSDictionary* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([lookup[#"resultCount"] integerValue] == 1)
{
NSString* appStoreVersion = lookup[#"results"][0][#"version"];
NSString* currentVersion = infoDictionary[#"CFBundleShortVersionString"];
if (![appStoreVersion isEqualToString:currentVersion])
{
NSLog(#"Need to update [%# != %#]", appStoreVersion, currentVersion);
return YES;
}
}
return NO;
}
Note: Make sure that when you enter the new version in iTunes, this matches the version in the app you are releasing. If not then the above code will always return YES regardless if the user updates.

How to remove a core data persistent store

I need to delete my persistent store (doing it object by object is not practical because I have over 100,000 objects). I've tried this:
- (IBAction)resetDatabase:(id)sender {
NSPersistentStore* store = [[__persistentStoreCoordinator persistentStores] lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
// release context and model
[__managedObjectContext release];
[__managedObjectModel release];
__managedObjectModel = nil;
__managedObjectContext = nil;
[__persistentStoreCoordinator removePersistentStore:store error:nil];
[__persistentStoreCoordinator release];
__persistentStoreCoordinator = nil;
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];
if (error) {
NSLog(#"filemanager error %#", error);
}
// recreate the stack
__managedObjectContext = [self managedObjectContext];
}
But I get this error when I try to insert entities into the store afterwards:
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
Update:
I tried releasing the MOC and MOM before removing the persistent store but I still get the same error.
Here is how I do a "reset data" function in several apps:
- (void)reset {
// Release CoreData chain
[_managedObjectContext release];
_managedObjectContext = nil;
[_managedObjectModel release];
_managedObjectModel = nil;
[_persistentStoreCoordinator release];
_persistentStoreCoordinator = nil;
// Delete the sqlite file
NSError *error = nil;
if ([fileManager fileExistsAtPath:_storeURL.path])
[fileManager removeItemAtURL:_storeURL error:&error];
// handle error...
}
Basically I just release the CoreData chain, then delete the persistentStore file. That's what you are trying to do, without using removePersistentStore, which I do not care since I will just rebuild the persistentStore coordinator later. Then at next core data call the chain is rebuilt transparently using singleton-lazy-style constructors like :
- (NSManagedObjectModel *) managedObjectModel {
if (!_managedObjectModel)
_managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return _managedObjectModel;
}
You can do it externally given that you only need to do this while developing your application. I have a terminal open in which I remove the store manually before re-running my app. All you need to know is where it is located. I log it to console everytime my app runs with the following code:
[[CoreDataSingleton sharedManager] managedObjectContext]; //be sure to create the store first!
//Find targeted mom file in the Resources directory
NSString *momPath = [[NSBundle mainBundle] pathForResource:#"Parking" ofType:#"mom"];
NSLog(#"momd path: %#",momPath);
Hope that helps!
You need to make sure that any managed object context attached to the persistent store have been released before you try to delete the store. Otherwise, the context will evoke that error.