I have an iPad app using Core Data. In my data model I have an object called HubBrand and have generated NSManagedObects using XCode. The generated object has the following code:
Header:
#class HubModel;
#interface HubBrand : NSManagedObject
#property (nonatomic, retain) NSString * brandName;
#property (nonatomic, retain) NSSet *relModels;
#end
#interface HubBrand (CoreDataGeneratedAccessors)
- (void)addRelModelsObject:(HubModel *)value;
- (void)removeRelModelsObject:(HubModel *)value;
- (void)addRelModels:(NSSet *)values;
- (void)removeRelModels:(NSSet *)values;
#end
Implementation:
#implementation HubBrand
#dynamic brandName;
#dynamic relModels;
#end
I am trying to create an instance of the HubBrand class and populate it using the foloowing code:
HubBrand *brand = [[HubBrand alloc] init];
[brand setBrandName:[NSString stringWithFormat:#"_Custom:, %#", [_txtHubBrand text]]];
//brand.brandName = [NSString stringWithFormat:#"_Custom:, %#", [_txtHubBrand text]];
When I do so, I get the following runtime error:
-[HubBrand setBrandName:]: unrecognized selector sent to instance
When using generated managed objects, am I required to implement my own setters? Any clues as to why I am getting this error? Thanks!
You need to create an instance of a sub class of a NSManagedObject using a NSManagedObjectContext based on its NSEntityDescription:
NSManagedObjectContext *managedObjectContext; // Get this from your Core Data stack, probably in the app delegate
HubBrand *brand = [NSEntityDescription insertNewObjectForEntityForName:#"HubBrand" inManagedObjectContext:managedObjectContext];
[brand setBrandName:[NSString stringWithFormat:#"_Custom:, %#", [_txtHubBrand text]]];
See the Creating, Initializing, and Saving a Managed Object section of the doc for more info.
You can also use the sub classes initializer:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"HubBrand" inManagedObjectContext:managedObjectContext];
HubBrand *brand = [[HubBrand alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjectContext];
But its a bit more wordy!
You're not calling the designated initializer for NSManagedObject, so you're not getting valid objects. You can't create instances using init, you have to use initWithEntity:insertIntoManagedObjectContext:. It's also possible to use the constructor on NSEntityDescription called insertNewObjectForEntityForName:inManagedObjectContext:.
Related
I have an array (_websites) which returns 2 results (i can see the records using NSLog).
What I am trying to do is to display those 2 records in NSTableView that has 3 columns. I make numerous attempts to bind the content of my array with the NSArrayController, without any success.
Here is the .h file
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface CombinedViewController : NSViewController <NSTableViewDataSource>
#property (nonatomic,strong) NSManagedObjectContext *mObjContext;
#property AppDelegate *appDelegate;
#property (strong) IBOutlet NSArrayController *combinedRecordsArrayController;
#property (nonatomic,strong)NSArray *websites;
#property (weak) IBOutlet NSTableView *tableView;
#end
the .m file code:
#import "CombinedViewController.h"
#import "Website.h"
#import "Customer.h"
#import "Hosting.h"
#interface CombinedViewController ()
#end
#implementation CombinedViewController
- (void)viewDidLoad {
[super viewDidLoad];
_appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
self.mObjContext = _appDelegate.managedObjectContext;
[self getCombinedResutls];
}
-(NSArray *)getCombinedResutls {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Website" inManagedObjectContext:self.mObjContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [self.mObjContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Error:%#",error);
}
_websites = [_mObjContext executeFetchRequest:fetchRequest error:nil];
for (Website *ws in _websites) {
Customer *cust = ws.customerInfo;
Hosting *host = ws.hostingInfo;
NSLog(#"Website: %#, Customer: %#, Hosting Provider: %#",ws.websiteUrl, cust.customerName, host.hostingProvider);
}
return fetchedObjects;
}
#end
I am trying to learn how to do it both ways, using cocoa binding and programmatically, so any kind solution will be appreciated. Links with some up to date tutorial will be also very welcomed.
I just forgot to mention...I bind the NSArrayController with ContentArray in Controller Content, and then I bind the NSTableView with the NSArrayController, as well as my Table Column,but I am getting empty NSTableView...no error is shown in console whatsoever.
If you use direct iVar access-- _websites-- to write the websites property's iVar, the KVO notification that the binding depends upon never happens.
If you instead use self.websites = or more explicitly [self setWebsites: ..., then you will trigger a KVO notification to the array controller that the value of the websites property has been updated.
Instead, the array controller in the Xib is unarchived and bound to websites before viewDidLoad, so at that point, websites is nil. And subsequently, you never trigger any KVO notification about websites changing value because you explicitly avoid using the websites accessor setWebsites and instead use direct instance variable access. So the AC never knows that websites changes and the table never reflects any value for websites except nil.
In general never use the instance variable to access a property's value unless you have a very good reason to do so and fully understand why you're doing so.
I have a simple app with AppDelegate and MainController - I have passed the managedObjectContext to the MainController (I think successfully) but I receive an error when added an object to the context.
Code:
#implementation AppDelegate
-(void)applicationDidFinishLaunching:(NSNotification *) aNotification
{
// this line is wrong: MainController *controller = [[MainController alloc] init];
controller.managedObjectContext = self.managedObjectContext;
}
#interface MainController : NSObject
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#implementation MainController
-(IBAction)addItem:(id)sender {
NSManagedObject *newObject = [NSEntityDescription
insertNewObjectForEntityForName:#"Person"
inManagedObjectContext: self.managedObjectContext];
//The above line gives an error
ERROR:
+entityForName: nil is not a legal NSManagedObjectContext parameter search for entity name "Person"
If I change the code and do everything in the AppDelegate, everything works without issues.
I am not sure what is going on.
[EDIT]
I needed to create an IBOutlet from the MainController object in IB to the AppDelegate - thanks Nofel.
I think it should be like this:
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:_managedObjectContext];
[person setName:someNameTextField]; // if you want to set some properties
[_managedObjectContext save:nil]
It depends on how you create your Core-Data model and what you use in you appDelegate.
Best way is to watch some tutorial or Developer Library first.
I try to extend NSManagedObject.
Using XCode I created MyBox.m and MyBox.h (directly from the xcdatamodel file).
Then I modified these files:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface MyBox : NSManagedObject
#property (nonatomic, retain) NSDate * endDate;
#property (nonatomic, retain) NSNumber * globalId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSDate * startDate;
-(NSString *)sayHello;
#end
and
#import "MyBox.h"
#implementation MyBox
#dynamic endDate;
#dynamic globalId;
#dynamic name;
#dynamic startDate;
-(NSString *)sayHello {
return #"hello";
}
#end
I can fetch all myBoxes
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"MyBox" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSMutableArray *myBoxes = [context executeFetchRequest:fetchRequest error:&error];
but later I call
MyBox *myBox = [myBoxes objectAtIndex:indexPath.row];
[myBox sayHello];
it compiles but then I get
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSManagedObject sayHello]: unrecognized selector sent to instance 0x8e73fc0'
If I only read a value like
NSLog(#"%#", myBox.name);
it works
I found similar problems here, but no solution.
Thanks for your help.
I've just got the same issue. Solved it by changing class name to the name of my NSManagedObject subclass in myApp.xcdatamodeld -> configurations -> default -> entities -> myEntity.
Assuming you have set the class name correctly on the MyBox entity, I would guess that the app has an older version of your Core Data managed object model. Clean your build and delete the app on the simulator/device for good measure. To be 100% sure, also delete your derived data folder.
If it doesn't work after that, I'll bet that you haven't set the entity class name correctly. Print out your NSEntityDescription and make sure it is what you expect.
For Swift 5.0
This issue is present when you create the CoreData object in this way:
let object = CoreDataClass()
print(object.someProperty) // this is emit crash
I had the right class name set in xcdatamodeld, but I didn't include the class's .m file in my target. I had to click on the .m on the left sidebar, then check the correct box on the right sidebar under Target Membership.
Wrong Xcdatamodel.
I had the wrong xcdatamodel. It's a super dumb mistake but when you assume the latest model is 27 but your coworker changed it to 28 and you added your properties to model 27, it happens.You get these kind of errors and you assume it's something wrong with your Core Data model but it's simply your xcdatamodel number.
Gotta love programming =_=.
I am getting the exception:
-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'
The offending line is:
[delegate.sharedData.dictFaves setObject:#"test" forKey:#"4"];
Delegate is initialized thus in MyViewController.m:
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
This is how my delegate is defined in AppDelegate.h:
#import "CommonData.h"
...
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSString *tempFave;
CommonData *sharedData;
}
#property (strong, nonatomic) NSString *tempFave;
#property (strong, nonatomic) CommonData *sharedData;
sharedData is initialized in AppDelegate.m thus:
#import "AppDelegate.h"
...
#implementation AppDelegate
#synthesize sharedData;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
sharedData = [[CommonData alloc] init];
return YES;
}
sharedData is defined in CommonData.h:
#interface CommonData : NSObject
{
NSMutableDictionary *dictAffirms;
NSMutableDictionary *dictFaves;
}
#property (nonatomic, copy) NSMutableDictionary *dictAffirms;
#property (nonatomic, copy) NSMutableDictionary *dictFaves;
shared data implementation file CommonData.m:
#import "CommonData.h"
...
#implementation CommonData
#synthesize dictAffirms;
#synthesize dictFaves;
#end
I have declared the members of CommonData to be Mutable. Apparently that is insufficient. What else must I do in order to write to the Dictionaries inside CommonData?
I have seen this error before when trying to write to a dictionary that is filled from a plist. If you use
yourMutableDictionary = [someDataSource objectForKey:#"someKey"];
your dictionary will be immutable, even if it is declared mutable. Use instead
yourMutableDictionary = [someDataSource mutableArrayValueForKey:#"someKey"];
and your problem will go away, assuming this is in fact your problem. It might be something like:
yourMutableDictionary = [[NSDictionary alloc] init];
or
yourMutableDictionary = [NSDictionary new];
and you just are accidentally creating immutable objects, which is pretty much the same problem as above, just different.
It would be nice to see the code used to initialize the NSMutableDictionaries.
Edit: Maybe try something like this, as I'm curious as to what the results would be. Instead of using:
[delegate.sharedData.dictFaves setObject:#"test" forKey:#"4"];
try
NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithDictionary:delegate.sharedData.dictFaves];
[dict setObject:#"test" forKey:#"4"];
delegate.sharedData.dictFaves = dict;
[dict release];
You've declared the dictFaves to be mutable, but that doesn't mean you actually stored a mutable object in there. Check your initializer. You'll probably have something like the following:
dictFaves = [[NSDictionary alloc] init];
If so, you need to change that to NSMutableDictionary instead.
Your problem is that your property setter for the dictionary is declared as copy. NSMutableDictionary's copy method returns an immutable NSDictionary (in general, copy almost always returns an immutable object). So assuming you're using the standard synthesized setter, any time you set that property, you're assigning the wrong type behind the scenes. It should probably be strong instead.
Im attempting to pass an array that is created in one class into another class. I can access the data but when I run count on it, it just tells me that I have 0 items inside the array.
This is where peopleArray's data is set up, it's in a different class than the code that is provided below.
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
This is how I get the array from another class, but it always prints count = 0
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
The rest of my code:
data.h
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *peopleArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
data.m(some of it at least)
#implementation data
#synthesize viewController, managedObjectContext, peopleArray;
- (void)fetchRecords {
[self setupContext];
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
[mutableFetchResults release];
[request release];
//NSLog(#"this is an array: %#", eventArray);
}
login.h
#import <UIKit/UIKit.h>
#import "data.h"
#class rootViewController, data;
#interface login : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UIButton *loginButton;
UIButton *newUser;
UITextField *entry;
data *dataClass;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, assign) data *dataClass;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField;
#end
login.m
#import "login.h"
#import "data.h"
#interface login (PrivateMethods)
- (void)setUpFromTheStart;
- (void)loadDataScreen;
-(void)login;
#end
#implementation login
#synthesize viewController, dataClass;
-(void)login{
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
}
Is it the same object? If so, what you have should work. Check to see how you are getting the dataClass instance -- if you alloc a new one, you don't get the array from the other object.
Edit: From your comments below, it appears that you are having some confusion on the difference between classes and objects. I will try to explain (I'm going to simplify it):
A class is what you write in Xcode. It's the description that lets your application know how to create and access objects at run-time. It is used to figure out how much memory to allocate (based on instance variables) and what messages can be sent, and what code to call when they are. Classes are the blueprints for creating objects at runtime.
An object only exists at run-time. For a single class, many objects of that class can be created. Each is assigned its own memory and they are distinct from each other. If you set a property in one object, other objects don't change. When you send a message to an object, only the one you send it to receives it -- not all objects of the same class.
There are exceptions to this -- for example if you create class properties (with a + instead of a - at the beginning), then they are shared between all objects -- there is only one created in memory, and they all refer to the same one.
Also, since everything declared with a * is a pointer -- you could arrange for all pointer properties to point to the same data. The pointer itself is not shared.
Edit (based on more code): dataClass is nil, [dataClass peopleArray] is therefore nil, and then so is the count message call. You can send messages to nil, and not crash, but you don't get anything useful.
I don't see how the login object is created. When it is, you need to set its dataClass property.
Try running the code in the debugger, setting breakpoints, and looking at variables.
From the code, it looks like you are passing a mutable array.
[self setPeopleArray: mutableFetchResults];
Probably the items of the array are removed somewhere in your calling class / method. Or the array is reset by the class from which you get the mutableFetchResults in the first place.