CoreData: How to properly set created entity attributes - ios

I have a Core Data Entity, let's call it "Record", and I have several attributes that I need to set with data from other objects/entities (as well as setting up relationships).
Let's say "Record" has the following attributes:
#interface Record (CoreDataProperties)
+ (NSFetchRequest<Record *> *)fetchRequest;
#property (nullable, nonatomic, copy) NSNumber *superRecordID;
#property (nullable, nonatomic, copy) NSNumber *otherDataID;
#property (nullable, nonatomic, copy) NSNumber *active;
#property (nullable, nonatomic, copy) NSDate *createDate;
#property (nullable, nonatomic, copy) NSDate *updateDate;
#property (nullable, nonatomic, copy) NSString *uuid;
#property (nullable, nonatomic, retain) SuperRecord *superRecord;
#end
I know for things such as active, I can set the default to 1 from XCode and for things such as createDate, updateDate and uuid, I can override the awakeFromInsert method.
My question is: what is the best practice for setting the other values and relationships upon creation? The only way I can think of that I know should work is to create the instance THEN set all the attributes, but is there a better way I can do this? Where I pass in the additional values/objects are parameters and set the attributes/relationships on creation?

Simply write a convenience allocator like this:
- (instancetype)initWithSuperRecord:(SuperRecord*)superRecord active:(BOOL)active … context:(NSManagdObjectContext*)context
{
self = [super initWithEntity:… /* in subclasses you typically know that */
insertIntoManagedObjectContext:context];
if( self )
{
// Set the properties you get from the args
}
return self;
}
+ (instancetype)newWithSuperRecord:(SuperRecord*)superRecord active:(BOOL)active … context:(NSManagdObjectContext*)context
{
return [[self alloc] initWithSuperRecord:superRecord … context:context];
}
Some suggestions: The superID seems to be a property of the super record. Don't store it twice. Moreover you should check, whether you need an ID at all. This is not OOPish. Boolean values should by typed to boolean values. Use YES and NO or #YES and #NO instead of 1 and 0.
Typed in Safari.

Related

How do I iterate through NSSet<NSManagedObject *> in objective C?

This is my class... I am populating it fine when loading the XML via web service, however when I'm passing the object to another controller I can't get the list of purchase order items into an array I can put into a table.
#interface PORecord (CoreDataProperties)
+ (NSFetchRequest<PORecord *> *)fetchRequest;
#property (nonatomic) int64_t poActivityId;
#property (nullable, nonatomic, retain) NSSet<NSManagedObject *> *receivedRefReasons;
#property (nullable, nonatomic, retain) NSSet<NSManagedObject *> *purchaseOrderItems;
#end

Adding an object to NSMutableArray - weird behaviour

I am facing a very weird issue. I create an object and I add it to a NSMutableArray but when I try to read it after I insert it, some subclasses of the object change to some weird classes like
PINGIFAnimatedImageManager
Here is the code I use to create the object and insert it to the NSMutableArray:
CustomContentGridRow *row = [[CustomContentGridRow alloc]init];
row.child1 = [dataManager getMapLocation]; // This is the MapLocation object that will change to this weird PINGIFAnimatedImageManager
row.useFullWidth=1;
row.index=0;
[arrCustomChilds addObject:row];
This is the CustomContentGridRow class:
#interface CustomContentGridRow : NSObject
#property (nonatomic, assign) MapLocation *child1;
#property (nonatomic, assign) MapLocation *child2;
#property (nonatomic, assign) int useFullWidth;
#property (nonatomic, assign) int index;
#end
So when I put a breakpoint at this line [arrCustomChilds addObject:hotelRow];, when I read the row object I get the expected results. But when I place a breakpoint after the above code to read the arrCustomChilds the class of child1 changes to some weird classes. Also, sometimes it won't change to another class but it will give nil values.
Any idea why this is happening?
You should change property modifier from "assign" to "strong" for class objects. Otherwise undefined behaviour can happen.
In Xcode -> Product -> Scheme - edit Scheme. Check the settings of RUN mode. If it is Release change to Debug.
This will give you the correct values
Change below properties
#property (nonatomic, strong) MapLocation *child1;
#property (nonatomic, strong) MapLocation *child2;
assign to strong
Your interface should be:
#interface CustomContentGridRow : NSObject
#property (nonatomic, strong) MapLocation *child1;
#property (nonatomic, strong) MapLocation *child2;
#property (nonatomic, assign) int useFullWidth;
#property (nonatomic, assign) int index;
#end
the class objects should be strong not assign

useing JSQMessageMediaData with core data

