I just installed the framework restkit 0.9.3 and followed the Discussion Board example. Well, everything just worked great, however when I tried to use Core Data my User NSManagedObject class is duplicating even after declaring his primaryKeyAttribute (userID). For example, when I send a login request to my web-server, I return {"user":{"id":1, "username":"teste", ...}} .. but it seems to create a new row every time it invoques objectLoader:didLoadObjects.
User table:
Example code:
~ AppDelegate.m didFinishLaunching
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
userMapping.primaryKeyAttribute = #"userID";
userMapping.setDefaultValueForMissingAttributes = YES; // clear out any missing attributes (token on logout)
[userMapping mapKeyPathsToAttributes:
#"id", #"userID",
#"email", #"email",
#"username", #"username",
#"password", #"password",
nil];
[objectManager.mappingProvider registerMapping:userMapping withRootKeyPath:#"user"];
~ User.m loginWithDelegate
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
~ User.m didLoadObjects (RKObjectLoaderDelegate)
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
if ([objectLoader wasSentToResourcePath:#"/login"]) {
[self loginWasSuccessful];
}
NSLog(#"number of user rows: %i", [User findAll].count);
}
What am I doing wrong?
Are you correctly implementing RKManagedObjectCache? For debugging I had it simply return nil and forgot about that. A little while later I found I had duplicates also.
The cache works by fetching local objects and comparing with server returned objects. Any local objects that are not in the server response will be deleted. In earlier versions it used a fetch request but in newer versions you must manually perform the request and return actual objects.
If you return nil, it thinks this object is not in your cache and will add a duplicate. Try implementing this method:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
For example:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
[request setFetchLimit: 1];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", primaryKeyAttribute, primaryKeyValue]];
NSArray *results = [NSManagedObject executeFetchRequest:request inContext: managedObjectContext];
if ([results count] == 0)
{
return nil;
}
return [results objectAtIndex:0];
}
I have found the problem related to targetObject (RKObjectLoader)
/**
* The target object to map results back onto. If nil, a new object instance
* for the appropriate mapping will be created. If not nil, the results will
* be used to update the targetObject's attributes and relationships.
*/
So when I set it to nil the postObject calls findOrCreateInstanceOfEntity:withPrimaryKeyAttribute:andValue
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.targetObject = nil;
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.
Related
I want to insert 200 5Mb records in my Core Database. But when I save the NSManagedObject, the memory wasn't released (autoreleased pool didn't help), and after inserting 30 records I got the memory warning and the application crashed. Here is my code
- (void)SaveItem
{
NSString *entityName = kEntityName;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSEntityDescription *entityDesctiption = [NSEntityDescription
entityForName: entityName
inManagedObjectContext:context];
// check if town exists
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %d", self.imageID];
NSFetchRequest *requestToCheckExistense = [[NSFetchRequest alloc] init];
[requestToCheckExistense setEntity:entityDesctiption];
[requestToCheckExistense setPredicate:predicate];
NSArray *objects = [context executeFetchRequest:requestToCheckExistense error:nil];
[requestToCheckExistense release];
if (objects == nil)
{
NSLog(#"there was an error");
}
NSManagedObject *object;
if ([objects count] > 0)
{
// edit item
object = [objects objectAtIndex:0];
}
else
{
// if object doesn't exist, find max id to imlement autoincrement
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesctiption];
request.propertiesToFetch = [NSArray arrayWithObjects: #"id", nil];
NSArray *allobjects = [context executeFetchRequest:request error:nil];
[request release];
NSInteger newID = 1;
if ([allobjects count] > 0)
{
NSNumber *maxID = [allobjects valueForKeyPath:#"#max.id"];
newID = [maxID intValue] + 1;
}
// write item
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
[object setValue:[NSNumber numberWithInt:newID] forKey:#"id"];
self.imageID = newID;
}
// fill NSManagedObject
// size of objNSData is about 5MB
NSMutableData *objNSData = [[DatabaseManager sharedDatabaseManager] encryptedDataFromImage:bigImage];
[object setValue:objNSData forKey:#"big"];
[context save:nil];
}
When I commented
[object setValue:objNSData forKey:#"big"];
everything was OK.
I tried to add the code into #autoreleasepool , but that didn't help.
I know, that now, when I save data to database, it's still in iPhone RAM. How to release it from this memory? When I get a set of Managed Objects from the database, they are not in the RAM (I can easyly get 100 object, each of them has 5Mb fields)
object =(tblEntity *) [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
try to type cast the object,this may solve the problem
I've solved the issue.
after call of [self SaveItem];
I used
[context save];
[context reset];
[context save];
all the NSManagedObjects from the context will be released.
After that operation I can add as many big objects as I want
Because you don't own an NSManagedObject when you create it, it may be retained by the core data stack even after releasing it (when using an autoreleasepool contained inside the loop).
This may help:
Set the undo manager of your managedobjectContext to nil:
[context setUndoManager:nil];
Be sure that no properties of that object are retained anywhere, because then the managed object will not be released on time inside your loop.
Be sure to add an autorelease pool inside every loop execution, not wrapping all the loop itself, similar to:
for(i;i<n;i++) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[obj saveItem];
[pool drain];
}
If that object belongs to a hierarchy of NSManagedObjects, then you need to release the owner of this object too, for this one to be deallocated from memory.
You can check apple's documentation about memory management in CoreData.
Warning: big objects (> 1MB) are not recommended by Apple to be stored inside CoreData (Check this other question/answer.)
I'm not sure how to use the XMPPFramework's core data to store incoming messages. Does anyone have any tutorials on how to do this? I see User objects, which in turn can have many "resources". Is each message received supposed to be a new resource that I create and persist?
I do not know what part is my responsibility and what part the framework provides regarding message history. I can intercept every incoming message. Then am I supposed to create and store each message inside a Messages table using core data?
I'd have a Message Entity. And each XMPPUser would have an array of Message objects. But then wouldn't I be rolling my own solution, which would be working against the framework?
Thanks!
I know this is an old thread but as I am currently working with XMPP on iOS I must say that there is a built in support for archiving messages in XMPP.
I downloaded the XMPP framework for iOS and in it there is folder marked XEP-0136. Import the folders in XCode and activate client side archiving by using the following lines of code in the class you instantiate XMPP client:
xmppMessageArchivingStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
xmppMessageArchivingModule = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:xmppMessageArchivingStorage];
the following one line of code saves you from sending archive specific stanzas to the xmpp server
which will most probably respond with service-not-implemented
[xmppMessageArchivingModule setClientSideMessageArchivingOnly:YES];
[xmppMessageArchivingModule activate:xmppStream];
[xmppMessageArchivingModule addDelegate:self delegateQueue:dispatch_get_main_queue()];
And you are set. From that moment on, messages (outgoing and incoming) will be stored in a table created by the framework.
If you need more info please comment and i will get back to you.
#PraviJay
I did like this :
-(void)testMessageArchiving{
XMPPMessageArchivingCoreDataStorage *storage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
NSManagedObjectContext *moc = [storage mainThreadManagedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"XMPPMessageArchiving_Message_CoreDataObject"
inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entityDescription];
NSError *error;
NSArray *messages = [moc executeFetchRequest:request error:&error];
[self print:[[NSMutableArray alloc]initWithArray:messages]];
}
-(void)print:(NSMutableArray*)messages{
#autoreleasepool {
for (XMPPMessageArchiving_Message_CoreDataObject *message in messages) {
NSLog(#"messageStr param is %#",message.messageStr);
NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:message.messageStr error:nil];
NSLog(#"to param is %#",[element attributeStringValueForName:#"to"]);
NSLog(#"NSCore object id param is %#",message.objectID);
NSLog(#"bareJid param is %#",message.bareJid);
NSLog(#"bareJidStr param is %#",message.bareJidStr);
NSLog(#"body param is %#",message.body);
NSLog(#"timestamp param is %#",message.timestamp);
NSLog(#"outgoing param is %d",[message.outgoing intValue]);
}
}
}
Hope it helps :)
The responses that indicate XMPP Framework doesn't save the history are incorrect.
To integrate results in a table view use:
XMPPMessageArchivingCoreDataStorage *storage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
NSManagedObjectContext *moc = [storage mainThreadManagedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"XMPPMessageArchiving_Contact_CoreDataObject"
inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entityDescription];
_contactsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:nil cacheName:#"MessagesContactListCache"];
NSError *error;
BOOL rval = [_contactsController performFetch:&error];
an example to get archived messages in Swift 4
declares and initializes the variables XMPPMessageArchivingCoreDataStorage where I initialize the XMPPStream
var xmppMessageStorage: XMPPMessageArchivingCoreDataStorage?
var xmppMessageArchiving: XMPPMessageArchiving?
xmppMessageStorage = XMPPMessageArchivingCoreDataStorage.sharedInstance()
xmppMessageArchiving = XMPPMessageArchiving(messageArchivingStorage: xmppMessageStorage)
xmppMessageArchiving?.clientSideMessageArchivingOnly = true
xmppMessageArchiving?.activate(stream)
xmppMessageArchiving?.addDelegate(self, delegateQueue: DispatchQueue.main)
doing this, whenever a message arrives, this will cause it to be archived without needing to do anything else.
then, to retrieve the archived message
func RecibedMessageArchiving(idFriend: String) {
let JabberIDFriend = idFriend //id friend chat, example test1#example.com
let moc = xmppMessageStorage?.mainThreadManagedObjectContext
let entityDescription = NSEntityDescription.entity(forEntityName: "XMPPMessageArchiving_Message_CoreDataObject", in: moc!)
let request = NSFetchRequest<NSFetchRequestResult>()
let predicateFormat = "bareJidStr like %# "
let predicate = NSPredicate(format: predicateFormat, JabberIDFriend)
request.predicate = predicate
request.entity = entityDescription
//jabberID id del usuario, cliente
var jabberIDCliente = ""
if let jabberj = globalChat.value(forKey: "jabberID"){
jabberIDCliente = jabberj as! String
}
do {
let results = try moc?.fetch(request)
for message: XMPPMessageArchiving_Message_CoreDataObject? in results as? [XMPPMessageArchiving_Message_CoreDataObject?] ?? [] {
var element: DDXMLElement!
do {
element = try DDXMLElement(xmlString: (message as AnyObject).messageStr)
} catch _ {
element = nil
}
let body: String
let sender: String
let date: NSDate
let isIncomings: Bool
if message?.body != nil {
body = (message?.body)!
} else {
body = ""
}
if element.attributeStringValue(forName: "to") == JabberIDFriend {
sender = jabberIDCliente
isIncomings = false
} else {
sender = "test2#example.com"
isIncomings = true
}
var m: [AnyHashable : Any] = [:]
m["msg"] = message?.body
print("body", message?.body)
print("test", element.attributeStringValue(forName: "to"))
print("test2", element.attributeStringValue(forName: "body"))
}
} catch _ {
//catch fetch error here
}
}
XMPPFramework does not store message history,So i suggest to you it is better to use core data.Create a table by taking sender,receiver,message,time as columns .Insert record when send message method calling and receive message method calling...
-(void)saveChatHistory:(NSString *)sender:(NSString*)receiver:(NSString*)message:(NSString*)time
{
NSManagedObjectContext *context=[[self appDelegate] managedObjectContext];
NSManagedObject *newContext=[NSEntityDescription insertNewObjectForEntityForName:#"ChatHistory" inManagedObjectContext:context];
[newContext setValue:sender forKey:#"sender"];
[newContext setValue:receiver forKey:#"receiver"];
[newContext setValue:message forKey:#"message"];
[newContext setValue:time forKey:#"time"];
NSError *error;
if(![context save:&error])
{
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Error Occured" message:#"Data is not Stored in Database Try Again" delegate:self cancelButtonTitle:#"ok" otherButtonTitles:nil];
[alertView show];
}
}
Retrive chat history when specific user selected from tableview.... the fallowing method shows how to retrive chat history...and call this method from didSelectRowAtIndexPath method and pass destination id as parameter
-(void)getChatHistory:(NSString*)jidString1
{
NSManagedObjectContext *context=[[self appDelegate] managedObjectContext];
NSEntityDescription *entity=[NSEntityDescription entityForName:#"ChatHistory" inManagedObjectContext:context];
NSFetchRequest *req=[[NSFetchRequest alloc] init];
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"receiver=%#",jidString1];
[req setEntity:entity];
[req setPredicate:predicate];
NSManagedObject *matchRecords=nil;
NSError *error;
NSArray *objects=[context executeFetchRequest:req error:&error];
if([objects count]==0)
{
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"No Record found" message:#"there is no previous chat history" delegate:self cancelButtonTitle:#"ok" otherButtonTitles:nil];
[alertView show];
}
else
{
for(int i=0;i<[objects count];i++)
{
matchRecords=[objects objectAtIndex:i ];
NSLog(#"sender is %#",[matchRecords valueForKey:#"sender"]);
NSLog(#"reciver is %#",[matchRecords valueForKey:#"receiver"]);
NSLog(#"messages is %#",[matchRecords valueForKey:#"message"]);
NSLog(#"time is %#",[matchRecords valueForKey:#"time"]);
}
}
}
I hope this is useful to you
It seems crazy to me that I have all of these NSFetchRequests for the same NSManagedObjects spread out throughout different view controllers in my app, is there a good pattern for data access that puts what I need in a single place?
I agree it is a bit much, fortunately there is Active Record for Core Data. This makes fetching less tedious, for example, fetching all Person objects from core data would be as simple as
NSArray *people = [Person findAll];
Yes there is, it is called a facade pattern. Simply define a public method on your NSManagedObject subclass like so:
#interface Group : NSManagedObject { }
// … cruft here …
-(NSArray*)peopleSortedByName;
#end
And hide the nasty implementation like so:
-(NSArray*)peopleSortedByName;
{
NSFetchRequest* request = // … bla bla, lots of code here
return [[self managedObjectContext] executeFetchRequest:request
error:NULL];
}
Then use the method just as if the it was any other class in your code. Write once, relief everywhere.
Define a category method for NSManagedObject context which wrappers up a general query into a one-liner.
#interface NSManagedObjectContext(MyQueryAdditions)
-(NSArray *)queryEntityForName:(NSString *)name predicateFormat:(NSString *)pstring argumentArray:(NSArray *)arr;
#end
#implementation NSManagedObjectContext(MyQueryAdditions)
-(NSArray *)queryEntityForName:(NSString *)name predicateFormat:(NSString *)pstring argumentArray:(NSArray *)arr
{
NSEntityDescription *entity = [NSEntityDescription entityForName:name inManagedObjectContext:self];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:entity];
NSPredicate *pred;
if(pstring)
{
if(arr) pred = [NSPredicate predicateWithFormat:pstring argumentArray:arr];
else pred = [NSPredicate predicateWithFormat:pstring];
[fetch setPredicate:pred];
}
NSError *error = nil;
[self retain];
[self lock];
NSArray *results = [self executeFetchRequest:fetch error:&error];
if (error) {
NSLog(#"MOC Fetch - Unresolved error %#, %#", error, [error userInfo]);
results = [NSArray array];
}
[self unlock];
[self release];
return results;
}
#end
Means a basic all items query can be as simple as
NSArray *cres = [managedObjectContext queryEntityForName:#"Person" predicateFormat:nil argumentArray:nil];
I am writing an application that has four main entities that are all linked via relationships. Some are one to one, some are one to many. Upon initial load, three of the entities load their data from XML files stored locally to the application and one of the entities downloads an XML from the web and loads its data from it. When the app loads it performs a check to see if the data from each of these files is more recent than what it currently has and, if so, it will replace all current data in that entity with data from the appropriate file.
As part of my debug process during writing I have been forcing a delete of all data. When the delete function is called and all data is loaded at app launch the application runs beautifully and all entities and relationships behave exactly as they should. However, when I remove the call to the delete function and it performs the checks and tries to run from data it has stored, all of the relationships seem to disappear. In debugging this, I have found that all of the entities do contain all of the regular data that they are supposed to, they just don't have the relationships anymore. I can't figure out why in the world the relationships are saved on first load but don't retain when all data is not re-imported.
I would imagine some code would be helpful to anyone debugging, however, I'm not sure how much I should include. So, I will start by including just one of the methods called in the data loading class. If anything else would help, please let me know. Any help is very much appreciated.
UPDATED CODE: 2/25/11 (Based on Suggestions - Problem still exists)
UPDATED CODE: 2/25/11 - Problem Solved
- (NSArray *) loadFeatures {
if ([self checkForUpdate:#"Features"]) {
[self deleteAllObjects:#"Features"];
NSString *filePath = [self dataFilePath:FALSE withResourceName:#"Features"];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
NSArray *featureElements = [doc.rootElement elementsForName:#"FEATURE"];
NSMutableSet *featureSections = [[NSMutableSet alloc] init];
for (GDataXMLElement *featureElement in featureElements) {
NSString *featureName = nil;
NSNumber *featureSecure = nil;
NSNumber *featureID = nil;
NSNumber *featureSortKey = nil;
DisplayTypes *featureDisplayType = nil;
NSArray *names = [featureElement elementsForName:#"NAME"];
if (names.count > 0) {
GDataXMLElement *firstName = (GDataXMLElement *) [names objectAtIndex:0];
featureName = firstName.stringValue;
} else continue;
NSArray *secures = [featureElement elementsForName:#"SECURE"];
if (secures.count > 0) {
GDataXMLElement *firstSecure = (GDataXMLElement *) [secures objectAtIndex:0];
featureSecure = [NSNumber numberWithInt:firstSecure.stringValue.intValue];
} else continue;
NSArray *featureIDs = [featureElement elementsForName:#"FEATUREID"];
if (featureIDs.count > 0) {
GDataXMLElement *firstFeatureID = (GDataXMLElement *) [featureIDs objectAtIndex:0];
featureID = [NSNumber numberWithInt:firstFeatureID.stringValue.intValue];
}
NSArray *featureSortKeys = [featureElement elementsForName:#"SORTKEY"];
if (featureSortKeys.count > 0) {
GDataXMLElement *firstSortKey = (GDataXMLElement *) [featureSortKeys objectAtIndex:0];
featureSortKey = [NSNumber numberWithInt:firstSortKey.stringValue.intValue];
}
NSArray *featureDisplays = [featureElement elementsForName:#"DISPLAYTYPEID"];
if (featureDisplays.count > 0) {
GDataXMLElement *firstFeatureDisplay = (GDataXMLElement *) [featureDisplays objectAtIndex:0];
for (DisplayTypes *thisDisplayType in self.displayTypes) {
if (thisDisplayType.displayTypeID == [NSNumber numberWithInt:firstFeatureDisplay.stringValue.intValue]) {
featureDisplayType = thisDisplayType;
}
}
}
NSArray *sectionElements = [featureElement elementsForName:#"SECTIONS"];
for (GDataXMLElement *sectionElement in sectionElements) {
NSArray *sectionIDs = [sectionElement elementsForName:#"SECTION"];
for (GDataXMLElement *sectionID in sectionIDs) {
NSArray *thisSectionIDs = [sectionID elementsForName:#"SECTIONID"];
if ([thisSectionIDs count]) {
GDataXMLElement *thisSectionID = (GDataXMLElement *) [thisSectionIDs objectAtIndex:0];
for (Sections *thisSection in self.sections) {
if ([thisSection.sectionID isEqualToNumber:[NSNumber numberWithInt:thisSectionID.stringValue.intValue]]) {
[featureSections addObject:thisSection];
}
}
}
}
}
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *featureInfo = [NSEntityDescription insertNewObjectForEntityForName:#"Features" inManagedObjectContext:context];
[featureInfo setValue:featureName forKey:#"name"];
[featureInfo setValue:featureSecure forKey:#"secure"];
[featureInfo setValue:featureID forKey:#"featureID"];
[featureInfo setValue:featureSortKey forKey:#"sortKey"];
[featureInfo setValue:featureDisplayType forKey:#"display"];
[[featureInfo mutableSetValueForKey:#"section"] unionSet:featureSections];
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[[self.managedObjectContext objectWithID:featureDisplayType.objectID] addFeatureObject:featureInfo];
[self.managedObjectContext save:&error];
[featureSections removeAllObjects];
}
[xmlData release];
[doc release];
[featureSections release];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Features" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *featureArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
return featureArray;
}
UPDATE: 5/25/2011
Per request I am posting a couple of screen shots.
1) This is what I get when the app loads after all data has been deleted and the relationships are in tact
2) This is what I get when the app runs again without first deleting and reloading data. The tabs at the bottom are created by one of the entities, and are titled a bit different. This happens because the relationship with the DisplayType is not present and it doesn't know what type of view controller to load and it doesn't know which icon to use for the tab.
Typically, you wouldn't need to explicitly set both sides of a relationship. When you're dealing with a to-many relationship, it's probably safer to add one entity at a time to the collection, instead of setting the collection all at once. So, instead of:
[featureInfo setValue:[NSSet setWithSet:featureSections] forKey:#"section"];
I would loop through the featureSections Set and add each object one by one to the section relationship of the Feature entity, e.g.:
for (Sections *aSection in featureSections) {
// use the automatically-generated relationship mutator
[featureInfo addSectionsObject:aSection];
}
I hope this helps...
Otherwise, this section in the Apple documentation might be of interest.
I am loading my app with a property list of data from a web site. This property list file contains an NSArray of NSDictionaries which itself contains an NSArray of NSDictionaries. Basically, I'm trying to load a tableView of restaurant menu categories each of which contains menu items.
My property list file is fine. I am able to load the file and loop through the nodes structure creating NSEntityDescriptions and am able to save to Core Data. Everything works fine and expectedly except that in my menu category managed object, I have an NSArray of menu items for that category. Later on, when I fetch the categories, the pointers to the menu items in a category is lost and I get all the menu items. Am I suppose to be using predicates or does Core Data keep track of my object graph for me?
Can anyone look at how I am loading Core Data and point out the flaw in my logic? I'm pretty good with either SQL and OOP by themselves, but am a little bewildered by ORM. I thought that I should just be able to use aggregation in my managed objects and that the framework would keep track of the pointers for me, but apparently not.
NSError *error;
NSURL *url = [NSURL URLWithString:#"http://foo.com"];
NSArray *categories = [[NSArray alloc] initWithContentsOfURL:url];
NSMutableArray *menuCategories = [[NSMutableArray alloc] init];
for (int i=0; i<[categories count]; i++){
MenuCategory *menuCategory = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuCategory"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *category = [categories objectAtIndex:i];
menuCategory.name = [category objectForKey:#"name"];
NSArray *items = [category objectForKey:#"items"];
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
for (int j=0; j<[items count]; j++){
MenuItem *menuItem = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuItem"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *item = [items objectAtIndex:j];
menuItem.name = [item objectForKey:#"name"];
menuItem.price = [item objectForKey:#"price"];
menuItem.image = [item objectForKey:#"image"];
menuItem.details = [item objectForKey:#"details"];
[menuItems addObject:menuItem];
}
[menuCategory setValue:menuItems forKey:#"menuItems"];
[menuCategories addObject:menuCategory];
[menuItems release];
}
if (![[self managedObjectContext] save:&error]) {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
You set a NSArray as to-many relationship object
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
[menuCategory setValue:menuItems forKey:#"menuItems"];
which might cause the trouble.(should throw an exception?) Relationships in CoreData are always unsorted, therefore NSSets. Add a sortIndex property to your entities for ordering.
I had the same issue. There are 2 major problems with using NSSets and Core Data: if you need non-distinct objects and need them ordered. As an example, say you have 2 entities in Core Data: professor and student. The student takes 10 classes for a degree program and you wish to have a (one-to-many) relationship from the student to the professor in order that the classes were taken. Also, the same professor may teach more than one class. This was how I overcame the issue. Create a Binary Data attribute (we'll call it profData) in student and store dictionaries that make it possible to reconstruct the data as needed. Note: don't store an array of professors, since they inherit from NSManagedObject vs. NSObject. That can cause problems. You can bolt on the required methods using a category. In this example, I created a category on Student called ProfList (Student+ProfList.h/m). This keeps the code out of the NSManagedObject subclasses, so if my attributes in Core Data change, I can regenerate the subclasses automatically without wiping out this code. Here is some sample code:
// Student+ProfList.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Student.h"
#import "Professor.h"
#interface Student (ProfList)
- (NSArray *)getStudentsFullList;
- (void)storeStudentsFullList:(NSArray *)fullList;
#end
// Student+ProfList.m
#import "Student+ProfList.h"
#implementation Student (ProfList)
- (NSArray *)getStudentsFullList
{
NSData *storedData = self.profData;
if (!storedData) return nil;
NSMutableArray *fullList = [[NSMutableArray alloc] init];
// Retrieve any existing data
NSArray *arrayOfDictionaries = [NSKeyedUnarchiver unarchiveObjectWithData:storedData];
// Get the full professor list to pull from when recreating object array
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Professor"];
NSSortDescriptor *alphaSort =
[[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:#[alphaSort]];
NSSet *allProfessors = [NSSet setWithArray:[context executeFetchRequest:fetchRequest error:nil]];
for (NSDictionary *dict in arrayOfDictionaries) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name LIKE %#", [dict objectForKey:#"name"]];
NSSet *filteredSet = [allProfessors filteredSetUsingPredicate:predicate];
Professor *newProfessor = [filteredSet anyObject];
newProfessor.index = [dict objectForKey:#"index"];
[fullList addObject:newProfessor];
}
return fullList;
}
- (void)storeStudentsFullList:(NSArray *)fullList
{
NSMutableArray *encodedList = [[NSMutableArray alloc] init];
for (Professor *professor in fullList) {
[encodedList addObject:#{#"index" : #([encodedList count]), #"name" : professor.name}];
}
NSArray *encodedArray = [NSArray arrayWithArray:encodedList];
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:encodedArray];
self.profData = arrayData;
}
#pragma mark - Core Data
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
#end
You store a local variable in a view controller, then send this message to the student instance to get the list and save it locally for use in a table view or whatever.