Crash in OS X Core Data Utility Tutorial - objective-c

I'm trying to follow Apple's Core Data utility Tutorial. It was all going nicely, until...
The tutorial uses a custom sub-class of NSManagedObject, called 'Run'. Run.h looks like this:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Run : NSManagedObject {
NSInteger processID;
}
#property (retain) NSDate *date;
#property (retain) NSDate *primitiveDate;
#property NSInteger processID;
#end
Now, in Run.m we have an accessor method for the processID variable:
- (void)setProcessID:(int)newProcessID {
[self willChangeValueForKey:#"processID"];
processID = newProcessID;
[self didChangeValueForKey:#"processID"];
}
In main.m, we use functions to set up a managed object model and context, instantiate an entity called run, and add it to the context. We then get the current NSprocessInfo, in preparation for setting the processID of the run object.
NSManagedObjectContext *moc = managedObjectContext();
NSEntityDescription *runEntity = [[mom entitiesByName] objectForKey:#"Run"];
Run *run = [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moc];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
Next, we try to call the accessor method defined in Run.m to set the value of processID:
[run setProcessID:[processInfo processIdentifier]];
And that's where it's crashing. The object run seems to exist (I can see it in the debugger), so I don't think I'm messaging nil; on the other hand, it doesn't look like the setProcessID: message is actually being received. I'm obviously still learning this stuff (that's what tutorials are for, right?), and I'm probably doing something really stupid. However, any help or suggestions would be gratefully received!
===MORE INFORMATION===
Following up on Jeremy's suggestions:
The processID attribute in the model is set up like this:
NSAttributeDescription *idAttribute = [[NSAttributeDescription alloc]init];
[idAttribute setName:#"processID"];
[idAttribute setAttributeType:NSInteger32AttributeType];
[idAttribute setOptional:NO];
[idAttribute setDefaultValue:[NSNumber numberWithInteger:-1]];
which seems a little odd; we are defining it as a scalar type, and then giving it an NSNumber object as its default value. In the associated class, Run, processID is defined as an NSInteger. Still, this should be OK - it's all copied directly from the tutorial.
It seems to me that the problem is probably in there somewhere. By the way, the getter method for processID is defined like this:
- (int)processID {
[self willAccessValueForKey:#"processID"];
NSInteger pid = processID;
[self didAccessValueForKey:#"processID"];
return pid;
}
and this method works fine; it accesses and unpacks the default int value of processID (-1).
Thanks for the help so far!

If you are getting EXC_BAD_ACCESS on
[run setProcessID:[processInfo processIdentifier]];
it's almost certainly due to one of the pointers no longer pointing to a real object. Either run has been dealloc'd or processInfo has been dealloc'd. This assumes that the line is not the next line of code after
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
If it is, then both objects should be valid, so you are probably looking at something wrong with
[self willChangeValueForKey:#"processID"];
or
[self didChangeValueForKey:#"processID"];
if you have any objects observing that key, it's possible they have gone stale somehow.

This did not solve my problem with willAccessValueForKey but it solved a mystery. I subclassed an entity but I forgot to set the custom class in the model. The custom class worked until I wrote a method to send back a string which was a concatenation of some of the properties. The method was nearly identical to another method that was in another custom NSManagedObject class. I could not figure out why the one class worked and the other didn't. Once I set the custom class in the model they both worked.

Related

Bad Access Parent is Null - Wht is this Happening?

I understand what the error is, but in this case not what is causing it. In general use it occurs maybe 1% of the time (probably less) but I have found an extreme way to cause it which I will describe below. First, I am using an in-app purchase process I found on Ray Wenderlich's site. Below are the specific pieces of concern here:
.h:
typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
#interface IAPHelper : NSObject
- (void)requestProductsWithCompletionHandler:RequestProductsCompletionHandler)completionHandler;
#end
.m
#implementation IAPHelper
{
SKProductsRequest * _productsRequest;
RequestProductsCompletionHandler _completionHandler;
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(#"Loaded list of products...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
_completionHandler(YES, skProducts); // here is where bad access occurs
_completionHandler = nil;
}
Again, 99%+ of the time this works just fine. Given how infrequent the bad access happens in regular use and it has been difficult to diagnose. However, I found an extreme way to cause the issue. The setup is "Tab 1" is a table view controller and "Tab 2" is a table view controller that uses the code from above. If I quickly switch back and forth between the two tabs I can usually cause the problem to occur anywhere from a few seconds into it to 20-30 seconds. Doesn't happen every time in this scenario but it does the vast majority. As marked above the following line gets a bad access error with Parent is Null.
_completionHandler(YES, skProducts);
To solve the issue I simple do the following:
if (_completionHandler)
{
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
While that fix does work and does solve the problem I am still bothered by why this is occurring. Any thoughts as to the cause of this?
Update:
Apologies to all as I did forget to include the following in what I pasted above.
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
You need to treat your completion block as any other object when you are storing it. So if you are storing your block as a variable and then using it within a different scope from where you assign it, you need to increment the reference count by either copying it or retaining it. The simple solution is to create a strong property to store your block.
Depending on unseen bits of code, your completion handler block might not be being assigned correctly. You need to copy a block if you intend to use it outside of the scope in which it was created.
In your interface, declare your completion handler's storage attribute as "copy".
#property (nonatomic, readwrite, copy) void (^completionHandler)(BOOL, NSArray *);
If you want to control the local variable, you can synthesize the property manually in your implementation:
#synthesize completionHandler = _completionHandler;

Order of instance variable assignments freezes iOS app

In my header, I have two properties as shown below.
#interface HZCalendarDataSource : NSObject
#property (strong, nonatomic) NSMutableArray *datesOnCalendar;
#property (strong, nonatomic) HZCalendarDay *currentDay;
#end
Then in my implementation's initializer, I have the following lines of code.
- (id)init {
self = [super init];
if (self) {
// Alloc / Init instance variables.
self.datesOnCalendar = [[NSMutableArray alloc] init];
self.currentDay = [[HZCalendarDay alloc] init];
HZCalendarDay *date = [[HZCalendarDay alloc] initOnDate:today withEventStore:self.eventStore];
[self.datesOnCalendar addObject:date];
// THIS line causes the app to freeze!
// If this line is above [self.datesOnCalendar addObject:date];
// Then it does not freeze. Why does this happen?
self.currentDay = date;
}
return self;
}
The issue that I have, is that as shown in the comments, the self.currentDay = date; line freezes the app on the device. However, if I move the self.currentDay = date; line above the line where the date object is added to the NSMutableArray, then the code works just fine.
So my question is, why does the order of this matter? It should just be setting self.currentDay to reference the same date object that I added to the NSMutableArray correct?
I'd appreciate it if someone could explain this to me, I'm not understanding it. The order doesn't really matter, so for now I've moved the troublesome line to be executed prior to adding the date object to the array, however for educational purposes, I'd like to know why this is an issue in the first place.
Edit:
After letting the app run frozen for awhile, it finally failed in Xcode after invoking [HZCalendarDateSource setCurrentDay:] 25,827 times. It failed with EXC_BAD_ACCESSS in the debugger and -[__NSArrayM countByEnumeratingWithState:objects:count:];
Hope this helps.
Thanks!
So I figured it out, after re-reading the error message, it sounded like the setter was failing for some reason, even though I'm not implementing it.
However, I had wrote a helper method to loop through my array and perform some actions on an item that matches the args i provide it. I had called that helper method setCurrentDay:, thus I had a naming conflict. My code was getting stuck in a for-loop that I had wrote. The for-loop scanned the self.datesOnCalendar property, and when self.currentDay = date; was executed prior to adding the object to the array, the setCurrentDay: method would return, because the array was empty. Setting the currentDay after adding an object to the array was causing my setCurrentDay: method to get stuck in a loop.
Fix was to rename the setCurrentDay: method to something that does not conflict with the setter of the currentDay property, along with adjusting my for-loop.

CoreData issue with appDelegate

I'm relatively new to Objective C. So far everything has been going really well until I hit CoreData. I just can't get it to work! After spending many hours on something that seems to be pretty straightforward, I'm at my wits' end.
PLEASE help me figure out what I have done wrong:
I created a new Windows-Based app and checked 'use Core Data for storage'
In the xcdatamodel, I created an entity named 'RecipeData' with only one attribute 'recipeName' it is a string
in the app delegate, I load an XML file and parse it. When I parse the recipe name, I use the following:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:managedObjectContext];
I get the following error:
terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'RecipeData'
Which leads me to the big 3 questions:
is there anything really obvious that I am doing wrong?
since I checked 'use Core Data for storage,' it seems the following code is injected automatically into the app delegate .h:
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
Does this interfere with the code I am using?
I tried creating a new NSManagedObjectContext called *myManagedObjectContext but that did not work.
One other tidbit, when I add the following right above my code:
if (managedObjectContext == nil) {
NSLog(#"NO CONTEXT");
}
The console prints "NO CONTEXT"
I really appreciate any help. Thanks.
Where has managedObjectContext come from? Is it a typo for managedObjectContext_? The project templates create the latter, not the former. Using the code above with the code provided by the standard project templates should produce a syntax error. I'm guessing you've renamed some things?
You seem to be using managedObjectContext as an ivar. It is a property. Inside the class, there is a private managedObjectContext_ ivar which holds the reference to the object context. You shouldn't access this. You should be accessing the managedObjectContext property. When this property is first accessed, its getter method will create the context for you. Since you aren't accessing the property, the getter method isn't called and the context never gets created.
Where you have code like this:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:managedObjectContext];
...you should be using code like this:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:self.managedObjectContext];
Note the self. bit. This means that you are accessing a property on the self object, not accessing an ivar from the object the method is being called on.
Note that reading a property is the same as calling the getter method, so the above can also be written as:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:[self managedObjectContext]];

Writing my own #dynamic properties in Cocoa

Suppose (for the sake of argument) that I have a view class which contains an NSDictionary. I want a whole bunch of properties, all of which access the members of that dictionary.
For example, I want #property NSString* title and #property NSString* author.
For each one of these properties, the implementation is the same: for the getter, call [dictionary objectForKey:propertyName];, and for the setter do the same with setObject:forKey:.
It would take loads of time and use loads of copy-and-paste code to write all those methods. Is there a way to generate them all automatically, like Core Data does with #dynamic properties for NSManagedObject subclasses? To be clear, I only want this means of access for properties I define in the header, not just any arbitrary key.
I've come across valueForUndefinedKey: as part of key value coding, which could handle the getters, but I'm not entirely sure whether this is the best way to go.
I need these to be explicit properties so I can bind to them in Interface Builder: I eventually plan to write an IB palette for this view.
(BTW, I know my example of using an NSDictionary to store these is a bit contrived. I'm actually writing a subclass of WebView and the properties will refer to the IDs of HTML elements, but that's not important for the logic of my question!)
I managed to solve this myself after pouring over the objective-c runtime documentation.
I implemented this class method:
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
NSString *method = NSStringFromSelector(aSEL);
if ([method hasPrefix:#"set"])
{
class_addMethod([self class], aSEL, (IMP) accessorSetter, "v#:#");
return YES;
}
else
{
class_addMethod([self class], aSEL, (IMP) accessorGetter, "##:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
Followed by a pair of C functions:
NSString* accessorGetter(id self, SEL _cmd)
{
NSString *method = NSStringFromSelector(_cmd);
// Return the value of whatever key based on the method name
}
void accessorSetter(id self, SEL _cmd, NSString* newValue)
{
NSString *method = NSStringFromSelector(_cmd);
// remove set
NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:#""] lowercaseString] stringByReplacingOccurrencesOfString:#":" withString:#""];
// Set value of the key anID to newValue
}
Since this code tries to implement any method that is called on the class and not already implemented, it'll cause problems if someone tries calling something you're note expecting. I plan to add some sanity checking, to make sure the names match up with what I'm expecting.
You can use a mix of your suggested options:
use the #dynamic keyword
overwrite valueForKey: and setValue:forKey: to access the dictionary
use the objective-c reflection API's method class_getProperty and check it for nil. If it's not nil your class has such a property. It doesn't if it is.
then call the super method in the cases where no such property exists.
I hope this helps. Might seem a bit hacky (using reflection) but actually this is a very flexible and also absolutely "legal" solution to the problem...
PS: the coredata way is possible but would be total overkill in your case...
Befriend a Macro? This may not be 100% correct.
#define propertyForKey(key, type) \
- (void) set##key: (type) key; \
- (type) key;
#define synthesizeForKey(key, type) \
- (void) set##key: (type) key \
{ \
[dictionary setObject];// or whatever \
} \
- (type) key { return [dictionary objectForKey: key]; }
sounds like you should should be using a class instead of a dictionary. you're getting close to implementing by hand what the language is trying to give you.
There is a nice blog with example code with more robust checks on dynamic properties at https://tobias-kraentzer.de/2013/05/15/dynamic-properties-in-objective-c/ also a very nice SO answer at Objective-C dynamic properties at runtime?.
Couple of points on the answer. Probably want to declare an #property in the interface to allow typeahead also to declare the properties as dynamic in the implementation.

What is the type for boolean attributes in Core Data entities?

I am using Core Data programmatically (i.e. not using .xcdatamodel data model files) in much the same manner as depicted in Apple's Core Data Utility Tutorial. So my problem is that when I try to add an attribute to an entity with the type NSBooleanAttributeType, it gets a bit buggy. When I add it to my NSManagedObject subclass header file (in the tutorial, that would be Run.h) as
#property (retain) BOOL *booleanProperty;
compiling fails, saying error: property 'booleanProperty' with 'retain' attribute must be of object type.
It seems like some places in Cocoa use NSNumber objects to represent booleans, so I tried setting it to
#property (retain) NSNumber *booleanProperty;
instead. However, this evokes the following runtime errors:
*** -[NSAttributeDescription _setManagedObjectModel:]: unrecognized selector sent to instance 0x101b470
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSAttributeDescription _setManagedObjectModel:]: unrecognized selector sent to instance 0x101b470'
Using GDB, I am able to trace this back to the line in my source code where I add my entity to the managed object model:
[DVManagedObjectModel setEntities:[NSArray arrayWithObjects:myEntityWithABooleanAttribute, myOtherEntity]];
So my question is this: what type should I set booleanProperty to in my custom class header?
Try:
#property (nonatomic) BOOL booleanProperty;
The problem was that you used the retain in the property definition. For that you must have a property for an Objective-C class (it should be able to understand the 'retain' method). BOOL is not a class but an alias for signed char.
I wouldn't recommend the method suggested by Diederik Hoogenboom (i got an error even though my core data attribute was set as Boolean).
It's worth pointing out that although this line will work for a custom object, it will not work for a subclass of NSManagedObject:
#property (nonatomic) BOOL booleanProperty;
Your property should be set as this:
#property (nonatomic, retain) NSNumber *booleanProperty;
When i copy the method declarations for a Boolean type (using the technique suggested by Jim Correia), the getter and setter are typed as:
NSNumber:-(NSNumber *)booleanProperty;
-(void)setBooleanProperty:(NSNumber *)value;
...this is what a Boolean property in core data is set as, and you need to validate your property with something like this:
-(BOOL)validateBooleanProperty:(NSNumber **)toValidate error:(NSError **)outError
{
int toVal = [*toValidate intValue];
if ( (toVal < 0) || (toVal > 1) )
{
NSString *errorString = NSLocalizedStringFromTable(#"Boolean Property", #"TheObject", #"validation: not YES or NO");
NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:errorString forKey:NSLocalizedDescriptionKey];
NSError *error = [[[NSError alloc] initWithDomain:NSCocoaErrorDomain code:-1 userInfo:userInfoDict] autorelease];
*outError = error;
return NO;
}
return YES;
}//END
…remember to include the validateBooleanProperty declaration in the header file. The setter and getter methods store and retrieve your property using -(id)primitiveValueForKey:(NSString *)key.
Finally you need to explicitly call the validate method from whatever view controller / app delegate you're setting the object from:
NSNumber *boolProp = [[[NSNumber alloc] initWithInt :0] autorelease];
NSError *valError = nil;
if ([TheObject validateBooleanProperty:&boolProp error:&valError] == YES)
{
[TheObject setBooleanProperty :boolProp];
}
In the header,
#property (nonatomic, retain) NSNumber *booleanProperty;
In the implementation,
#dynamic booleanProperty;
To set it to true...
self.booleanProperty = [NSNumber numberWithBool:YES];
To set it to false...
self.booleanProperty = [NSNumber numberWithBool:NO];
To compare it to a literal true boolean:...
self.booleanProperty.boolValue == YES;
To compare it to a literal false boolean:...
self.booleanProperty.boolValue == NO;
For more information: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/nsnumber_Class/Reference/Reference.html#//apple_ref/occ/clm/NSNumber/numberWithBool:
One of the best ways to generate correct accessors in your NSManagedObject subclass is to bring up the contextual menu on a attribute or property in the data modeling tool and choose one of the following commands:
Copy Method Declarations to Clipboard
Copy Method Implementations to Clipboard
Copy Obj-C 2.0 Method Declarations to Clipboard
Copy Obj-C 2.0 Method Implementations to Clipboard
Let Xcode 4.0 decide for you.
In Xcode: select an Entity from your *.xcdatamodel file view.
Select Editor>Create NSMagedObject Subclass...
Xcode declares your Boolean objects as type NSNumber.
Edit: I'm curious what the motivation is for mitigating the xcdatamodel? Anyone?
An attribute of type Boolean in a NSManagedObject is of type NSCFBoolean. This is a private subclass of NSNumber.
I don't know if this is just a typo on your part, but this:
[NSArray arrayWithObjects:myEntityWithABooleanAttribute, myOtherEntity]
is definitely wrong. The last parameter of that method should always be nil.
Use NSNumber. There's no bool in the CoreData framework.