i successfully integrated core data in my JSQ project, for my JSQMessageData i use NSManagedObject i created called CDChatMessage
#interface CDChatMessage : NSManagedObject < JSQMessageData >
#end
at my JSQMessagesViewController i use NSfetchedresultsController,
it works fine for text messages but i can't figure out how to implement media messages.
JSQMessage.h have a property that represent the Media Data
#property (copy, nonatomic, readonly) id< JSQMessageMediaData > media;
but obviously i cant assassin property of type JSQMessageMediaData to my NSManagedObject,
anyone have a solution for using JSQMessageMediaData with Core Data ?
thanks.
Basically what I've done to solve this kind of issue is this:
Instead of using CoreData object which conforms to JSQMessageData I use something called viewModel.
A ViewModel is basically a normal NSObject which just unwraps all necessary information from the CoreData object and conforms to JSQMessageData protocol - providing text, senderId, and other information (and also media message if necessary)
#interface ChatMessageViewModel : NSObject <JSQMessageData>
#property (nonatomic, strong, readonly) CDChatMessage *chatMessage;
// main properties
#property (nonatomic, copy, readonly) NSString *text;
#property (nonatomic, copy, readonly) NSString *senderId;
#property (nonatomic, copy, readonly) NSString *watcherId;
...
#property (nonatomic, strong, readonly) JSQMessage *mediaMessage;
- (instancetype)initWithChatMessage:(CDChatMessage *)chatMessage;
#end
.m file could look like this:
#interface ChatMessageViewModel ()
#property (nonatomic, strong, readwrite) CDChatMessage *chatMessage;
// main properties
#property (nonatomic, copy, readwrite) NSString *text;
#property (nonatomic, copy, readwrite) NSString *senderId;
#property (nonatomic, copy, readwrite) NSString *watcherId;
...
#property (nonatomic, strong, readwrite) JSQMessage *mediaMessage;
#end
#implementation ChatMessageViewModel
- (instancetype)initWithChatMessage:(CDChatMessage *)chatMessage
if (self = [super init]) {
_chatMessage = chatMessage;
[self unpackViewModel];
}
return self;
}
- (void)unpackViewModel {
self.senderId = self.chatMessage.senderId;
self.text = self.chatMessage.senderId;
self.mediaMessage = [self unpackMediaData];
}
- (JSQMessage *)unpackMediaData {
// Here CDCustomPhotoMediaItem is a subclass of JSQPhotoMediaItem which just lets me create custom look of JSQ media item.
JSQPhotoMediaItem *photoItem = [[CDCustomPhotoMediaItem alloc] init];
return [JSQMessage messageWithSenderId:self.senderId displayName:#"" media:photoItem];
}
After I fetch data using NSFetchResultsController I just take all core data objects and turn them into immutable viewModels.
Then in cellForItemAtIndexPath I just call this:
cell.mediaView = [viewModel.media mediaView];
This approach creates nice immutable wrapper which contains only necessary chunk of information needed by the JSQ chat library. Also, you can easily write tests for such object. If you're using swift, you can use struct for this kind of purpose.
Hope my answer helps. Please ask if you need more detailed answer. ;-)

use of undeclared identifier core data

