Is it still true that it's a bad idea to name attributes and relationships like any non-parameter method of NSObject or NSManagedObject? - iphone

I did some little experimentation on this.
I created an boolean attribute, named isFault. You know, that's a method of NSManagedObject and therefore actually not allowed for an attribute name because of KVC.
Simply, I used the default Core Data template for this test, but created the data model programmatically so I can show you what I do.
So here we go:
NSAttributeDescription *badAttr = [[NSAttributeDescription alloc] init];
[badAttr setName:#"isFault"];
[badAttr setAttributeType:NSBooleanAttributeType];
[badAttr setOptional:YES];
// don't want to occupy you with the whole, non-important rest ...
Next, I've modified the -insertNewObject method of the controller, added these lines:
// Assume: An managed object is created into the context, but not saved yet...
NSLog(#"isFault = %d", [newManagedObject isFault]); // 0 = NO
BOOL isFault = [[newManagedObject valueForKey:#"isFault"] boolValue];
NSLog(#"isFault = %d", isFault); // 0 = NO
[newManagedObject setValue:[NSNumber numberWithBool:YES] forKey:#"isFault"];
isFault = [[newManagedObject valueForKey:#"isFault"] boolValue];
NSLog(#"isFault = %d", isFault); // 0 = NO
Like you can see, I'm not able to set the isFault attribute to YES. It remains NO. Now, changing the attribute name to isFaultXYZ, will allow that.
So actually, what I wanted to ask is... since this stuff seems to depend on KVC, does the rule only apply to methods that return something and have no parameter? And does it matter what data type is returned? For example, -changedValues has no parameter and returns an NSDictionary. But since there is no attribute type like that, would this cause a collision anyways?

So, here is the evidence. When naming the attribute changedValues, THIS happens after attempting to access it via KVC:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFDictionary boolValue]: unrecognized selector sent to instance 0x1205680'
2010-06-10 14:52:40.232 CoreData[33996:207] Stack: (
8035,
4437,
8825,

Related

Instantiating Custom Object with Core Data

Currently whenever I want to save a custom object called List I use
(Core Date + Magical Record)
List *list = [List MR_createInContext:_managedObjectContext];
Now I'm wondering whether I could instantiate a List item like this
List *localList = [[List alloc] init];
// set some properties
localList.name = #"foobar";
List *newList = [List MR_createInContext:_managedObjectContext];
newList = locaList
Will this cause any problems with Core Data/memory issues?
No, for a couple of reasons:
You can't use init with managed objects. The designated initializer is initWithEntity:insertIntoManagedObjectContext:. There's also a convenience constructor on NSEntityDescription called insertNewObjectForEntityForName:inManagedObjectContext:. If you don't use one of those, you'll have problems.
When you assign newList = localList, you throw away the previous object in localList with all of its data. All that's left after this point is the one you originally assigned to newList. Your assignment to localList.name, for example, disappears with the localList object.
Since you've defined List it's hard to say whether you're creating a List instance correctly in both cases. At the very least your code leaks the second List that you create, and it seems unlikely that that code does what you think it does.
Your code is equivalent to:
List *localList = [[List alloc] init];
// set some properties
localList.name = #"foobar";
List *newList = localList;

MagicalRecord sorting using by KVC

I used KVC until now to access object's properties.
In my object i have a method like this:
-(Address *)mainAddress {
if (self.addresses != nil) {
return [self.addresses anyObject]; //stub method
}
else {
return nil;
}
}
I can use this method with KVC using
mystring = [cliente valueForKeyPath:#"mainAddress.city"];
but i cannot use to create a NSFetchRequestController (this code use MagicalRecord)
NSFetchedResultsController *acontroller = [Customer fetchAllSortedBy:#"mainAddress.city" ascending:ascending withPredicate:companyPredicate groupBy:nil delegate:self];
This is the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath mainAddress.city not found in entity <NSSQLEntity Customer id=4>'
In order to use the sorting with an NSFetchedResultsController, your mainAddress keyPath needs to be an attribute on your entity. NSFRC will sort the data not using KVC in memory, but using the underlying data store. Bottom line answer: make mainAddress a field on your entity in the data model.

KVC Collection Accessor + index 0 beyond bounds for empty array error

I have a mutable array property declared and synthesized:
#property (nonatomic, strong) NSMutableArray *arrayOfTasks;
I am using KVC collection Accessors for the same property and also I have other methods which will internally call this KVC Collection accessor method like this:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
I had to do some modifications and add the object into the array only when a particular condition is satisfied, so to make sure that this check goes into the designated method, I included the following in the -insertObject KVC Collection accessor method:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
if ([inTask isOperatable])
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
Now each time when I trigger -addObjectINArrayOfTasks method and if the -isOperatable condition returns boolean NO, the app crashes with no stack trace at all! (Stack trace is at main() of the application). All it says is "index 0 beyond bounds for empty array error".
I am not understanding the reason for this, I am not trying to access the array yet, so I am not giving a chance for framework to complain me that there is no element at index 0. Moreover, I am doing the count of array items check everywhere before accessing the objects out of array. For, if I was trying to access and element out of the bounds index, the app would crash at the same point and let me know exactly where I was trying to access the index out of bounds. That would have been a simple straightforward fix.
Now, to just cross verify, I made a small change in the code like this, which seems to work:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
if ([inTask isOperatable])
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
}
I can go ahead with this approach which is working and does not crash, but my concerns are the following:
Adding the same check in designated method would be an added advantage in future if some other programmer would want to invoke the designated method from somewhere else.
Why would the app crash in first case when I wont insert the object into the array in KVC collection accessors based on some condition check?
Thanks for any inputs,
Raj
I think the crash you are seeing is more likely related to internal KVC behavior than your array. That might be the reason you don't see a usable stack trace. Have you enabled the exception breakpoint in Xcode?
KVC basically expects that -insertObject:in<Key>AtIndex: will insert a new object at the given index (presumably 0 in your case). Since it assumes that the object was inserted it should now be accessible by queuing the data structure (NSMutableArray) for the object at the given index. When the condition evolves to NO, you fail to insert this object, which means that an index out of bounds exception is possible when KVO tries to query using the provided index.
The second code snipped you posted avoids this error by not calling the KVC collection accessor when an insertion is not needed.
If you want to minimize the chance of someone incorrectly using those methods, expose just -addObjectInArrayOfTasks: in your public header. In addition you can document this. If you want to make it absolutely certain that -insertObject:in<Key>AtIndex: can't be accessed on int's own, you can add an NSAssert, that checks if the method was called from -addObjectInArrayOfTasks:.

Why does my property getter cause an NSZombie error?

I have a service object called JSONNetworkUtility, and I store it in my model as an ivar, as well as a synthesized property with the same name, nonatomic and retained:
myNetworkUtility = [[JSONNetworkUtility alloc] initNetworkConnectionWithURL:urlString withQueryString:nil delegate:self];
The delegate includes two callbacks, one is networkUtility:didFailWithError: and the other is networkUtility:didFinishWithData:. The weird thing is that the property is causing some weird errors:
- (void)networkUtility:(JSONNetworkUtility *)networkUtility didFinishWithData:(NSArray *)jsonArray
{
NSLog(#"myNetworkUtility = %#", myNetworkUtility);
// returns <JSONNetworkUtility: 0x3944450>
NSLog(#"networkUtility = %#", networkUtility);
// also returns <JSONNetworkUtility: 0x3944450>
NSLog(#"self.myNetworkUtility = %#", self.myNetworkUtility);
// fails and throws an NSZombie error!
}
The error I get on that line is:
*** -[MyModel myNetworkUtility]: message sent to deallocated instance 0x148190
I'm totally stumped! Any clues as to why it's failing on a getter? And why is it returning a completely different object?
The reason I'm using a getter is because I wanted to use self.myNetworkUtility = nil so I can write over the top of the property with a new object, but I've cut it back to just this trace and I'm still having problems...
Thanks!
I think the problem is that MyModel is deallocated. When you're accessing the ivar, the objects are still there in that memory address address, the memory is not yet corrupted. But when sending a message to deallocated 'self' the runtime catches the fact that the object is deallocated.

Retrieve properties of "id" type object iphone

I'm facing some difficulty in retrieving properties of "id" type object. This is how I'm accessing it:
I'm doing following to assign an object to id type object from a generic array containing different types of objects and calling method "savedata" to which I'm passing the object as well as its type:
for(id objInArray in genericArray){
NSString *objType = [objInArray valueForKey:#"type"];
[objInArray retain];
[self saveData:objInArray :objType];
}
In savedata method I'm writing following code to retrieve the properties of id object:
-(void)saveData:(id)object :(NSString *)objectType
{
self.managedObjectContext = appDelegate.managedObjectContext;
if([objectType isEqualToString:#"event"])
{
Event * newEvent = (Event *)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
[newEvent setEletitle:[NSString stringWithFormat:#"%#", [object valueForKey:#"eletitle"]]];
[self saveAction];
}
But the object "object" containing the values fails to assign them to object newEvent.
I also tried to retrive this value in a string object like this:
NSString *eletit = [object valueForKey:#"eletitle"];
[eletit retain];
But eletit is also invalid at the end of this transaction.
Can anybody please help? This' really urgent.
Thanx in advance.
I don't have you answer unfortunately but I have few comments on your code.
Are you sure it's normal you array contain so generics object? It's strange because all your object contained in your array need to respond to "type" or "eletitle" messages, so I guess objInArray is less generic than just "id".
Second, it's not recommended to have selector like saveData::, in Objective-C it's usual and recommended to name the arguments, it's more understandable.