I have the following problem:
As our app is going to be shown to potential customers it has to run offline in first version (due to dependencies to a backend that is not finished right now) and therefore all the data shown in the app will be retrieved by CoreData from a sqlite db.
As we want to avoid later refactoring we decided to put the CoreData entities behind protocols. A service will take care of all the data retrieval and hide the entities with the corresponding protocols.
In a later version of the app the UI developers will just switch the service to the backend one's and do not have to alter the rest of their code due to sticking to the protocols. For UI developer it does not make a difference if the entities will be NSManagedObjects or just NSObjects.
And now comes the problem.
We have declared the protocols (set- and get-Methods) for all the entities in our app and generated CoreData entities that fit to those protocols. CoreData is using #dynamic for all the set/get-Methods (that will we be generated during runtime from the CoreData framework).
All the NSManagedObjects should now implement their corresponding protocols, but the compiler is giving out warnings (because of #dynamic) that the CoreData-Object is not implementing the protocol.
I just want to give you ONE entity and the corresponding protocol to explain my problem in detail:
TaskCD.h
#interface TaskCD : NSManagedObject<Task> {
#private
}
#property (nonatomic, retain) NSString * category;
#property (nonatomic, retain) NSNumber * frequency;
#property (nonatomic, retain) NSDate * validityEnd;
#property (nonatomic, retain) NSDate * validityStart;
#property (nonatomic, retain) NSNumber * periodicity;
#property (nonatomic, retain) NSString * descr;
#property (nonatomic, retain) NSNumber * selected;
#property (nonatomic, retain) NSSet* measurements;
#end
TaskCD.m
#import "TaskCD.h"
#import "MeasurementCD.h"
#implementation TaskCD
#dynamic category;
#dynamic frequency;
#dynamic validityEnd;
#dynamic validityStart;
#dynamic periodicity;
#dynamic descr;
#dynamic selected;
#dynamic measurements;
.... CoreData One-To-Many-stuff ....
#end
Task.h
#protocol Task <NSObject>
- (NSString*) getCategory;
- (void) setCategory:(NSString*) category;
- (NSNumber*) getFrequency;
- (void) setFrequency:(NSNumber*) frequency;
- (void) setValidityEnd:(NSDate *) date;
- (NSDate *) getValidityEnd;
- (void) setValidityStart:(NSDate *) date;
- (NSDate *) getValidityStart;
- (void) setPeriodicity:(NSNumber *) number;
- (NSNumber *) getPeriodicity;
- (void) setDescr:(NSString *) descr;
- (NSString *) getDescr;
- (void) setSelected:(NSNumber *) selected;
- (NSNumber *) getSelected;
- (void) setMeasurements:(NSSet*) measurements;
- (NSSet *) getMeasurements;
#end
I am not that experienced with ObjC and I am coming from Java Development. Maybe it is a design failure and not an ObjC issue. What we want to definitely stick with, is that in the productive version with the real backend the UI developer should NOT work with NSManageObject classes to keep away the CoreData stuff. He just sees one facade giving him an API to interact with the layers behind (First CoreData. Later REST backend). The UI developer should only see plain VOs or stick to protocols (interfaces).
I just want to know how to avoid those warnings. All proposals are welcome. ;)
Thanks in advance guys!!
One thing I can suggest off the bat: the accessors defined in Task.h should match the names of the #property declarations in the TaskCD interface. Instead of:
- (NSString*) getCategory;
you should declare it as:
// this is the more common Obj-C naming convention
- (NSString*) category;
Alternately, specify the name of the "get" accessor in the #property declaration (in TaskCD.h):
//meh... not so nice
#property (nonatomic, retain, getter=getCategory) NSString * category;
Also IMHO, with Core Data you're much better off using the dynamically generated accessors, rather than implementing your own.
First, I want to say that I agree with octy in that the best solution would probably be just to change your protocol to use more standard Objective-C accessor names.
However, I also wanted to throw out the option that you could create a category for each of your generated CoreData objects that essentially maps their accessor methods to the accessor names you want:
#interface TaskCD (Accessors) <Task>
...
#implementation TaskCD (Accessors)
- (NSString *) getCategory {
return [self category];
}
...
This way, you can keep the generated files (and regenerate them however often you like) and still use your own accessor method names.
That being said, I still think it would be better to just stick with the Objective-C default accessor names.
there is another approach you could follow: keep core data even in your later REST version and simply update your local core data storage with the live data respectively use the NSManagedObjects. using core data in UI stuff is actually a good idea, because the you get a lot of benefits (e.g. feeding tableviews which successively fetch rows from core data when scrolling)
Related
I have a simple core data Object that will give me an EXC_BAD_ACCESS whenever I try to NSLog its contents in Xcode.
The managed object looks like this:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class ChatFriend, ChatMessage;
#interface ChatThread : NSManagedObject
#property (nonatomic, retain) NSString * threadId;
#property (nonatomic, retain) NSDate * timestamp;
#property (nonatomic, retain) NSSet *friends;
#property (nonatomic, retain) NSSet *messages;
#end
#interface ChatThread (CoreDataGeneratedAccessors)
- (void)addFriendsObject:(ChatFriend *)value;
- (void)removeFriendsObject:(ChatFriend *)value;
- (void)addFriends:(NSSet *)values;
- (void)removeFriends:(NSSet *)values;
- (void)addMessagesObject:(ChatMessage *)value;
- (void)removeMessagesObject:(ChatMessage *)value;
- (void)addMessages:(NSSet *)values;
- (void)removeMessages:(NSSet *)values;
#end
This is the .m file:
#import "ChatThread.h"
#import "ChatFriend.h"
#import "ChatMessage.h"
#implementation ChatThread
#dynamic threadId;
#dynamic timestamp;
#dynamic friends;
#dynamic messages;
- (NSString*) description
{
/* This can print out the contents of self.messages correctly
for(ChatMessage *message in self.messages)
{
Log(#"ChatMessage - %#", message);
}
*/
// using %# to print out self.messages directly, will fail:
return [NSString stringWithFormat:#"ChatThread - threadId:%#, messages count:%d, messages:%#", self.threadId, self.messages.count, self.messages];
}
#end
I noticed that if I attempt to print out the contents of self.messages in the stringWithFormat line, EXC_BAD_ACCESS will happen. If I remove self.messages, only print out the self.messages.count, it works. If I manually do a for loop to print out each message in the messages NSSet, it works and it proves that there are some values there.
So, why does printing self.messages directly will trigger EXC_BAD_ACCESS?
The documentation is extremely explicit when it comes to overriding description in an NSManagedObject:
You can safely invoke the following methods on a fault without causing it to fire: isEqual:, hash, superclass, class, self, isProxy, isKindOfClass:, isMemberOfClass:, conformsToProtocol:, respondsToSelector:, description, managedObjectContext, entity, objectID, isInserted, isUpdated, isDeleted, faultingState, and isFault. Since isEqual and hash do not cause a fault to fire, managed objects can typically be placed in collections without firing a fault. Note, however, that invoking key-value coding methods on the collection object might in turn result in an invocation of valueForKey: on a managed object, which would fire the fault.
Although the description method does not cause a fault to fire, if you implement a custom description method that accesses the object’s persistent properties, this will cause a fault to fire. You are strongly discouraged from overriding description in this way.
You appear to have violated the warning in that last paragraph. Make your NSManagedObjectContext not return objects as faults, or use valueForKey: to fire the fault explicitly and load the resultant property before trying to to access it's description. Faulted objects that fire themselves result in undefined behavior.
I am getting the following error:
"-[Order items]: unrecognized selector sent to instance 0x6b5f240"
I do have a class called Order, which looks like this:
Order.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class OrderItem;
#interface Order : NSManagedObject {
#private
}
#property (nonatomic, retain) NSNumber * orderID;
#property (nonatomic, retain) NSDate * date;
#property (nonatomic, retain) NSNumber * orderCode;
#property (nonatomic, retain) NSSet* items;
#end
Order.m
#import "Order.h"
#import "OrderItem.h"
#implementation Order
#dynamic orderID;
#dynamic date;
#dynamic orderCode;
#dynamic items;
...
It doesn't extend any sort of class which has an "items" method, if I'm reading that correctly?
Is there any other reason I would be getting such an error. To add to the madness, this project is copied directly from a previous project, with some minor edits. I've done text comparisons on every single class in both projects and there are no differences other than the cosmetic changes I've made.
#dynamic items tells the compiler that you will be providing the methods for items.
Since this was working in a previous project, it must have had the following method somewhere in the .m file:
- (NSSet *)items {
// Appropriate code
}
If you do not want to provide your own custom getter like this, then change #dynamic items to #synthesize items and the compiler will generate one for you.
For more details, see the Declared Properties section of The Objective-C Programming Language provided by Apple here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
EDIT
While everything above still applies to a normal object (and may still apply here), I just noticed that this is a subclass of NSManagedObject.
In your old data model there was probably a relationship called items and therefore the appropriate methods were provided by NSManagedObject and #dynamic was appropriate to prevent compiler warnings.
If in your new data model there is no relationship named items, then the methods will not be generated and it will cause the problem that you are getting here.
Here are 2 methods to assign property in Objective-C :
METHOD 1
// in header
#interface Book : NSObject {
NSString *_title;
}
#property (nonatomic, retain) NSString *title;
// in implementation
#implementation Book
#synthesize title = _title;
METHOD 2
// in header
#interface Book : NSObject {
NSString *title;
}
#property (nonatomic, retain) NSString *title;
// in implementation
#implementation Book
#synthesize title;
What are the difference? I use Method 1 recently, as more tutorials recommend Method 1, but nobody explains why.
The difference is the names. In #2 the property and instance field have the same name. In #1 they have different names.
The advantage to #1 is that it's difficult to accidentally reference the property when you mean the instance field or vice-versa. Referencing the wrong one can lead to having a object retained twice or not retained at all.
The advantage to #2 is that it's marginally simpler, and it works fine if you're careful and a bit formal in your use of things.
[And, I see, one flavor specifies assign and the other retain, which is a whole different lecture. You'd not normally use assign with an object pointer.]
Firstly, I recommend you use copy instead of retain(and assign) for NSString type of instance. If it's Mutable, then it gets copied; If not, then it just gets retained.
Maybe you'll like THIS DISCUSSION.
And for you question, the difference is that first one use the same name and the second one use the different name for iVar & property.
Actually, you have a METHOD 3 to use:
// in header
#interface Book : NSObject {
}
#property (nonatomic, copy) NSString *title;
// in implementation
#implementation Book
#synthesize title;
For #synthesize to work in the legacy runtime, you must either provide an instance variable with the same name and compatible type of the property or specify another existing instance variable in the #synthesize statement. With the modern runtime, if you do not provide an instance variable, the compiler adds one for you. For example, given the following class declaration and implementation.
Here is a sample code of official doc, you can make it clear( it includes difference between your METHOD 1 & METHOD 2):
#interface MyClass : NSObject {
float sameName;
float otherName;
}
#property float sameName;
#property float differentName;
#property float noDeclaredIvar;
#end
#implementation MyClass
#synthesize sameName;
#synthesize differentName=otherName;
#synthesize noDeclaredIvar;
#end
The compiler for the legacy runtime would generate an error at #synthesize noDeclaredIvar; whereas the compiler for the modern runtime would add an instance variable to represent noDeclaredIvar.
Note: iPhone applications and 64-bit programs on Mac OS X v10.5 and later use the modern version of the runtime. Other programs (32-bit programs on Mac OS X desktop) use the legacy version of the runtime. You can refer it HERE).
However, I suggest to use METHOD 1 or METHOD 3. As you can just use self.title in code, the property will help you manage the alloc & release. If you use METHOD 2, you may mix title with self.title(but _title is more clear, uh?). :)
What is the #private for in the file generated by Core Data below? I know what #private means in Objective-C, but there are not instance variables listed after it, so can't I just take it out?
//
// Event.h
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Event : NSManagedObject {
#private
}
#property (nonatomic, retain) NSDate * timestamp;
#end
//
// Event.m
//
#import "Event.h"
#implementation Event
#dynamic id;
#end
You can safely take it out, it won't change the semantics of your class. If you're not statisfied with what XCode generates for you (though it's a reasonable default), I'd suggest you take a look at https://github.com/rentzsch/mogenerator.
You can, but it doesn't hurt. If you generate the model again it will just put it back.
Xcode now defaults to generating classes with #private for instance variables, which you are supposed to declare in case you need them. You can safely remove that #private since, as you’ve already noticed, there are no instance variables. In fact, that class declaration is equivalent to
#interface Event : NSManagedObject
#property (nonatomic, retain) NSDate * timestamp;
#end
I have an application written using Core Data. I have 2 entities with a one-to-many relationship. I have subclassed NSManagedObject for both of them. The entity on the one-side is called Playlist and the other is called Song.
The interface for Playlist:
#interface VBPlaylist : NSManagedObject {
}
#property (readwrite, copy) NSString *name;
#end
The implementation for Playlist:
#implementation VBPlaylist
#dynamic name;
#end
I think that I should have another property to indicate the Songs under the Playlist class. I cannot find any sample code that shows to-many relationships written as properties. How do you do this?
To-one relationships are modeled as object references by Core Data. So a to-one relationship from Entity Bar to entity Baz (assuming Baz is implemented by the class Baz) would be
#interface Bar : NSManagedObject {
}
#property (readwrite,nonatomic) Baz * toBaz;
#end
To-many relationships are modeled as a mutable set property (though not as an NSMutableSet). Assuming a to-many relationship from Bar to Baz called manyBazz:
#interface Bar : NSManagedObject {
}
#property (retain) NSSet* manyBazz;
#end
// coalesce these into one #interface AnalysisRecord (CoreDataGeneratedAccessors) section
#interface Bar (CoreDataGeneratedAccessors)
- (void)addManyBazzObject:(Baz *)value;
- (void)removeManyBazzObject:(Baz *)value;
- (void)addManyBazz:(NSSet *)value;
- (void)removeManyBazz:(NSSet *)value;
#end
If you want to use the NSMutableSet interface to manipulate the manyBazz relationship, you should call -mutableSetValueForKey:#"manyBazz" to get a KVO-compliant proxy for the manyBazz relationship.
On Leopard (OS X 10.5) and later, all appropriate methods are automaticall generated at run-time by the Core Data framework, even if you do not explicitly declare or implement them (you will, of course, get a compiler warning if you try to use them without declaring them in a header file). Thus you do not need to subclass
The easiest way to get the declaration and implementation right is to select the attributes in the data modeler and choose "Copy Objective-C 2.0 Method Declarations To Clipboard" from the "Design->Data Model" menu, the paste into your implementing classes .h file. Of course, you have to keep your .h and model in sync... hence a hearty recommendation for rentzsch's awesome MO Generator, a tool that will automatically generate (and re-generate) NSManagedObject subclasses from your data model.
The simplest way to create .h and .m files for your CoreData entities is this:
Select an entity in the data modeler.
Press Command-N or select File->New File…
Select 'Cocoa' from the source list.
In the template chooser, you should now see an item called 'Managed Object Class'. If this isn't there, click Cancel and repeat steps 1-2.
Press Next, choose the project/target, and press Next again.
Now you see something like the following window: New Managed Object Class window http://blog.alanquatermain.net/images/ManagedObjectClass.png
Select the options you need and click Finish.
This will generate the following header and source files:
Entity.h:
#import <CoreData/CoreData.h>
#interface Entity : NSManagedObject
{
}
#property (retain) NSNumber * uniqueID;
#property (retain) NSString * name;
#property (retain) Entity * parent;
#property (retain) NSSet* children;
#end
#interface Entity (CoreDataGeneratedAccessors)
- (void)addChildrenObject:(Entity *)value;
- (void)removeChildrenObject:(Entity *)value;
- (void)addChildren:(NSSet *)value;
- (void)removeChildren:(NSSet *)value;
#end
Entity.m:
#import "Entity.h"
#implementation Entity
#dynamic uniqueID;
#dynamic name;
#dynamic parent;
#dynamic children;
#end
Note that the implementation doesn't contain anything except #dynamic markers to tell the compiler not to worry about missing methods/variables to match the properties.
This has slightly changed. As of 4.2.1 (March 2012), you go to New File; select Core Data (not Cocoa), and then select New NSObjectModel subclass. From there it produces the subclass as described above.