I'm using core data in Xcode 7 beta 6 and I just generated categories and managed object subclasses for each of my entities. The issue is that when I try to utilize the properties created from the attributes in my model, I get a "use of undeclared identifier" error. I was under the impression that I was supposed to put custom behavior in the managed object subclass that were generated, however I was not clear on how I could use the properties from the categories in the managed object subclass, so I placed the custom behavior in the categories as shown below. I feel like I'm merely missing an import statement, but I'm not sure. I understand I'm using beta software.
Core Data Model:
Thought+CoreDataProperties.h:
#import "Thought.h"
NS_ASSUME_NONNULL_BEGIN
#interface Thought (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *objectId;
#property (nullable, nonatomic, retain) id recordId;
#property (nullable, nonatomic, retain) Collection *parentCollection;
#property (nullable, nonatomic, retain) NSNumber *placement;
#property (nullable, nonatomic, retain) NSString *text;
#property (nullable, nonatomic, retain) NSString *extraText; // allows for extra description text to be set. Should be in smaller print than headline text and should only appear as an option in text != nil
#property (nullable, nonatomic, retain) NSSet<Photo *> *photos;
#property (nullable, nonatomic, retain) id location; // place a CLLocation here
#property (nullable, nonatomic, retain) id tags; // place an NSArray here
#property (nullable, nonatomic, retain) NSDate *creationDate;
#pragma mark - Initializers
/*!
#abstract this method converts a CKRecord into a Thought object
#discussion parentCollection will still be nil after this method executes
*/
-(nullable instancetype) initWithRecord: (nonnull CKRecord *) record;
/*!
#abstract this method converts a CKRecord into a Thought object. photos set is not populated
*/
-(nullable instancetype)initWithRecord: (nonnull CKRecord *) record collection: (nonnull Collection *) collection;
/*!
#abstract Creates a new Thought object with generic recordId, objectId, placement, and photos array
#discussion parentCollection will still be nil after this method executes
*/
-(nullable instancetype) init;
… other methods
#end
#interface Thought (CoreDataGeneratedAccessors)
- (void)addPhotosObject:(Photo *)value;
- (void)removePhotosObject:(Photo *)value;
- (void)addPhotos:(NSSet<Photo *> *)values;
- (void)removePhotos:(NSSet<Photo *> *)values;
#end
NS_ASSUME_NONNULL_END
Thought+CoreDataProperties.m:
#import "Thought+CoreDataProperties.h"
#implementation Thought (CoreDataProperties)
#dynamic creationDate;
#dynamic extraText;
#dynamic location;
#dynamic objectId;
#dynamic placement;
#dynamic recordId;
#dynamic tags;
#dynamic text;
#dynamic parentCollection;
#dynamic photos;
-(nullable instancetype) init {
self = [super init];
if (self) {
// THIS IS WHERE I GET MANY ERROR FOR USE OF UNDECLARED IDENTIFIER
_objectId = [IdentifierCreator createId];
_recordId = [[CKRecord alloc] initWithRecordType:THOUGHT_RECORD_TYPE zoneID:[[CKRecordZone alloc] initWithZoneName:ZONE_NAME].zoneID].recordID;
_photos = [NSArray new];
_placement = [NSNumber numberWithInt:0];
_creationDate = [NSDate date];
}
return self;
}
-(instancetype) initWithRecord:(nonnull CKRecord *)record {
self = [super init];
if (self) {
_objectId = [record objectForKey:OBJECT_ID_KEY];
_recordId = [record recordID];
_text = [record objectForKey:TEXT_KEY];
_extraText = [record objectForKey:EXTRA_TEXT_KEY];
_location = [record objectForKey:LOCATION_KEY];
_photos = [NSSet new];
_tags = [record objectForKey:TAGS_KEY];
_placement = [record objectForKey:PLACEMENT_KEY];
_creationDate = record.creationDate;
}
return self;
}
-(instancetype) initWithRecord:(CKRecord *)record collection:(Collection *)collection {
self = [self initWithRecord:record];
self.parentCollection = collection;
return self;
}
Thought.h:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Frameworks.h" // includes Frameworks I'm using and some string constants
#import "ForFundamentals.h" // includes mostly string constants
#import "Photo.h"
#import "Collection.h"
#class Collection, Photo;
NS_ASSUME_NONNULL_BEGIN
#interface Thought : NSManagedObject
// I think I should put method declarations here
#end
NS_ASSUME_NONNULL_END
#import "Thought+CoreDataProperties.h"
Thought.m:
#import "Thought.h"
#import "Collection.h"
#import "Photo.h"
#implementation Thought
// I think I should put method implementations here
#end
Subclasses of NSManagedObject do their initialization in awakeFromInsert or awakeFromFetch. Don't override init or implement initWith.... You have to wait until the object is instantiated, and alive within an NSManagedObjectContext, before you can set its properties.
Don't assign collection instances to your ivars corresponding to Core Data relationships (i.e. _photos, parentCollection. Core Data will do that for you when you insert or fetch the object.
Instead of your init methods, rethink your approach. Write a method insertInManagedObjectContext:withSKRecord. That method calls -insertNewObjectForEntityForName:#"Thought" inManagedObjectContext:foo, which returns an instance of Thought. Now, with that istance, set the objecgtID, recordID, etc–but with your accessors, not by directly banging the instance variables.
So it seems that although I can't use _name = #"string" syntax to set property values, I can use method syntax, like [self setName: #"string"]. This seems very strange to me. However, the method syntax does work in both the subclass and the category so I guess problem solved… for now.
UPDATE
I didn't understand #dynamic. This post helped clear it up. I can't use _propertyName because the accessor methods are dynamically created by core data.

Use transient property on any Core Data attribute that doesn't need to be saved?

I'm trying to understand transient properties. I have this object:
#interface AddressAnnotation : NSObject <MKAnnotation>
#property (nonatomic, copy) NSString *address;
#property (nonatomic, copy) NSString *city;
#property (nonatomic, strong) NSNumber *latitude;
#property (nonatomic, strong) NSNumber *longitude;
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, strong) NSString *state;
#property (nonatomic, strong) NSString *street;
#property (nonatomic, strong) NSString *zip;
#property (nonatomic, strong) NSString *name;
I use this to show my annotation on a MKMapView. I want to save these pins in some Route entity. A Route would just let the user name the route. For my app, the only thing really important is the latitude and longitude. The other properties I can always recalculate with a reverse geocoder since I have the lat/long. To save space, I was thinking that if I want to make this object a Core Data entity, can I make all the properties that are not latitude and longitude transient properties? I read some examples where transient was used for a prperty that was calculated based on other non-transient properties. Is this a proper use of transient? Thanks.
That would be the case you can apply transient property in your example. From my perspective, you can still keep some properties in the core data to avoid redundant query for later use. For example, the address you can use reverse geocode to get the real address by longitude and latitude. But you will let user to wait for query address every time when you use reverse geocoder. I will prefer to keep the properties in the core data if that property needs a bit of calculation or wait for connection.

Resources