I am trying to subclass some Core Data classes. I've got the following classes:
Core Data class:
#interface CDExplanatoryMaterial : NSManagedObject
#property (nonatomic, retain) NSString * document;
#property (nonatomic, retain) NSString * id;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * pageNumber;
#property (nonatomic, retain) NSNumber * realPageNumber;
#property (nonatomic, retain) NSString * url;
#end
Business logic class's protocol:
#protocol BLDataClass <NSObject>
- (NSArray*)favouriteInGroups;
#property (nonatomic, readonly, retain) NSString* type;
#property (nonatomic, readonly, retain) NSArray* inFavouriteGroups;
- (void)addAddToFavouriteGroup: (NSString*) groupName;
- (void)removeFromFavouriteGroup: (NSString*) groupName;
- (void)addToHistory;
#end
Interface for BLExplanatoryMaterial:
#interface BLExplanatoryMaterial : CDExplanatoryMaterial <BLDataClass>
I get the data like this:
+ (NSMutableArray*) explanatoryMaterials {
NSMutableArray* results = [[NSMutableArray alloc] init];
for(CDExplanatoryMaterial *item in [Helper fetchDataObjectsOfType:#"CDExplanatoryMaterial"])
{
[results addObject: (BLExplanatoryMaterial*)item];
}
return results;
}
The helper class looks like this:
#implementation Helper
+ (NSArray*) fetchDataObjectsOfType:(NSString *)type {
DataManager* manager = [DataManager sharedInstance];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:type inManagedObjectContext:manager.mainObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *fetchedObjects = [manager.mainObjectContext executeFetchRequest:fetchRequest error:&error];
return fetchedObjects;
}
#end
This issue that I have is that the fetchedObjects array in fetchDataObjectsOfType and the results array in explanatoryMaterials only contain NSManagedObject objects. I'd expect fetchedObjects to contain CDExplanatoryMaterial objects and results to contain BLExplanatoryMaterial. I need the end result to be BLExplanatoryMaterial objects or I can't use any of my instance methods, what is the best way to do this?
Thanks,
Joe
EDIT:
Just to clarify it is the following code that fails because expMat is an NSManagedObject and doesn't support the addToFavouriteGroup method.
NSMutableArray* expMats = [Data explanatoryMaterials];
BLExplanatoryMaterial* expMat = (BLExplanatoryMaterial*) [expMats objectAtIndex:0];
[((BLExplanatoryMaterial*)expMat) addToFavouriteGroup:#"Test Group"]
One thing that I forgot to mention is that all of the code in my original post is in a static library. The code posted in this edit is in a IOS App project. I'm not sure if this makes a difference. All of the classes in the static library are marked as public.
You need to specify BLExplanatoryMaterial as the class for the entity in your managed object model. This will tell Core Data to instantiate objects of that class instead of NSManagedObject.
Thanks for all of the help. It turns out that because the core data classes were in a seperate static library they were not being built in to the main bundle. To get around this I subclassed them within my main application and then pointed the core data file at those subclasses.
Related
I'm trying to write and read from CoreData on OSX, combinig cocoa classes with applescript-objc.
I wrote methods to handle my CoreData as in tutorial (youtube, Cocoa Tutorial: Core Data Introduction in iOS and Mac OS Programming Part 2) <- those are cocoa classes in my project
I have a class called CoreDataHelper that defines methods to manipulate Core Data. Here are some of them:
+(NSManagedObjectContext *) managedObjectContext{
NSError*error;
[[NSFileManager defaultManager] createDirectoryAtPath:[CoreDataHelper directoryForDatabaseFilename] withIntermediateDirectories:YES attributes:nil error:&error];
if(error){
NSLog(#"%#", [error localizedDescription]);
return nil;
}
NSString* path = [NSString stringWithFormat:#"%#/%#",[CoreDataHelper directoryForDatabaseFilename],[CoreDataHelper databaseFilename]];
NSURL *url = [NSURL fileURLWithPath:path];
NSManagedObjectModel* managedModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator* storeCordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedModel];
if(![storeCordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]){
NSLog(#"%#",[error localizedDescription]);
return nil;
}
NSManagedObjectContext* managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
return managedObjectContext;
}
+(id)insertManagedObjectOfClass:(Class) aClass inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext{
NSManagedObject* managedObject = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(aClass) inManagedObjectContext:managedObjectContext];
return managedObject;
}
I'm using .xcdatamodeld for my Model.
I have 2 entities in it with classes for each one of them:
Tab
Service
Tab.h looks like this
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Service;
NS_ASSUME_NONNULL_BEGIN
#interface Tab : NSManagedObject
// Insert code here to declare functionality of your managed object subclass
#end
NS_ASSUME_NONNULL_END
#import "Tab+CoreDataProperties.h"
and Tab.m looks like this:
#import "Tab.h"
#import "Service.h"
#implementation Tab
// Insert code here to add functionality to your managed object subclass
#end
Tab+CoreDataProperties.h:
#import "Tab.h"
NS_ASSUME_NONNULL_BEGIN
#interface Tab (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *nZakladka;
#property (nullable, nonatomic, retain) NSString *uZakladka;
#property (nullable, nonatomic, retain) Service *podstronaSerwisu;
#end
NS_ASSUME_NONNULL_END
Tab+CoreDataProperties.m:
#import "Tab+CoreDataProperties.h"
#implementation Tab (CoreDataProperties)
#dynamic nZakladka;
#dynamic uZakladka;
#dynamic podstronaSerwisu;
#end
Now, my AppDelegate.applescript looks like this
...
property CoreDataHelper: class "CoreDataHelper"
property cService: class "Service"
property cTab: class "Tab"
script AppDelegate
on applicationWillFinishLaunching_(aNotification)
set theContext to CoreDataHelper's managedObjectContext()
set ccTab to cTab's alloc()'s init()
//the one below cause an error "CoreData: error: Failed to call designated initializer on NSManagedObject class 'Tab'"
set theTab to CoreDataHelper's insertManagedObjectOfClass_inManagedObjectContext_(ccTab, theContext)
end applicationWillFinishLaunching_
...
end Script
When I run whole thing I got an error message posted in a commented line in AppleScriptObjC code. The method that is causing an error is NSManagedObject* managedObject = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(aClass) inManagedObjectContext:managedObjectContext];
What is the problem?
I have the following Model:
#interface Person : NSObject
#property (nonatomic, copy) NSString *firstName;
#property (nonatomic, copy) NSString *middleName;
#property (nonatomic, copy) NSString *lastName;
#property (nonatomic, copy) NSString *status;
#property (nonatomic, copy) NSString *favoriteMeal;
#property (nonatomic, copy) NSString *favoriteDrink;
#property (nonatomic, copy) NSString *favoriteShow;
#property (nonatomic, copy) NSString *favoriteMovie;
#property (nonatomic, copy) NSString *favoriteSport;
-(NSDictionary *)getSomeInfo;
-(NSDictionary *)getAllInfo;
#end
Part 1:
I want getSomeInfo to return NSDictionary (e.g. {"firstName", self.firstName}) for all the fields that does not contain nil. How can I do that? (I could check every value but I wonder if there's a better way)
Part 2:
I want getAllInfo to return NSDictionary with all the property and if one contains nil then it should throw an error. Again do I have to write a long conditional statement to check or is there a better way?
Note: I want to do this without using external library. I'm new to the language so I'm open to suggestions if there's a better pattern in Objective-C.
There are two approaches.
1) Check each value:
- (NSDictionary *)getSomeInfo {
NSMutableDictionary *res = [NSMutableDictionary dictionary];
if (self.firstName.length) {
res[#"firstName"] = self.firstName;
}
if (self.middleName.length) {
res[#"middleName"] = self.middleName;
}
// Repeat for all of the properties
return res;
}
2) Use KVC (Key-value coding):
- (NSDictionary *)getSomeInfo {
NSMutableDictionary *res = [NSMutableDictionary dictionary];
NSArray *properties = #[ #"firstName", #"middleName", #"lastName", ... ]; // list all of the properties
for (NSString *property in properties) {
NSString *value = [self valueForKey:property];
if (value.length) {
res[property] = value;
}
}
return res;
}
For the getAllInfo method you can do the same but instead return nil if any value is missing. Treat the nil results as your indication that not all properties have a value.
I have 2 model:
#interface Program : NSObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, strong) NSMutableArray *guides;
#end
#interface Guide : NSObject
#property (nonatomic, retain) NSString *name;
#end
And I add some guides to program from one xml:
Program *program = [Program new];
program.name = #"My list"
for(DDXMLElement *guideElement in [programElement nodesForXPath:#"guide" error:&error])
{
Guide *guide = [Guide new];
guide.name = [guideElement stringValue];// [p attribute:#"name"];
[program.guides addObject:guide];
NSLog(#"load guide number: %d", [program.guides count]);
}
The out is always "load guide number: 0"
program.guides is nil, since you never created it.
In your Program's init method, add:
self.guides = [[NSMutableArray alloc] init];
Or, more sloppily, before your for loop add:
program.guides = [[NSMutableArray alloc] init];
I am trying to initialize NSDictionary but it gives me following error:
Program received signal: “EXC_BAD_ACCESS”.
Here is my problematic code[Quiz.m]:
#implementation Quiz
#synthesize question;
#synthesize correctAnswer;
#synthesize userAnswer;
#synthesize questionId;
-(NSString*) getAsJsonString
{
// Following line gives the error
NSDictionary *qDictionary=[[NSDictionary alloc] initWithObjectsAndKeys:question,#"questionText",questionId,#"questionId",userAnswer,#"userAnswer",correctAnswer,#"correctAnswer",nil];
..............
.........
............
return jsonString;
}
#end
And here is Quiz.h file for reference
#interface Quiz : NSObject {
#public
NSString * question;
BOOL correctAnswer;
BOOL userAnswer;
NSInteger questionId;
}
#property (nonatomic,retain) NSString * question;
#property (nonatomic, assign) NSInteger questionId;
#property (nonatomic,assign) BOOL correctAnswer;
#property (nonatomic,assign) BOOL userAnswer;
- (NSString*) getAsJsonString;
#end
How should I fix it, please help me, I am new to objective c and it is driving me nuts. Does the NSDictionary initWithObjectsAndKeys handle objects other than string and null objects?
NSDictionary cannot store scalar values (like BOOL, NSInteger, etc.), it can store only objects. You must wrap your scalar values into NSNumber to store them:
NSDictionary *qDictionary = [[NSDictionary alloc]
initWithObjectsAndKeys:question,#"questionText",
[NSNumber numberWithInteger:questionId],#"questionId",
[NSNumber numberWithBool:userAnswer],#"userAnswer",
[NSNumber numberWithBool:correctAnswer],#"correctAnswer",
nil];
I am doing something wrong here... I know that
I'm using Xcode and I have created the following class using the data modeller:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Project : NSManagedObject {
#private
}
#property (nonatomic, retain) NSNumber * indent;
#property (nonatomic, retain) NSNumber * collapsed;
#property (nonatomic, retain) NSString * color;
#property (nonatomic, retain) NSNumber * project_id;
#property (nonatomic, retain) NSNumber * item_order;
#property (nonatomic, retain) NSNumber * cache_count;
#property (nonatomic, retain) NSNumber * user_id;
#property (nonatomic, retain) NSString * name;
#end
When I am trying to propagate this class with data from a JSON source using the following code:
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"projects" ofType:#"json"];
if (filePath) {
NSString* jsonString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
DLog(#"JSON for Projects:%#", jsonString);
SBJsonParser* jsonParser = [SBJsonParser new];
id response = [jsonParser objectWithString:jsonString];
NSArray* array = (NSArray*) response;
NSEnumerator* e = [array objectEnumerator];
NSDictionary* dictionary;
while ((dictionary = (NSDictionary*)[e nextObject])) {
Project* project = [[Project alloc] init];
project.user_id = [dictionary objectForKey:#"user_id"];
project.name = [dictionary objectForKey:#"name"];
project.color = [dictionary objectForKey:#"color"];
project.collapsed = [dictionary objectForKey:#"collapsed"];
project.item_order = [dictionary objectForKey:#"item_order"];
project.cache_count = [dictionary objectForKey:#"cache_count"];
project.indent = [dictionary objectForKey:#"indent"];
project.project_id = [dictionary objectForKey:#"project_id"];
[elementArray addObject:project];
[project release];
}
}
However, the code stops at the project.user_id = [dictionary objectForKey:#"user_id"]; line with an exception "* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Project setUser_id:]: unrecognized selector sent to instance 0x590bcb0'"
I don't know why this is happening or how to resolve this.
I've set up a reality distortion field so I don't violate my NDA. And now I can answer your question, it has nothing to do with the product-that-must-not-be-named anyway.
There is your bug: Project* project = [[Project alloc] init];
The #dynamic setters and getters are not created for you if you create your object this way.
You can't use NSManagedObjects without a NSManagedObjectContext.
You should use something like this:
Project *project = (Project *)[NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
Property names with underscores are not very sensible in the Objective C world - I guess the properties generated by Core Data have the wrong names therefore. Try using CamelCase, that is calling your properties userID, itemOrder, cacheCount etc.
You may need to set up your getters and setters.
It could be as simple as adding:
#synthesize user_id;
In your class file.