Bad Access Parent is Null - Wht is this Happening? - objective-c

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;

Related

Correct way to reset class values in unit testing with setup

I'm new to Objective-C so please bear with me.
I have a unit test class that looks something like this:
static NSString * const kTestingUserID = #"userID";
#interface UserPreferencesTest
#property(nonatomic) UserPreferences *userPreferences;
#end
#implementation UserPreferencesTest
- (void)setUp {
[super setUp];
self.userPreferences = [[UserPreferences alloc] init];
}
- (void)testEmptyDictionary {
STAssertFalse([self.userPreferences getLockoutStatus:kTestingUserID],
#"An unset username should always return false");
}
- (void)testBlockManualEntry {
//lock the username
[self.userPreferences lockout:kTestingUserID];
STAssertTrue([self.userPreferences getLockoutStatus:kTestingUserID],
#"The account has been locked out");
}
#end
They call the following methods in the userPreferences Class:
- (void)lockout:(NSString *) userID {
NSMutableDictionary *dictionary =
[[self.defaults objectForKey:dict] mutableCopy];
dictionary[userID] = [NSNumber numberWithBool:YES];
[self.defaults setObject:[NSDictionary dictionaryWithDictionary:dictionary]
forKey:dict];
[self synchronize];
}
- (BOOL) getLockoutStatus:(NSString *) userID {
NSDictionary *dictionary = [self.defaults objectForKey:dict];
if ([dictionary objectForKey:userID])
return true;
return false;
}
If I comment out the testBlockManualEntry the test passes. If I run the two tests together the first one, testEmptyDictionary fails. The issue to my understanding is that the username is set in the 2nd test and then is found in the 1st test.
If I were to write a similar unit test in java I would use #Before in my setup and thus setup would run before each test method is called. That would ensure that the userPreferences is created each time and thus the values in that object are reset.
What am I missing here? My understanding of setup in Objective-C is that it should behave similarly (clearly it's not unless theres another bug that I'm not seeing).
Also please ignore the fact that a set would be better used in this scenario than a dict, I'm just looking (and how to fix the issue) for why the tests fail when run together.
I would suggest changing over to Xcode's XCT test framework. SN* is an old framework.
Your problem is probably coming from
#interface UserPreferencesTest
It should be
#interface UserPreferencesTest: XCTestCase
So that it inherits all the functionality of the test framework. Then it should work.
I also noticed this code:
if ([dictionary objectForKey:userID])
return true;
return false;
There is nothing wrong with this code and it will work. But from a style point of view I would change it. Firstly I always recommend ensuring that if ... statements always have {...} brackets. The reason is that it's easy to miss read a single line if and subtle bugs can appears, especially if other developers are updating your code.
The second thing is that whenever I see a return YES or return NO, I tend to look to see if they are a verbose version of what the code is trying to achieve.
In that light, I would probably code something like this:
return [dictionary objectForKey:userID] != nil;
Having said that, sometimes code is clearer when you do return YES or return NO. So it's how you feel about it.

Mac OS X, Objective-C crash with NSMutableArray

Environment: Mac OS 10.8.5, XCode 5.1.1
Problem: Crash in obj_msgsend on addObject message to a NSMutableArray
Disclaimer: I'm new to Objective-C, so this could an obvious mistake. But it's mysterious.
Details:
I've been able to prune the problem down to a small test case (thankfully), though the exact manifestation of the problem is different from the full application.
Here's the #interface:
#interface ObjCQueue : NSObject
+ (void) push: (NSString *)calEvent;
+ (NSString *) pop;
#end
Here's the Objective-C class implementation.
#import <Foundation/Foundation.h>
#include "ObjcQueue.h"
NSMutableArray *qArray;
#implementation ObjCQueue
{
}
+ (void) init
{
qArray = [[NSMutableArray alloc] init];
// NSLog(#"(init)qArray class is: %#\n", NSStringFromClass([qArray class]));
}
+ (void) push:(NSString *)calEvent
{
[qArray addObject:calEvent];
}
+ (NSString *) pop
{
// This will return nil if there's no first object
NSString *retEvent = [qArray objectAtIndex:0];
// Don't delete the front of the queue if nothing is there
if (retEvent != nil)
[qArray removeObjectAtIndex:0];
return retEvent;
}
#end
and main.m does this:
int main(int argc, const char * argv[])
{
#autoreleasepool {
[ObjCQueue init];
[ObjCQueue push:#"Pushed thing"];
NSLog(#"Popped: %#\n", [ObjCQueue pop]);
}
return 0;
}
For the moment, let's ignore the possibility that how I'm doing this is totally wrong (we'll get back to that).
If I run this code as-is, I get a crash in objc_msgSend called by the addObject message sent from [ObjCQueue push:]
The mystery part is, if I uncomment the NSLog call in [ObjCQueue init] everything runs just fine.
In the larger application, I see a different issue. The failure also occurred in the push method, except the run-time error I got said that addObject was an invalid selector. When I check the type of qArray in that case, it has a type of NSDictionary (that's from memory, it wasn't spelled exactly that way) instead of NSMutableArray. Also, in the larger application, adding the NSLog call in the init method makes everything run smoothly.
In this smaller example, the type of qArray always appears to be NSMutableArray.
In other answers to similar questions, the implication is that the object corresponding to qArray is getting overwritten, and/or released prematurely. I don't see how that could happen here, since it's global, and ObjCQueue only has class methods and no instance of it is created. [ObjCQueue init] is only called once.
One other bit of data: In this smaller case, qArray gets displayed differently depending where (in the debugger) it's displayed.
In init, in the case where it crashes, immediately after qArray gets its value, the debugger shows:
Printing description of qArray:
<__NSArrayM 0x10010a680>(
)
But in push, just before the addObject method is called, the debugger shows:
Printing description of qArray:
(NSMutableArray *) qArray = 0x000000010010a680
The value is the same, but the type is kinda sorta different (maybe). In the case with no crash, the display is identical in both cases (they're both the same as the first display)
This may not be the best way (or it may be a blatantly wrong way) to initialize qArray, and I can accept that. But why would the behavior change with the addition of the NSLog call?
Any help/insights will be appreciated.
-Eric
P.S. Here's the XCode project: Bug Test
The problem is because ARC is releasing qArray before you called push so you're calling on an object that is already released. A good solution to this problem would be to either change your class to an actual instance, or create a singleton so that ARC knows to retain the array rather than just releasing it right after you init.

Unit Testing Example with OCUnit

I'm really struggling to understand unit testing. I do understand the importance of TDD, but all the examples of unit testing I read about seem to be extremely simple and trivial. For example, testing to make sure a property is set or if memory is allocated to an array. Why? If I code out ..alloc] init], do I really need to make sure it works?
I'm new to development so I'm sure I'm missing something here, especially with all the craze surrounding TDD.
I think my main issue is I can't find any practical examples. Here is a method setReminderId that seems to be a good candidate for testing. What would a useful unit test look like to make sure this is working? (using OCUnit)
- (NSNumber *)setReminderId: (NSDictionary *)reminderData
{
NSNumber *currentReminderId = [[NSUserDefaults standardUserDefaults] objectForKey:#"currentReminderId"];
if (currentReminderId) {
// Increment the last reminderId
currentReminderId = #(currentReminderId.intValue + 1);
}
else {
// Set to 0 if it doesn't already exist
currentReminderId = #0;
}
// Update currentReminderId to model
[[NSUserDefaults standardUserDefaults] setObject:currentReminderId forKey:#"currentReminderId"];
return currentReminderId;
}
Update: I've improved on this answer in two ways: it's now a screencast, and I switched from property injection to constructor injection. See How to Get Started with Objective-C TDD
The tricky part is that the method has a dependency on an external object, NSUserDefaults. We don't want to use NSUserDefaults directly. Instead, we need to inject this dependency somehow, so that we can substitute a fake user defaults for testing.
There are a few different ways of doing this. One is by passing it in as an extra argument to the method. Another is to make it an instance variable of the class. And there are different ways of setting up this ivar. There's "constructor injection" where it's specified in the initializer arguments. Or there's "property injection." For standard objects from the iOS SDK, my preference is to make it a property, with a default value.
So let's start with a test that the property is, by default, NSUserDefaults. My toolset, by the way, is Xcode's built-in OCUnit, plus OCHamcrest for assertions and OCMockito for mock objects. There are other choices, but that's what I use.
First Test: User Defaults
For lack of a better name, the class will be named Example. The instance will be named sut for "system under test." The property will be named userDefaults. Here's a first test to establish what its default value should be, in ExampleTests.m:
#import <SenTestingKit/SenTestingKit.h>
#define HC_SHORTHAND
#import <OCHamcrestIOS/OCHamcrestIOS.h>
#interface ExampleTests : SenTestCase
#end
#implementation ExampleTests
- (void)testDefaultUserDefaultsShouldBeSet
{
Example *sut = [[Example alloc] init];
assertThat([sut userDefaults], is(instanceOf([NSUserDefaults class])));
}
#end
At this stage, this doesn't compile — which counts as the test failing. Look it over. If you can get your eyes to skip over the brackets and parentheses, the test should be pretty clear.
Let's write the simplest code we can to get that test to compile and run — and fail. Here's Example.h:
#import <Foundation/Foundation.h>
#interface Example : NSObject
#property (strong, nonatomic) NSUserDefaults *userDefaults;
#end
And the awe-inspiring Example.m:
#import "Example.h"
#implementation Example
#end
We need to add a line to the very beginning of ExampleTests.m:
#import "Example.h"
The test runs, and fails with the message, "Expected an instance of NSUserDefaults, but was nil". Exactly what we wanted. We have reached step 1 of our first test.
Step 2 is to write the simplest code we can to pass that test. How about this:
- (id)init
{
self = [super init];
if (self)
_userDefaults = [NSUserDefaults standardUserDefaults];
return self;
}
It passes! Step 2 is complete.
Step 3 is to refactor code to incorporate all changes, in both production code and test code. But there's really nothing to clean up yet. We are done with our first test. What do we have so far? The beginnings of a class that can access NSUserDefaults, but also have it overridden for testing.
Second Test: With no matching key, return 0
Now let's write a test for the method. What do we want it to do? If the user defaults has no matching key, we want it to return 0.
When first starting with mock objects, I recommend making them by hand at first, so that you get an idea of what they're for. Then start using a mock object framework. But I'm going to jump ahead and use OCMockito to make things faster. We add these lines to the ExampleTest.m:
#define MOCKITO_SHORTHAND
#import <OCMockitoIOS/OCMockitoIOS.h>
By default, an OCMockito-based mock object will return nil for any method. But I'll write extra code to make the expectation explicit by saying, "given that it's asked for objectForKey:#"currentReminderId", it will return nil." And given all that, we want the method to return the NSNumber 0. (I'm not going to pass an argument, because I don't know what it's for. And I'm going to name the method nextReminderId.)
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldReturnZero
{
Example *sut = [[Example alloc] init];
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:nil];
assertThat([sut nextReminderId], is(equalTo(#0)));
}
This doesn't compile yet. Let's define the nextReminderId method in Example.h:
- (NSNumber *)nextReminderId;
And here's the first implementation in Example.m. I want the test to fail, so I'm going to return a bogus number:
- (NSNumber *)nextReminderId
{
return #-1;
}
The test fails with the message, "Expected <0>, but was <-1>". It's important that the test fail, because it's our way of testing the test, and ensuring that the code we write flips it from a failing state to a passing state. Step 1 is complete.
Step 2: Let's get the test test to pass. But remember, we want the simplest code that passes the test. It's going to look awfully silly.
- (NSNumber *)nextReminderId
{
return #0;
}
Amazing, it passes! But we're not done with this test yet. Now we come to Step 3: refactor. There's duplicate code in the tests. Let's pull sut, the system under test, into an ivar. We'll use the -setUp method to set it up, and -tearDown to clean it up (destroying it).
#interface ExampleTests : SenTestCase
{
Example *sut;
}
#end
#implementation ExampleTests
- (void)setUp
{
[super setUp];
sut = [[Example alloc] init];
}
- (void)tearDown
{
sut = nil;
[super tearDown];
}
- (void)testDefaultUserDefaultsShouldBeSet
{
assertThat([sut userDefaults], is(instanceOf([NSUserDefaults class])));
}
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldReturnZero
{
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:nil];
assertThat([sut nextReminderId], is(equalTo(#0)));
}
#end
We run the tests again, to make sure they still pass, and they do. Refactoring should only be done in "green" or passing state. All tests should continue to pass, whether refactoring is done in the test code or the production code.
Third Test: With no matching key, store 0 in user defaults
Now let's test another requirement: the user defaults should be saved. We'll use the same conditions as the previous test. But we create a new test, instead of adding more assertions to the existing test. Ideally, each test should verify one thing, and have a good name to match.
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldSaveZeroInUserDefaults
{
// given
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:nil];
// when
[sut nextReminderId];
// then
[verify(mockUserDefaults) setObject:#0 forKey:#"currentReminderId"];
}
The verify statement is the OCMockito way of saying, "This mock object should have been called this way one time." We run the tests and get a failure, "Expected 1 matching invocation, but received 0". Step 1 is complete.
Step 2: the simplest code that passes. Ready? Here goes:
- (NSNumber *)nextReminderId
{
[_userDefaults setObject:#0 forKey:#"currentReminderId"];
return #0;
}
"But why are you saving #0 in user defaults, instead of a variable with that value?" you ask. Because that's as far as we've tested. Hang on, we'll get there.
Step 3: refactor. Again, we have duplicate code in the tests. Let's pull out mockUserDefaults as an ivar.
#interface ExampleTests : SenTestCase
{
Example *sut;
NSUserDefaults *mockUserDefaults;
}
#end
The test code shows warnings, "Local declaration of 'mockUserDefaults' hides instance variable". Fix them to use the ivar. Then let's extract a helper method to establish the condition of the user defaults at the start of each test. Let's pull that nil out to a separate variable to help us with the refactoring:
NSNumber *current = nil;
mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:current];
Now select the last 3 lines, context click, and select Refactor ▶ Extract. We'll make a new method called setUpUserDefaultsWithCurrentReminderId:
- (void)setUpUserDefaultsWithCurrentReminderId:(NSNumber *)current
{
mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:current];
}
The test code that invokes this now looks like:
NSNumber *current = nil;
[self setUpUserDefaultsWithCurrentReminderId:current];
The only reason for that variable was to help us with the automated refactoring. Let's inline it away:
[self setUpUserDefaultsWithCurrentReminderId:nil];
Tests still pass. Since Xcode's automated refactoring didn't replace all instances of that code with a call to the new helper method, we need to do that ourselves. So now the tests look like this:
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldReturnZero
{
[self setUpUserDefaultsWithCurrentReminderId:nil];
assertThat([sut nextReminderId], is(equalTo(#0)));
}
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldSaveZeroInUserDefaults
{
// given
[self setUpUserDefaultsWithCurrentReminderId:nil];
// when
[sut nextReminderId];
// then
[verify(mockUserDefaults) setObject:#0 forKey:#"currentReminderId"];
}
See how we continually clean as we go? The tests have actually become easier to read!
Fourth Test: With matching key, return incremented value
Now we want to test that if the user defaults has some value, we return one greater. I'm going to copy and alter the "should return zero" test, using an arbitrary value of 3.
- (void)testNextReminderIdWithCurrentReminderIdInUserDefaultsShouldReturnOneGreater
{
[self setUpUserDefaultsWithCurrentReminderId:#3];
assertThat([sut nextReminderId], is(equalTo(#4)));
}
That fails, as desired: "Expected <4>, but was <0>".
Here's simple code to pass the test:
- (NSNumber *)nextReminderId
{
NSNumber *reminderId = [_userDefaults objectForKey:#"currentReminderId"];
if (reminderId)
reminderId = #([reminderId integerValue] + 1);
else
reminderId = #0;
[_userDefaults setObject:#0 forKey:#"currentReminderId"];
return reminderId;
}
Except for that setObject:#0, this is starting to look like your example. I don't see anything to refactor, yet. (There actually is, but I didn't notice until later. Let's keep going.)
Fifth Test: With matching key, store incremented value
Now we can establish one more test: given those same conditions, it should save the new reminder ID in user defaults. This is quickly done by copying the earlier test, altering it, and giving it a good name:
- (void)testNextReminderIdWithCurrentReminderIdInUserDefaultsShouldSaveOneGreaterInUserDefaults
{
// given
[self setUpUserDefaultsWithCurrentReminderId:#3];
// when
[sut nextReminderId];
// then
[verify(mockUserDefaults) setObject:#4 forKey:#"currentReminderId"];
}
That test fails, with "Expected 1 matching invocation, but received 0". To get it passing, of course, we simply change the setObject:#0 to setObject:reminderId. Everything passes. We're done!
Wait, we're not done. Step 3: Is there anything to refactor? When I first wrote this, I said, "Not really." But looking it over after watching Clean Code episode 3, I can hear Uncle Bob telling me, "How big should a function be? 4 lines is OK, maybe 5. 6 is… OK. 10 is way too big." That's at 7 lines. What did I miss? It must be violating the rule of functions by doing more than one thing.
Again, Uncle Bob: "The only way to be really be sure that a function does one thing is to extract 'til you drop." Those first 4 lines work together; they calculate the actual value. Let's select them, and Refactor ▶ Extract. Following Uncle Bob's scoping rule from episode 2, we'll give it a nice, long descriptive name since its scope of use is very limited. Here's what the automated refactoring gives us:
- (NSNumber *)determineNextReminderIdFromUserDefaults
{
NSNumber *reminderId = [_userDefaults objectForKey:#"currentReminderId"];
if (reminderId)
reminderId = #([reminderId integerValue] + 1);
else
reminderId = #0;
return reminderId;
}
- (NSNumber *)nextReminderId
{
NSNumber *reminderId;
reminderId = [self determineNextReminderIdFromUserDefaults];
[_userDefaults setObject:reminderId forKey:#"currentReminderId"];
return reminderId;
}
Let's clean that up to make it tighter:
- (NSNumber *)determineNextReminderIdFromUserDefaults
{
NSNumber *reminderId = [_userDefaults objectForKey:#"currentReminderId"];
if (reminderId)
return #([reminderId integerValue] + 1);
else
return #0;
}
- (NSNumber *)nextReminderId
{
NSNumber *reminderId = [self determineNextReminderIdFromUserDefaults];
[_userDefaults setObject:reminderId forKey:#"currentReminderId"];
return reminderId;
}
Now each method is really tight, and it's easy for anyone to read the 3 lines of the main method to see what it does. But I'm uncomfortable having that user defaults key spread across two methods. Let's extract that into a constant at the head of Example.m:
static NSString *const currentReminderIdKey = #"currentReminderId";
I'll use that constant wherever that key appears in the production code. But the test code continues to use the literals. This guards us from someone accidentally changing that constant key.
Conclusion
So there you have it. In five tests, I have TDD'd my way to the code you asked for. Hopefully it gives you a clearer idea of how to TDD, and why it's worth it. By following the 3-step waltz
Add one failing test
Write the simplest code that passes, even if it looks dumb
Refactor (both production code and test code)
you don't just end up at the same place. You end up with:
well-isolated code that supports dependency injection,
minimalist code that only implements what has been tested,
tests for each case (with the tests themselves verified),
squeaky-clean code with small, easy-to-read methods.
All these benefits will save more time than the time invested in TDD — and not just in the long term, but immediately.
For an example involving a full app, get the book Test-Driven iOS Development. Here's my review of the book.

Coredata & NSPersistentDocument: Sum of column numbers crash

I have a problem with a #sum binding of a column in my program:
I'm doing a Coredata, NSPersistentDocument based program. I'm doing mostly everything from IB, the creation of the data model, NSArrayController and NSTableView...
I have just 1 entity with 62 attributes (61 NSString and 1 NSNumber). I import a CSV file with 12722 records. Import works well, can save to xml, binary, sqlite... I've double checked that the overall process works perfect. Can save/load. Everything is there.
The problem that I have: I've created a label that I BIND to #sum of the column with the NSNumber property. This is how I did
> label->Bindings Inspector->Value
> Bind to: My_Entity_NSArrayController
> Controller Key: selection
> Model Key Path: #sum.myNumericAttribute
When I run the program, click on Import, Select ALL the rows, the #sum works well. It's fast, however and here is the first problem: once I save the file (tried all... binary/xml/sqlite) and later load it and try to Select ALL again, the program crash without error.
Tried through "Profile"->Allocations. I noticed:
I don't have memory leaks
When loading from disk and then select all: Goes extremelly slow. After 5 minutes didn't yet finished (I stopped it) and I saw +45MB of CFNumber (Live Bytes) and >1.500.00#Overall. So, something is wrong here, as I'm talking about 12722 rows/registers of type Interger32.
The second problem is the same but reproduced from a different angle. Instead of using "selection" I've tried to use "arrangedObjects".
In this case the problem appears even while importing from CSV, it goes extremely slow and it finally crash. Trying to open an already created file also crash.
This is how I did label->Bindings Inspector->Value
> label->Bindings Inspector->Value
> Bind to: My_Entity_NSArrayController
> Controller Key: arrangedObjects
> Model Key Path: #sum.myNumericAttribute
Can you please help me with some light on what to look for or ideas that can help me find where the problem is?.
Thanks a lot.
Luis
---- NEW EDIT AFTER MORE RESEARCH ----
I've found a workaround which I DONT' UNDERSTAND, please comments/answers really appreciated.
My program uses Coredata (SQLite), NSPersistentDocument, NSTableView and an NSArrayController. I want to have a working NSTextField bound to a #sum Collection Operation
Problem: As soon as I open an existing document with SQLite DB populated and I try to bind to the arrangedObjects.#sum.t_24_Bookings from the NSWindowController, the program crash.
My initial guess it's related to the Cannot access contents of an object controller after a nib is loaded however I've followed the recommendation of performing a first Fetch like this without success:
- (void) awakeFromNib
{
:
BOOL ok = [[self out_CtEn_Transaction] fetchWithRequest:nil merge:NO error:&error];
:
Continuing with this idea I've found that if I create a "real" complete Fetch + I perform a #sum access from the Document subclass, then it works.
Here is the code with comments I've put in place in order to have the workaround working.
ABDocument interface (a NSPersistentDocument subclass)
#interface ABDocument : NSPersistentDocument {
BOOL ivNewDocument;
NSArray *ivFetchedTransactions;
NSNumber *ivTotalBookings;
}
#property (nonatomic, getter=isNewDocument) BOOL newDocument;
#property (nonatomic, retain) NSArray *fetchedTransactions;
#property (nonatomic, retain) NSNumber *totalBookings;
:
ABDocument implementation
#import "ABDocument.h"
#import "ABWindowController.h"
#implementation ABDocument
#synthesize newDocument = ivNewDocument;
#synthesize totalBookings = ivTotalBookings;
#synthesize fetchedTransactions = ivFetchedTransactions;
:
/** #brief Create one instance of my custom NSWindowController subclass (ABWindowController)
*
* In my NSPersistentDocument I do override makeWindowControllers, where I create
* one instance of my custom NSWindowController subclass and use addWindowController:
* to add it to the document.
*
*/
- (void) makeWindowControllers
{
// Existing Document?
if ( ![self isNewDocument]) {
// NSLog(#"%#:%# OPENING EXISTING DOCUMENT", [self class], NSStringFromSelector(_cmd));
// Opening existing document (also has an existing DDBB (SQLite)), so
// make sure I do perform a first complete "fetch + #sum" to void issues
// with my NIB bind's.
[self firstFetchPreventsProblems];
}
// Now I can create the Window Controller using my "MainWindow.xib".
ABWindowController *windowController = [[ABWindowController alloc] init];
[self addWindowController:windowController];
[windowController release];
}
/** #brief First complete "fetch + #sum" to void issues with my NIB bind's.
*
* Before I create the Window Controller with "MainWindow.xib" I have to perform a
* first Fetch AND also retrieve a #sum of an NSNumber column.
*
* My NIB has an NSTextField BOUND to #arrangedObjects.#sum.<property> through a NSArrayController
* If I don't call this method before the NIB is loaded, then the program will crash.
*
*/
- (void) firstFetchPreventsProblems {
// Prepare the Fetch
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Transaction"];
// 1) Perform the Fetch
NSError *error = nil;
[self setFetchedTransactions:[[self managedObjectContext ] executeFetchRequest:request error:&error]];
if ([self fetchedTransactions] == nil)
{
NSLog(#"Error while fetching\n%#",
([error localizedDescription] != nil) ? [error localizedDescription] : #"Unknown Error");
exit(1);
}
// 2) Execute Collection Operation #sum
[self setTotalBookings:[[self fetchedTransactions] valueForKeyPath:#"#sum.t_24_Bookings"]];
}
ABWindowController (The controller that loads my NIB)
- (void)windowDidLoad
{
:
// PROGRAM CRASH HERE
// IF [self firstFetchToPreventsProblems]; is NOT CALLED
// ABDocument's "makeWindowControllers:"
[[self totalSumField] bind: #"value" toObject: [self out_CtEn_Transaction]
withKeyPath:#"arrangedObjects.#sum.t_24_Bookings" options:nil];
}
Please If you can comment really appreciated, I've got a solution but I don't understand why.
Tanks,
Luis
I found the problem myself after several days researching. It was easy (now that I know):
In parallel I was creating a secondary thread and happened that I was accessing the data model from two different threads. As it's been explained in several Q&As here in Stackoverflow, it's very dangerous.
I've applied the commented solutions in several posts of creating a secondary MOC in the secondary thread.
Now my code is thread safe as per coredata related actions, so program is not crashing.
Thanks again to the community.
Luis

Objective-C & KeyValueCoding: How to avoid an exception with valueForKeyPath:?

I've got an object of type id and would like to know if it contains a value for a given keyPath:
[myObject valueForKeyPath:myKeyPath];
Now, I wrap it into a #try{ } #catch{} block to avoid exceptions when the given keypath isn't found. Is there a nicer way to do this? Check if the given keypath exists without handling exceptions?
Thanks a lot,
Stefan
You could try this:
if ([myObject respondsToSelector:NSSelectorFromString(myKeyPath)])
{
}
However, that may not correspond to the getter you have, especially if it is a boolean value. If this doesn't work for you, let me know and I'll write you up something using reflection.
For NSManagedObjects, an easy solution is to look at the object's entity description and see if there's an attribute with that key name. If there is, you can also take it to the next step and see what type of an attribute the value is.
Here's a simple method that given any NSManagedObject and any NSString as a key, will always return an NSString:
- (NSString *)valueOfItem:(NSManagedObject *)item asStringForKey:(NSString *)key {
NSEntityDescription *entity = [item entity];
NSDictionary *attributesByName = [entity attributesByName];
NSAttributeDescription *attribute = attributesByName[key];
if (!attribute) {
return #"---No Such Attribute Key---";
}
else if ([attribute attributeType] == NSUndefinedAttributeType) {
return #"---Undefined Attribute Type---";
}
else if ([attribute attributeType] == NSStringAttributeType) {
// return NSStrings as they are
return [item valueForKey:key];
}
else if ([attribute attributeType] < NSDateAttributeType) {
// this will be all of the NSNumber types
// return them as strings
return [[item valueForKey:key] stringValue];
}
// add more "else if" cases as desired for other types
else {
return #"---Unacceptable Attribute Type---";
}
}
If the key is invalid or the value can't be made into a string, the method returns an NSString error message (change those blocks to do whatever you want for those cases).
All of the NSNumber attribute types are returned as their stringValue representations. To handle other attribute types (e.g.: dates), simply add additional "else if" blocks. (see NSAttributeDescription Class Reference for more information).
If the object is a custom class of yours, you could override valueForUndefinedKey: on your object, to define what is returned when a keypath doesn't exist.
It should be possible to graft this behavior onto arbitrary classes reasonably simply. I present with confidence, but without warranty, the following code which you should be able to use to add a non-exception-throwing implementation of valueForUndefinedKey: to any class, with one, centralized line of code per class at app startup time. If you wanted to save even more code, you could make all the classes you wanted to have this behavior inherit from a common subclass of NSManagedObject and then apply this to that common class and all your subclasses would inherit the behavior. More details after, but here's the code:
Header (NSObject+ValueForUndefinedKeyAdding.h):
#interface NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler;
#end
Implementation (NSObject+ValueForUndefinedKeyAdding.m):
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import <objc/runtime.h>
#import <objc/message.h>
#implementation NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler
{
Class clazz = self;
if (clazz == nil)
return;
if (clazz == [NSObject class] || clazz == [NSManagedObject class])
{
NSLog(#"Don't try to do this to %#; Really.", NSStringFromClass(clazz));
return;
}
SEL vfuk = #selector(valueForUndefinedKey:);
#synchronized([NSObject class])
{
Method nsoMethod = class_getInstanceMethod([NSObject class], vfuk);
Method nsmoMethod = class_getInstanceMethod([NSManagedObject class], vfuk);
Method origMethod = class_getInstanceMethod(clazz, vfuk);
if (origMethod != nsoMethod && origMethod != nsmoMethod)
{
NSLog(#"%# already has a custom %# implementation. Replacing that would likely break stuff.",
NSStringFromClass(clazz), NSStringFromSelector(vfuk));
return;
}
if(!class_addMethod(clazz, vfuk, handler, method_getTypeEncoding(nsoMethod)))
{
NSLog(#"Could not add valueForUndefinedKey: method to class: %#", NSStringFromClass(clazz));
}
}
}
#end
Then, in your AppDelegate class (or really anywhere, but it probably makes sense to put it somewhere central, so you know where to find it when you want to add or remove classes from the list) put this code which adds this functionality to classes of your choosing at startup time:
#import "MyAppDelegate.h"
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import "MyOtherClass1.h"
#import "MyOtherClass2.h"
#import "MyOtherClass3.h"
static id ExceptionlessVFUKIMP(id self, SEL cmd, NSString* inKey)
{
NSLog(#"Not throwing an exception for undefined key: %# on instance of %#", inKey, [self class]);
return nil;
}
#implementation MyAppDelegate
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MyOtherClass1 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass2 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass3 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
});
}
// ... rest of app delegate class ...
#end
What I'm doing here is adding a custom implementation for valueForUndefinedKey: to the classes MyOtherClass1, 2 & 3. The example implementation I've provided just NSLogs and returns nil, but you can change the implementation to do whatever you want, by changing the code in ExceptionlessVFUKIMP. If you remove the NSLog, and just return nil, I suspect you'll get what you want, based on your question.
This code NEVER swizzles methods, it only adds one if it's not there. I've put in checks to prevent this from being used on classes that already have their own custom implementations of valueForUndefinedKey: because if someone put that method in their class, there's going to be an expectation that it will continue to get called. Also note that there may be AppKit code that EXPECTS the exceptions from the NSObject/NSManagedObject implementations to be thrown. (I don't know that for sure, but it's a possibility to consider.)
A few notes:
NSManagedObject provides a custom implementation for valueForUndefinedKey: Stepping through its assembly in the debugger, all it appears to do is throw roughly the same exception with a slightly different message. Based on that 5 minute debugger investigation, I feel like it ought to be safe to use this with NSManagedObject subclasses, but I'm not 100% sure -- there could be some behavior in there that I didn't catch. Beware.
Also, as it stands, if you use this approach, you don't have a good way to know if valueForKey: is returning nil because the keyPath is valid and the state happened to be nil, or if it's returning nil because the keyPath is invalid and the grafted-on handler returned nil. To do that, you'd need to do something different, and implementation specific. (Perhaps return [NSNull null] or some other sentinel value, or set some flag in thread-local storage that you could check, but at this point is it really all that much easier than #try/#catch?) Just something to be aware of.
This appears to work pretty well for me; Hope it's useful to you.
There's no easy way to solve this. Key Value Coding (KVC) isn't intended to be used that way.
One thing is for sure: using #try-#catch is really bad since you're very likely to leak memory etc. Exceptions in ObjC / iOS are not intended for normal program flow. They're also very expensive (both throwing and setting up the #try-#catch IIRC).
If you look at the Foundation/NSKeyValueCoding.h header, the comment / documentation for
- (id)valueForKey:(NSString *)key;
clearly states which methods need to be implemented for -valueForKey: to work. This may even use direct ivar access. You would have to check each one in the order described there. You need to take the key path, split it up based on . and check each part on each subsequent object. To access ivars, you need to use the ObjC runtime. Look at objc/runtime.h.
All of this is vary hacky, though. What you probably want is for your objects to implement some formal protocol and then check -conformsToProtocol: before calling.
Are your key paths random strings or are those strings under your control? What are you trying to achieve? Are you solving the wrong problem?
I don't believe this is possible in a safe way (i.e. without mucking with -valueForUndefinedKey: or something similar on other peoples' classes). I say that because on the Mac side of things, Cocoa Bindings—which can be set to substitute a default value for invalid key paths—simply catches the exceptions that result from bad key paths. If even Apple's engineers don't have a way to test if a key path is valid without trying it and catching the exception, I have to assume that such a way doesn't exist.