I'm developing an app that manages messages, and I want the app connects to the server, get messages and save them in the database(coredata). If the messages already exist, doesnt do anything and if they dont, add them to the database.
I'm thinking some ways to do it but I don't know exactly what to do. Any help? Thanks in advance
I would recommend using Restkit framework http://restkit.org
Reskit provides integration with Core Data.
Pros of using Restkit:
- Combines HTTP request/responses API, along with object mapping, offline/caching support with Core Data, all in one framework
- Object Mapping means that you're writing clean code, you define your classes and how they map to the JSON attributes, then you GET/POST/DELETE with few lines of code after that
- Core Data support means that your projects can work offline, data is sync when working online, but persistent when you need it offline
- The framework is well maintained
Cons:
- Works only with JSON REST APIs
- There can be a steep learning curve for some aspects
- Can be challenging if you work with REST APIs that are not completely 'standard'
The simplest way is to add a guid attribute (an identifier of type NSString, for example) to the entity you are interested in and check for that guid when you import data.
Here, you have two ways: let the server generate the guid for you or implement your own algorithm in the client side (iPhone, iPad, etc.). In both cases you need to be sure the guid is unique for each message.
So, for example, suppose the server generates the messages (and each message has its own guid). When you import data you also save the guid for each message object. If you have already a message with a specific guid, you don't add it, otherwise you add it. This could be done using the Find-or-Create pattern (see Implementing Find-or-Create Efficiently).
Hope that helps.
This is simple, it took me sometime to learn this, I use it in most of my apps.
First you need an ID of the fetched item, for example messageID.
When you fetch the JSON with all the items, for example using AFNetworking, you're going to receive an array of objects in NSDictionaries.
Before parsing the item load all the IDs of your stored items in a NSMutableDictionary (key => messageID, value objectID, this is related to the Core Data fault).
Don't forget to init the NSMutableArray somewhere:
_dictionaryOfEventIDAndObjectID = [NSMutableDictionary dictionary];
- (void)prepareDictionaryOfMessageIDs
{
[self.dictionaryOfEventIDAndObjectID removeAllObjects];
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Message"];
[fetchRequest setResultType:NSDictionaryResultType];
NSExpressionDescription *objectIDDescription = [[NSExpressionDescription alloc] init];
objectIDDescription.name = #"objectID";
objectIDDescription.expression = [NSExpression expressionForEvaluatedObject];
objectIDDescription.expressionResultType = NSObjectIDAttributeType;
[fetchRequest setPropertiesToFetch:#[objectIDDescription, #"messageID"]];
NSArray *objectsDict = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSDictionary *objectDict in objectsDict) {
[self.dictionaryOfMessageIDAndObjectID setObject:[objectDict valueForKeyPath:#"objectID"] forKey:[objectDict valueForKeyPath:#"messageID"]];
}
}
Then in the fetched data completion block just add something like this:
for (NSDictionary *objectDict in objectsDict) {
NSString *fetchedID = [objectDict objectForKey:#"id"];
if ([self.dictionaryOfMessageIDAndObjectID objectForKey:fetchedID]) {
continue;
}
[self parseMessageFromDictionary:objectDict];
}
Related
I have an NSArray called namesArray.
I need to store all the names existing in namesArray using coredata.
How do i achieve this ?
Does we need to create any database like 'names.sqlite' using sqlite manager?
No you don't need to create a names.sqlite using manager. You should go through some of the tutorial found on the net for example: Here or Here.
You basically need to save in the datamodel in valueForKey format.
//Coredata saving
self.theAppDel=[[UIApplication sharedApplication] delegate];
NSManagedObjectContext*context=[self.theAppDel managedObjectContext];
NSManagedObject*object=[NSEntityDescription insertNewObjectForEntityForName:#"Contacts" inManagedObjectContext:context];
NSError*error=nil;
[object setValue:[namesArray objectAtIndex:0] forKey:#"name"]; //for saving first name
1. Creating an instance of your appdelegate .
2. Access your Managed Object Context and Managed Object
3. Assuming you created a entity description with name contacts. With the count of number of items in your array, add each object for a column named name (which i'm assuming you would have created).
This is short example, but you should go through the tutorials and read apple's documentation.
EDIT: As Ondra mentioned my earlier solution would have added only the last element. Use the following for adding: Adding NSMutableArray in CoreData Thanks Ondra Peterka
I think that iNoobs answer would not work - the loop would overwrite the value several times (only last name would be saved). In every case reading the tutorials is good idea.
Maybe the answer you are looking for is here: Saving an NSMutableArray to Core Data
Also ... I know you explicitly said "save to core data", but just in case - you can use also different storage like NSUserDefaults:
[[NSUserDefaults standardUserDefaults] setObject:namesArray forKey:#"namesArray"];
and retrieve it later using:
myArray= [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"namesArray"]];
Of course NSUserDefaults are ment to be used only for small amount of data.
Good luck ;)
I have a Core Data model representing a TV guide on iOS 4+, with 3 classes:
Channel (BBC 1)
Program (Top Gear)
Broadcast (Top Gear on BBC 1 on Monday at 8pm)
I have about 40 channels, 8000 programs and 6000 broadcasts, and I would like to fine-tune the import process so that it doesn't take up to a minute to run.
Importing the channels and programs is easy because these are independent objects. A broadcast however has a relationship to a channel and to a program (1-to-many), and both channels and programs have inverse relationships to the broadcasts (many-to-1). To speed things up I have an in-memory dictionary of fault channels and programs that have only their Web Service identifier prefetched: I create a broadcast and look through both dictionaries to get the corresponding channel and program without a round-trip to the database.
But when I assign a program or a channel to a broadcast, the channel and program's inverse relationships access trigger a fault of both objects right away, causing a massive slowdown (6000 * 2 requests) and consequent memory pressure as shown in the Core Data Faults Instruments report. I tried pre-fetching the broadcasts relationship on both channels and programs, but the relationship still gets faulted.
Do you have any idea why the inverse relationships get accessed and fault their parents? How do I avoid reading from the database when saving a relationship?
UPDATE: Sample code, my assign / update method for a Broadcast instance. The dictionary variable comes from the Web Service and channels and programs contain the fault channels and programs objects indexed by Web Service identifier. Faulting occurs on the self.program = program and self.channel = channel lines.
- (BOOL)assignWithDictionary:(NSDictionary *)dictionary channels:(NSDictionary *)channels programs:(NSDictionary *)programs {
// Add channel relationship
NSNumber *channelIdentifier = [dictionary objectForKey:#"channel_id"];
if (self.channel == nil || ![self.channel.identifier isEqualToNumber:channelIdentifier]) {
Channel *channel = [channels objectForKey:channelIdentifier];
if (channel == nil) {
NSLog(#"Broadcast %# has invalid channel: %#", identifier, channelIdentifier);
return NO;
}
self.channel = channel;
}
// Same to add a program relationship
// ...
}
And my fetch request to get the channels or the programs list:
- (NSDictionary *)itemsForEntity:(NSEntityDescription *)entity {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSError *error = nil;
NSArray *itemsArray = nil;
request.entity = entity;
request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:#"broadcasts", nil];
request.propertiesToFetch = [NSArray arrayWithObjects:#"identifier", #"version", nil];
itemsArray = [self.context executeFetchRequest:request error:&error];
NSAssert1(error == nil, #"Could not fetch the items from the database: %#", error);
{
NSMutableDictionary *items = [NSMutableDictionary dictionaryWithCapacity:itemsArray.count];
for (NSManagedObject *item in itemsArray) {
[items setObject:item forKey:[item valueForKey:#"identifier"]];
}
return [NSDictionary dictionaryWithDictionary:items];
}
}
Not exactly sure what you are trying to do here but...
The first thing is that you can't alter properties using just faults. Faults are just placeholders to allow you to measure/count the object graph and walk relationships. If you actually alter a relationship it will fire the fault causing the related objects to load.
If you are trying to set relationships between specific Channel, Program and Broadcast objects using just faults, that won't work.
Your itemsForEntity: method I don't understand. It will fetch every existing managed object of the entity passed and then it will return those objects in a dictionary. That will cause a massive memory overhead especially in the case of the Program objects of which there are 8,000.
You can't use propertiesToFetch unless you set the fetch return to dictionary, which you don't. You can't use a dictionary return type anyway if you need to set relationships. You use both these when all you want is the data held in certain attributes. It's not a tool for manipulating the object graph's relationships.
Setting the relationshipKeyPathsForPrefetching only speeds things up if you know you will be accessing an existing relationship. It doesn't help when you are setting the relationships up in the first place e.g. if there is no existing objects in the broadcasts relationships or you are adding or removing Broadcast objects, prefetching the broadcasts keypath does nothing for you.
I'm not sure I understand your data model well enough but I think you are going about this the wrong way. It looks to me like your trying to use the identifier like a primary key in a SQL database and that is counterproductive. In Core Data, a relationship links to objects together, not a shared attribute and value.
As a rule, if you have two or more objects with the same attribute name with the same value, then you have a poorly designed data model in most cases.
Judging from the amount of related questions that were brought up, this looks like a frequently asked question which makes me all the more hesitant to ask it. I have looked at a majority of these questions but none of them seem to address the specific problem I am having trouble figuring out.
A little background information on what I'm trying to achieve, how I am going about it, and what issue I am having:
I am trying to parse an XML web service, load that data into an NSMutableArray(Or any other place from which I can access it later), and then take my data and load it into my Core Data model. The first part of this I can do, its once I have my information in the array and trying to load it into Core Data that I cannot seem to progress.
My model(simplified for this question) consists of a route entity that has a one to many relationship with a checkpoint entity. The data I would be trying to load is a variety of attribute information into my route entity, which is not included in my array, and then my list of checkpoints, which is what the array is. My problem is that I cannot reliably add my entire array of checkpoints and then save. For the static case I am using for development, I have a consistent 20 checkpoints being parsed into my NSMutableArray, of these, the most I have been able to transfer into my NSMutableSet aka the checkpoints part of my route entity is 7 before crashing with either a SIGABRT, or EXC_BAD ACCESS, or incorrect selector sent. I have been trying to figure it out for the better part of today with no luck. Now for some code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
//newRoute is the route that I am trying to create and then store persistently
newRoute = [NSEntityDescription insertNewObjectForEntityForName:#"Route" inManagedObjectContext:context];
//filling in some available attribute information
if (name.text == #"")
[newRoute setValue:[NSDate date] forKey:#"name"];
else
[newRoute setValue:name.text forKey:#"name"];
NSMutableSet *muteSet = [newRoute mutableSetValueForKey:#"myCheckpoints"];
for (int i = 0; i < [appDelegate.checkpoints count]; i++ )
{
Checkpoint *newCheckpoint = [[Checkpoint alloc] init];
[newCheckpoint setName:[[appDelegate.checkpoints objectAtIndex:i] valueForKey:#"name"]];
NSLog(#"Appending %# to set", newCheckpoint.name);
[muteSet addObject:newCheckpoint];
[newCheckpoint release];
}
//myCheckpoints is the route<-->>checkpoints relationship
[newRoute setValue:muteSet forKey:#"myCheckpoints"];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Out of curiosity, why doesn't the following work?
NSSet *ckPtSet = [[NSSet alloc] initWithArray:appDelegate.checkpoints];
[newRoute setValue:ckPtSet forKey:#"myCheckpoints"];
As far as I understand, and this might be where the problem is... when setting the value of myCheckpoints, the expectation is to be passed an NSSet. When going through with the debugger the initialized set actually contains the 20 objects, but when I try to step past I get the incorrect selector received error again.
Anyways, thank you for taking the time to read my wall of text, if you need more to help please let me know and I will add it asap!
-Karoly
The documentation for [id<NSKeyValueCoding> mutableSetValueForKey:] states that it returns a mutable set proxy that provides read-write access to the unordered to-many relationship specified by a given key. This means that the object returned isn't necessarily an NSMutableSet instance per se, but a proxy wherein any changes you make to that object are reflected in your model's set itself.
This might be why [newRoute setValue:muteSet forKey:#"myCheckpoints"]; is giving you troubles. I find that a better way to think about it is to not have an intermediate object, but to nest calls, e.g.:
[[newRoute mutableSetValueForKey:#"myCheckpoints"] addObject:newCheckpoint];
Currently, I have an application where a user clicks on a map and adds annotation points with certain subtitles. However, when the phone is power-cycled, all their added points are gone. I'm interested in making these annotations persistent. I've been trying to figure out how to do this with Core-Data, but after reading this tutorial here: http://www.raywenderlich.com/934/core-data-tutorial-getting-started, I'm a bit lost on where to start.
Any help would be appreciated, thanks.
If you have a core data application set up, you will only need to create an entity in the xcdatamodel file. Add attributes for whatever you may want to store.
latitude - double
longitude - double
title - NSString
etc. until you have what you want.
When you want to add an annotation, you should create a new core data object for your entity. It will look something like this
Location *newLocation = (Location *)[NSEntityDescription insertNewObjectForEntityForName:#"Location" inManagedObjectContext:self.managedObjectContext];
Location.latitude = ....
// and so on to store the information you want in its attributes;
You can set the attributes at different point if you change the values at a later point. You just need to be able to access the right object to go with the annotation. You should be able to do this by using NSFetchRequest in your managedObjectContext. You can use NSPredicate to filter the objects to the one you want. Then when you are ready to exit the app, save your context using
NSError *error = nil;
if ([managedObjectContext save:&error]) {
// handle the error;
}
which will store all the objects you've added to be used next time you open the app. You will then be able to create your annotations based on the objects in your managedObjectContext. I hope this is what you were looking for, or at least gives you an idea how to approach what you'd like to do.
What I'm trying is this:
1) Create a new manged object
2) Get it's temporary id with [myMO objectID];
3) Convert that ID to an NSURL, so I can save it for future reference:
NSManagedObjectID *moID = [myMO objectID];
NSURL *url = [moID URIRepresentation];
4) Save the managed object context
5) Some time later, fetch that object using the NSURL as ID
NSManagedObjectID *moID = [[context persistentStoreCoordinator] managedObjectIDForURIRepresentation:url];
And guess what: It does not work. I get an empty-stupid object back from
NSManagedObject *myOldMo = [context existingObjectWithID: moID error:&error];
But...as I said...the ID is temporary when creating an managed object. So it does make sense why this doesn't work at all. I must first save the context, and then I get a persistet ID. The real one. Right?
So is that the way to go?
1) Create the managed object
2) Save the context
3) Get the ID as NSURL
4) any time later, for example on your next birthday, access the managed object with the NSURL ;-)
I try to dream of NSManagedObjectID like a DB id which I can write on some yellow postIt sheet and glue on the middle of my monitor, so I refer back to it after lunch. You know... at least like in the old days where we used databases over telnet and executed SQL commands manually to query order information and stuff like that. The ID was the most important and significant thing, all the time.
But Core Data has this somewhat strange NSManagedObjectID thing.
What are your secret strategies? Do you actually recognize many use cases where you would need that NSManagedObjectID? Or is that something I could easily forget with no pain afterwards?
I'm not sure that it's such a big secret. The documentation describes the way to get permanent IDs for managed objects from the NSManagedObjectContext:
- (BOOL)obtainPermanentIDsForObjects:(NSArray *)objects error:(NSError **)error
http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html#//apple_ref/occ/instm/NSManagedObjectContext/obtainPermanentIDsForObjects:error: