Relating two objects in DBAccess - ios

I'm using dbaccess for my iOS project.
How can I pass an array to dbaccess object? For example:
I have dbobject like:
#interface Member : DBObject
#property (strong) NSString* firstname;
#property (strong) NSString* lastName;
#end
#interface Group : DBObject
#property (strong) NSString* groupName;
#property (strong) NSString* adminName;
#property (strong) Member* members;
#end
For this group, it has 4 member than How can I store all group members and group detail in one object and also how to retrieve them?
Thanx in adv.

To answer the question I have re-modelled and provided an example below of how you would go about creating a one-to-one relationship between these two objects.
This problem stems from the fact that there is no such thing as a typed array in Objective-c. When there is, we will look at re-implementing how the interface works.
I have moved the Group object into the member, as a member belongs to a group, and then added a members method to the Group object to look backwards to the child objects.
#interface Member : DBObject
#property (strong) NSString* firstname;
#property (strong) NSString* lastName;
#property (strong) Group* group;
#end
#interface Group : DBObject
#property (strong) NSString* groupName;
#property (strong) NSString* adminName;
- (DBResultSet*)members;
#end
And the implementations now look like this.
#implementation Member
#dynamic firstname,lastName, group;
#end
#implementation Group
#dynamic adminName, groupName;
- (DBResultSet *)members {
/* we achieve one-to-many relationships by querying the objects in the
manner a relational database would commonly be structured */
return [[[Member query] whereWithFormat:#"group = %#", self] fetch];
}
- (void)entityDidDelete {
/* when you create tight bonds, you may well wish to create 'trigger' methods
to clean data and keep the integrity in good order */
for (Member* member in [[[Member query] whereWithFormat:#"group = %#", self] fetch]) {
member.group = nil;
[member commit];
}
}
#end
Now there is nothing stopping you creating an array as a property type, and storing the Id values within it. But that is not as clean, and requires you to maintain it, whereas if you a looking for FK values, this requires no maintenance and you can create lovely logic to stop the deletion of objects if it is related to others, without having to hydrate lots of objects and then look inside arrays.
Plus you get the beautiful option of using the object dot notation to navigate the strongly typed relationships from the Person object.
NSString* admin = person.group.adminName;
Also, when you added the Group object into Member:
#property (strong) Group* group;
DBAccess automatically created an index in SQLite for the group property, and prioritises its importance within the cache, as objects which are linked this way are more likely to be accessed.
Hope this helps,
Adrian

Related

DBAccess: Get array of any one property value

I have dbobject like:
#import <Foundation/Foundation.h>
#import <DBAccess/DBAccess.h>
#interface GroupMember : DBObject
#property (strong) NSString *firstname;
#property (strong) NSString *lastname;
#property (strong) NSString *_id;
#end
How can I get an array of all group member's firstname? Thanks in adv.
Because you are not dealing with SQL but with whole objects, this requires a little bit of working round the problem, but it is possible.
NSDictionary* resultsGroupedByFirstName = [[GroupMember query] groupBy:#"firstname"];
NSArray* names = resultsGroupedByFirstName.allKeys;
This is a fairly expensive call as it is having to do a fair amount of work in the background. Although it is optimised slightly by using an index to detect changes in the column.
This should do the trick.
NOTE:
DBAccess v1.6.7 now has a distinct call thanks to your question and feedback. http://www.db-access.org/downloads

KVO or custom accessor methods?

I got few entities for an iOS App which are linked by relations. To make a simple example, lets assume we got a relation like this (one-to-many):
company <--->> person
I'm using xcode 4.6 and the core data modelling tool for model generation, so I end up with
//Company.h
#class Person;
#interface Company : NSManagedObject
#property (nonatomic, retain) NSString * company_name;
#property (nonatomic, retain) NSNumber *has_changed;
#property (nonatomic, retain) NSSet *person;
#end
#interface Company (CoreDataGeneratedAccessors)
- (void)addPersonObject:(Person *)value;
- (void)removePersonObject:(Person *)value;
- (void)addPerson:(NSSet *)values;
- (void)removePerson:(NSSet *)values;
#end
//Company.m
#import "Company.h"
#import "Person.h"
#implementation Company
#dynamic company_name;
#dynamic has_changed;
#dynamic person;
#end
And
//Person.h
#class Company;
#interface Person : NSManagedObject
#property (nonatomic, retain) NSString * first_name;
#property (nonatomic, retain) NSString * last_name;
#property (nonatomic, retain) Company *company;
#end
//Person.m
#import "Person.h"
#import "Company.h"
#implementation Person
#dynamic first_name;
#dynamic last_name;
#dynamic company;
#end
Now, suppose I want to set the boolean (implemented as NSNumber in Core Data) attribute has_changed to TRUE every time one of the following occurs:
a attribute of the company entity is changed, except the has_changed attribute itself of course (since this would cause a loop)
a attribute of the person entity is changed
What would be the best (easy to implement + fast to process) way to implement something like this? For what I was able to find out, is that there are two options:
Key value observing (KVO)
custom method accessors
However, everything I find related to this topic seems to be outdated because of the changes in objective-c 2.0, core-data / cocoa or iOS. For example, automatically generating the accessor methods by using the core-data modelling editor dosn't seem to work when using xcode 4.6 with ARC enabled, since everything I get pasted are the #dynamic lines which core-data generates anyways (see code example above).
Also I find it a bit confusing what the documentation says. What would be the best way to implement this? KVO? Custom accessors? Bit of both or even something compleatly different? And how would a possible implementation look like for the given example?
You could do this as such:
#implementation Company
// ...
- (void) didChangeValueForKey: (NSString *) key
{
[super didChangeValueForKey: key];
if (! [key isEqualToString: #"has_changed"])
self.has_changed = YES;
}
// ...
#end
and similar for Person though the company property. You'd also want to implement didChangeValueForKey:withSetMutation:usingObjects:
Note that this suggestion does not address the common need of having a controller know about a change. There are other ways to do that.

Add #propertys to Core Data class

I made few classes via Core Data. And I need some additional #propertys for one of that classes in runtime. This #propertys are responsible for download progress and I don't want to store them in Core Data DB. I tried to use a separate extension class:
#interface MyClass ()
{
CGFloat _downloadProgress;
NSInteger _downloadErrorCounter;
BOOL _downloadAllStuff;
BOOL _downloadUserCanceled;
}
#property (nonatomic, assign) CGFloat downloadProgress;
#property (nonatomic, assign) NSInteger downloadErrorCounter;
#property (nonatomic, assign) BOOL downloadAllStuff;
#property (nonatomic, assign) BOOL downloadUserCanceled;
#end
But private variables are not visible out of MyClass, and #propertys compile all right, but in runtime i get -[MyClass setDownloadErrorCounter:]: unrecognized selector sent to instance.
Can anyone suggest me some solution?
The easiest solution (if you don't want to modify the Xcode generated class files) is to add the properties to the Core Data model and define the
properties as transient. Transient properties are not saved to the store file.
Another option is to use a tool like "mogenerator", which generates two class files for each
entity, one for the Core Data properties (which is overwritten if the the model changes),
and one for your custom properties (which is not overwritten).
Update: Starting with Xcode 7, Xcode creates both a class and
a category for each managed object subclass, compare NSManagedObject subclass property in category. Custom properties can be added to the class
definition which is not overwritten when the model changes.
Just add
#synthesize downloadErrorCounter = _downloadErrorCounter;
...
in #implementation. Note, not #dynamic.
When trying to use the #synthesize solution i got the error:
#synthesize not allowed in a category's implementation.
Solution was to use associated objects as described in this blog: http://kaspermunck.github.io/2012/11/adding-properties-to-objective-c-categories/
MyManagedObject+Additions.h
#property (strong, nonatomic) NSString *test;
MyManagedObject+Additions.m
NSString const *key = #"my.very.unique.key";
- (void)setTest:(NSString *)test
{
objc_setAssociatedObject(self, &key, test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)test
{
return objc_getAssociatedObject(self, &key);
}

Subclass of custom class that needs to subclass NSManagedObject

So here's my dilemma. I'm dealing with legacy code and trying to simplify and reduce a huge amount of redundancy in the code base. Here's the crux of the matter. I'm trying to consolidate two very similar classes into a superclass/subclass relationship. One is a subclass of NSObject the other a subclass of NSManagedObject.
I have a class that contains only variables called InventoryItems.h. It is a subclass of NSObject.
#property (nonatomic, retain) NSString * desc;
#property (nonatomic, retain) NSString * locationInventoryId;
...
InventoryItems.m
#synthesize desc;
#synthesize locationInventoryId;
...
There is another class that is called FavoriteInventoryItems.h that is a subclass of NSManagedObject.
It contains exactly the same variables as InventoryItems with one additional variable.
FavoriteInventoryItems.h
#property (nonatomic, strong) NSString * desc;
#property (nonatomic, strong) NSString * locationInventoryId;
#property (nonatomic, strong) NSString * dateAddedAsFav;
....
FavoriteInventoryItems.m
#dynamic desc;
#dynamic locationInventoryId;
#dynamic dateAddedAsFav;
...
I can successfully make things work by making InventoryItems a subclass of NSManagedObject and then making FavoriteInventoryItems a subclass of InventoryItems. It does work but I get a message indicating the following:
CoreData: error: Failed to call designated initializer on NSManagedObject class 'InventoryItems'
My solution assuredly is a hack that may have negative consequences.
There are multiple places where the code resembles something like the following:
if (InventoryItem)
...many lines of code here
else if(FavoriteInventoryItem)
...exact same code as above based on favorites
I'm not sure how else to consolidate both of these class into superclass/subclass relationship. Or is there a better way to handle this problem that doesn't involve inheritance? Any ideas?
Try to use a protocol to specify what is common between the classes and allow the 'using' code to be generic to the protocol.
The specification of a protocol is the important part, the implementation already exists in the 2 classes you have. The specification would list the common methods (or properties) between the 2 classes. Then, in the duplicated code, instead of saying:
InventoryItem *item = ...
or
FavoriteInventoryItem *item = ...
You would say:
id < InventoryItem > item = ...
I'm duplicating names because I can't know what a better name is, but the protocol is defined as:
#protocol InventoryItem < NSObject >
#property (nonatomic, strong) NSString * desc;
#property (nonatomic, strong) NSString * locationInventoryId;
#end
Then the code using the protocol doesn't care about what the underlying class is, it just cares what the protocol offers:
item.desc = #"Teddies";
item.locationInventoryId = ...

Conditionally make a #property strong or weak

I am downloading a list of objects from an API to display to a user. The list has a mix of two types of objects. Imagine that they are combined books and authors, and the class definitions look like this:
#interface Book : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) Author *author;
#end
#interface Author : NSObject
#property (nonatomic, strong) NSString *fullName;
#property (nonatomic, weak) Book *book;
#end
Every Book can download its Author information from the API, and vice versa.
If the API gives me a Book, I can set its author property once I download it. The Author object points back to the Book through the book property, but this doesn't create an ARC Retain Cycle because the book property is weak.
However, if the API gives me an Author first, and I download its Book, the object will be deallocated once the method in which I set it returns, because the same property is weak.
I thought of a few ways around this:
Create a Content object that stores both (not viable for many-to-many relationships)
Create separate strongBook and weakBook properties, and then make a readonly property called book which checks which is set and returns that one
Those both seem messy to me, although the second option is preferable.
Is there a way to dynamically change a property from weak to strong (and vice-versa) using the Objective-C runtime?
UPDATE: I'm getting a few suggestions on how to work around the issue, which I don't have trouble coming up with myself. This question is specifically about whether there is a way to either (a) dynamically redefine #properties for a specific instance of a class, or (b) override ARC's retain/release behavior in specific circumstances (since this issue wouldn't exist in MRC).
Just a shot in the dark, but you could create the property and not specify and then use dynamic with the runtime apis. I didn't test it, but i think it should work:
//.h file
#import <Foundation/Foundation.h>
#interface SomeObject : NSObject
#property(nonatomic) NSObject *object;
#end
//.m file
#import "SomeObject.h"
#import <objc/runtime.h>
#implementation SomeObject
#dynamic object;
-(void)setObject:(NSObject *)object
{
BOOL isWeak = NO;
if(isWeak)
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
}
else
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_RETAIN);
}
}
-(NSObject *)object
{
return objc_getAssociatedObject(self, "object");
}
#end
For the period of the download, create a mutable dictionary to temporarily store author objects that arrive prior to the book. When a book is received, look in that array and see if the author info is there, if so attach it. When you are finished clean out the mutable array.

Resources