I have a class that contains multiple user objects and as such has an array of them as an instance variable:
NSMutableArray *users;
The tricky part is setting it. I am deserializing these objects from a server via Objective Resource, and for backend reasons users can only be returned as a long string of UIDs - what I have locally is a separate dictionary of users keyed to UIDs. Given the string uidString of comma separated UIDs I override the default setter and populate the actual user objects:
#dynamic users;
- (void)setUsers:(id)uidString {
users = [NSMutableArray arrayWithArray:
[[User allUsersDictionary] objectsForKeys:[(NSString*)uidString componentsSeparatedByString:#","]]];
}
The problem is this: I now serialize these to database using SQLitePO, which stores these as the array of user objects, not the original string. So when I retrieve it from database the setter mistakenly treats this array of user objects as a string! Where I actually want to adjust the setter's behavior when it gets this object from DB vs. over the network.
I can't just make the getter serialize back into a string without tearing up large code that reference this array of user objects, and I tried to detect in the setter whether I have a string or an array coming in:
if ([uidString respondsToSelector:#selector(addObject)]) {
// Already an array, so don't do anything - just assign users = uidString
but no success... so I'm kind of stuck - any suggestions? Thanks in advance!
The solution you've tried is not exactly wrong, but should look like this:
if ([uidString respondsToSelector:#selector(addObject:)]) {
// Already an array, so don't do anything - just assign users = uidString
Seems that you've forggoten about the ":" - the addObject takes one parameter after all.
However, the proper way to do is to check the class of passed object:
if ([uidString isKindOfClass[NSArray class]])
Hope this was helpful,
Cheers, Pawel
It would be much less confusing - and more in line with the usual Cocoa style - if you let the actual setter take an NSArray or an NSMutableArray:
- (void)setUsers:(NSArray*)usersArray { ... }
... and have another method, say -setUsersFromUidString:, taking an NSString parameter, for the first case:
- (void)setUsersFromUidString:(NSString*)uidString { ... }
Your original approach loses the advantage of self-documentation Objective-C has and will confuse users of the class.
Related
Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}
I know there are a lot of posts out there concerning the problem of how to archive custom objects in an NSArray or NSMutableArray and save them in NSUserDefaults. Conforming to the NSCoding Protocol and saving to NSUserDefaults isn't problematic and I use NSUserDefaults quite a lot to store the user-submitted data in my app - it mostly contains objects representing a Person (let's call the NSObject subclass "Person") which can have multiple objects of the NSObject subclass "Property" stored in an NSMutableArray. Therefore, the data structure looks like this:
NSMutableArray "persons":
Person "aPerson":
NSMutableArray "properties":
Property "aProperty"
Property "anotherProperty"
Person "anotherPerson:
...
Archiving and restoring the information was not problematic at first, because both Person and Property conform to the NSCoding Protocol - but now a problem occured which I was not able to solve yet despite those thousands of google requests in the last couple days ;)
Some of the Property objects contain references to other Persons ("Participants", which are linked to the same property and are contained in an NSMutableArray).
When I store the whole data to NSUserDefaults using NSKeyedArchiver, I use
[aCoder encodeObject:participants forKey:#"participants"];
in the Property's "encodeWithCoder" method to archive the NSMutableArray "participants" which stores the references to other Person objects. But when I decode those Person objects, they are created new and separated from the Person objects that already exist somewhere else. The NSMutableArray "participants" only contains references, weak links to the Person objects and should therefore conditional encode its content, as one can do with other objects manually in "encodeWithCoder":
[aCoder encodeConditionalObject:anObject forKey:aKey];
When the NSMutableArray gets decoded, it should represent a list of references to already existing Person objects - not completely new ones! The test "aPerson==[[aDecoder decodeObjectForKey:#"participants"] objectAtIndex:0]" is currently returning NO although it had returned YES before the encoding/decoding process has taken place.
I hope my explanation is somehow understandable and you can help me with my problem :) In simple words: How can I conditional encode custom objects contained in an NSMutableArray?
Thank You !
If NSMutableArray would use encodeConditionalObject:forKey: for the objects it contains, it would just mean that those objects aren't encoded at all, if they're not encoded unconditionally somewhere else in your object graph. This wouldn't help you in this case (the array would just be empty).
The problem is that you cannot really encode references to objects in memory. An object reference is basically just a pointer to an address in memory. When you start your app the next time and create the very same object (whether by unarchiving or otherwise), it will almost definitely have a different address in memory. There is no way the unarchiver can 'magically' know, which existing object corresponds to the reference it has archived, because the memory address (the object's 'identity') loses all its meaning when you quit your app.
You have to use other means of identifying your objects, such as database row IDs, dictionary keys, etc. and establish the connection of the archived key and the existing object corresponding to that key manually.
I had an issue with this too. I have objects that have an array of weak links to other objects. I know all the objects linked to will be encoded, so I just want to make sure I can rebuild the links.
With a single weak link is it possible to use:
aCoder.encodeConditionalObject(thing, forKey: "Thing")
...and if that item has already been encoded from elsewhere, then a reference to that encoded item will be used.
But, what to do if you have an array full of 'conditional' items, where the array needs to be encoded unconditionally?
I ended up wrapping the items I want to link to.
class thingLink: NSObject, NSCoding
{
weak var thing: Thing?
init(_ thing: Thing) {
self.thing = thing
}
required init?(coder aDecoder: NSCoder) {
thing = aDecoder.decodeObject(forKey: "Thing") as? Thing
}
func encode(with aCoder: NSCoder) {
// We encode these conditionally as they must be used elsewhere
aCoder.encodeConditionalObject(thing, forKey: "Thing")
}
}
...then I store these in my array which I encode as usual.
aCoder.encode(things, forKey: "Things")
If I move to a database to store things, I think this will help there too, because I will need a separate table to store the links and maintain priority etc.
I am using Core Data and have a subclass of NSManagedObject called Person (which is an entity in Core Data). The Person object has several properties (e.g. firstName, lastName, etc.) and relationships (e.g. friends, coWorkers). Some of these attributes are optional, whereas others are mandatory.
Given a Person object how can I efficiently determine whether a particular property or relationship is optional or mandatory?
I want to try and avoid having to do some kind of inefficient loop through each attribute of the Person object, as I will need to call the code fairly often, so something similar to the pseudocode below would be perfect:
if ( [[aPerson getProperty:#"firstName"] isOptional] ) {
// do stuff
}
...but I am not sure whether it could be done that simply. The above pseudocode would also only check properties, so I am guessing something else would need to be done in case the attribute being checked is a relationship.
I realize that it is probably more efficient to do a single loop and store the name of all the mandatory attributes in an array (then just check that array for the presence of the attribute name) and that's what I am doing at the moment, but unfortunately it doesn't really work with the structure of my code (thus the need for a more "on-demand" approach).
Any help would be greatly appreciated :)
You can write a function in your base subclass
NSDictionary *d = [[self entity] attributesByName];
NSAttributeDescription *attr = (NSAttributeDescription *)[d objectForKey:#"test"];
BOOL isopt = [attr isOptional];
I have an array filled with instances of a custom class which contains two String properties, firstname and lastname. Both have a getter method which is equal to the name of the property itself. There is also a method for retrieving the Full name of a person called "getFullName". Consider the example below.
CustomClass *person = [[CustomClass alloc] ...];
person.firstname // Returns "Thomas"
person.lastname // Returns "Meier"
[person getFullName] // Returns "Thomas Meier"
Now I would like to sort this Array by Fullname in a descending Order. I have been looking at some array sorting methods but was not quite able to figure out how to go about this. I guess that I have to create some kind of comparison function which compares two elements, yet how do I tell the SDK which values to pass to this method and where should I place it (in the custom class or in the class where the sorting happens?). Maybe there is another/better way of going about this? Admittedly I have close to none experience with sorting arrays.
Thanks a lot for your help!
Ps. The code should run on iOS 3.2
There are a couple ways to do this. One is to put a comparison method on the custom class. Call it -compare: or something like that. That method should take another object of the same custom type as its input. It should return NSOrderedAscending, NSOrderedDescending, or NSOrderedSame, depending on the result of the comparison. (Inside this compare function is where you look at your own fullName versus the passed-in object's fullName.)
Then you can use NSMutableArray's -sortUsingSelector: method, like this:
[myArray sortUsingSelector:#selector(compare:)];
This works on all versions of iOS. There are block comparison methods available in 4.0+.
I started to develop my singleton class but I have a problem.
What I want to do is have a search objects containing the values of the search form that I could use in several views.
I want to have the ability to get the singleton in any view in order to perform the search or build the search form.
So I have a set of values with a boolean for each to know if the variable has been initialized by the user or not, cause not all the search fields needs to be filled in.
For example :
NSString name= Bob;
BOOL nameFilled =True;
NSString adress= nil;
BOOL adressFilled=false;
NSNumber numberOfChilds = 0;
BOOL numberOfChildsFilled = false;
So my problem is that I can't retain the boolean in my header file because it's not a class.
How can I do, is there a better solution than what I presented above?
Hope I have been clear
You dont need to have this BOOLean value to see if it is filled, why not just use the object itself to see if it has been initialized so something like
if(name==nil)
//this means i t hasnt been initialized
else
//this means it has
Instead of using int, use NSNumber. Then, for objects that haven't been specified, use 'nil', which is distinct from an NSNumber with 0 as a value.
You don't need to #retain BOOL or other primitive types in Objective-C - you only need use that for object types.
Seriously, don't implement a singleton. It isn't necessary for this application. You should have a model class to handle this.
Try using dependancy injection and/or plist files to save the information. You'll have a much better time debugging and extending functionality.