How do I check whether an element exists in the UI hierarchy? I dont want EarlGrey to fail if an element does not exist but just check if its there, I need this for UITableView where I must constantly scroll to top and scroll down searching an element but the element is sometimes already on the current view to begin with.
EarlGrey supports Error API: when EarlGrey APIs take an NSError reference the APIs will not throw when they fail instead they fill the error reference with the corresponding error. For example, use this code to check if an element exists in the view hierarchy (and act on that information):
NSError *error;
[[EarlGrey selectElementWithMatcher:grey_fooElementMatcher()]
assertWithMatcher:grey_notNil() error:&error];
if (error && [error.domain isEqual:kGREYInteractionErrorDomain] &&
error.code == kGREYInteractionElementNotFoundErrorCode) {
// Element doesn’t exist.
} else if (!error) {
// Element exists.
}
Related
In EarlGrey how can I access the element object that I am interacting with, for example I want access to UIButton that I just tapped.
EarlGrey does not return (pointers to) elements as the view/element returned may not be around after the interaction is complete, moreover it can lead to hard to detect errors as holding strong references to views can modify the view's lifecycle. You can however get access to the element while the interaction is happening using GREYActionBlock.
- (void)testFoo {
[[EarlGrey selectElementWithMatcher:grey_FooElementMatcher()]
performAction:[GREYActionBlock actionWithName:#"bar"
performBlock:^(id element, NSError *__strong *errorOrNil) {
[element doWhatever];
return YES; // Return YES for success, NO for failure.
}
]];
}
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:.
I'm running a data model in one of my apps, where an event has an "eventType" relationship defined. This allows me to modify the look and feel of multiple events by changing their "eventType" relationship object.
The problem that I'm running into is that before I insert an event, I check if a typeRelationship for this object is present with the code below. This takes some time if I need to insert a large number of objects.
Can I cache the results of this fetch request (for example in NSMutableDictionary) and check that dictionary (local memory) to see if there is an NSManagedObject with the given EventIDEnum? Can I keep the cache alive forever, or will the underlying objects get "out of date" after a while?
-(Event*)insertAndReturnNewObjectWithTypeID:(EventIDEnum)eventTypeID date:(NSDate*)date
{
NSFetchRequest *eventTypesArray = [NSFetchRequest fetchRequestWithEntityName:#"EventType"];
eventTypesArray.predicate = [NSPredicate predicateWithFormat:#"SELF.id == %d", eventTypeID];
NSArray *eventTypes = [[DataManager sharedInstance].managedObjectContext executeFetchRequest:eventTypesArray error:nil];
if(eventTypes.count==0)
{
DLog(#"ERROR inserting event with type: %i NOT FOUND",(int)eventTypeID);
return nil;
}
else {
if(eventTypes.count !=1)
{
DLog(#"ERROR found %i events with type %i",eventTypes.count,(int)eventTypeID);
}
EventType* eventType = [eventTypes lastObject];
if(date)
{
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType date:date];
}else {
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType];
}
}
}
Thank you for taking a look at my question!
The array of objects returned by a fetch request cannot be cached. They are only valid as long as the NSManagedObjectContext that was used to query them has not been released. The NsManagedObject.objectID and the data you retrieve from the query can be cached and kept for as long as you like. You are probably better off copying the pertinent data and objectIDs into another object you cache and maintain separately from CoreData objects; and releasing the core data array that was returned by the fetch request.
The pattern you're using is often referred to as "find or create": look for an object whose uniquing characteristic matches, return it if it exists, create/populate/return it if it didn't exist.
One thing you can do to speed this up is to do the uniquing outside of Core Data. If it's possible based on your data, perhaps you can iterate over your EventIDEnum values, find the unique values you need to have available, and thus reduce the number of fetches you perform. You'll only search once for each EventIDEnum. As long as you're working within one thread/context, you can cache those.
When I'm writing this kind of code, I find it helpful to pass in the NSManagedObjectContext as a parameter. That allows me to use the find-or-create or bulk insert methods anywhere, either on the main thread or within a private queue/context. That would take the place of your [[DataManager sharedInstance] managedObjecContext] call.
I have a problem regarding core data.
I have an app with multiple tabs. Every tab holds a list of different "objects".
In each tab i have an add button (+) that takes me to a screen where i can add an "object".
The problem :
When i click add an entity for insert is being created and added to the context.
So, i go on first tab, click add - an entity is in context.
I go on other tab, i click add - another entity is in context.
I finish adding information for one of them ( to be valid ) and hit save.
Now core data throws an exception saying that could not save the context because the other entity it's not valid.
One idea that came into my mind was :
Copy all objects that are invalid from context, save the context, add the copied ones back (and so on when needed).
But an entity could have other relations with other entities so Person could have address, role, Contact Details.. and Company could have Address, Employes .. etc.
If person is invalid and has relationship Address valid , above idea fails because will not save person but will save address , what it's not correctly.
Something with a temporarily context could work but i don't have an clear idea how to implement this.
Another idea is to take all relationships for an entity (eg Person) when i want to save it, and save only Person.
But i failed to implement an recursive function ( the problem lays in the fact that relationships are inverse and because of many many relationships on my coredata model (person has contact details and also company) ).
Here is the code :
- (void)relationshipInstancesForManagedObject:(NSManagedObject *)managedObject
andSkipValue:(id)skipValue
andSet:(NSMutableSet *)set
{
for (NSRelationshipDescription *relationship in managedObject.entity.properties)
{
if (![relationship isKindOfClass:[NSRelationshipDescription class]]) continue;
id value = [managedObject valueForKey:relationship.name];
if (value == skipValue || value == nil || value == self || [set containsObject:value] ) continue;
NSLog(#"%#",value);
if (relationship.isToMany)
{
if ([value containsObject:skipValue] || [value containsObject:managedObject] || [value count] == 0 ) {
continue;
}
for (NSManagedObject *entity in value)
{
[set addObject:entity];
[self relationshipInstancesForManagedObject:entity
andSkipValue:skipValue
andSet:set];
}
} else {
if (value != nil) {
[set addObject:value];
[self relationshipInstancesForManagedObject:value
andSkipValue:skipValue
andSet:set];
}
}
}
}
If you have any ideas, I would be grateful.
The simplest solution to your problem is to make sure that all objects are valid when you add them to the store. Collect the information you need to create a valid object first, and only then add a new object along with all the required information.
Also, consider carefully whether you need to require all the properties that you currently do. If you have two entities that each have a relationship to the other, do you need to require both relationships? Could you make one of the relationships optional? That can help you avoid a chicken and egg problem where it's impossible to add objects for either entity because none of the other type exist yet.
I'm working with a subclass of NSManagedObject. Actually, it inherits from a class that inherits from a class that itself inherits from NSManagedObject (that shouldn't be a problem, right?).
The problem
After I make changes to the properties of the object, the object remembers the changes for its lifetime, but the changes are never saved to the database.
How Do I Know This?
I know this because:
when I restart the app, the changes I've made are lost.
telling the context to refresh the object – AFTER I've made changes to the object and told the context to save – sets the object's values back to their original state before I made the changes.
when running the app in the simulator, I can look at the sqlite database file in the Finder, and it's modified date isn't updated when I attempt to save the context.
Nothing is being written to the database!
Context
I'm using the auto-generated delegate methods to create the store coordinator and the context. Then I'm passing the context to the view controllers in their init methods, as recommended in the docs. The store is SQLite.
I am able to successfully insert objects into the database and read them. I can even make property changes to the newly inserted object and save it successfully. I simply don't seem to be able to update object properties when the object is pulled back out of the database.
The object is fetched from the store via a relationship from another object. After making changes to its properties, I call the context's save method. However, before doing so, I call the object's isUpdated method and the context's hasChanges method, and both return false. Shouldn't they return true since I've just made changes to the object's properties but haven't saved the context?
More
If I call the object's committedChanges method before saving the context, however, passing in the names of the properties that I've changed, I get back the correct values of the properties. I'm not sure what this means. I would have thought that this means that the object's new property values have been successfully saved, but clearly they are not saved.
I know that the result objects is registered with a context. If I call
[[result managedObjectContext] refreshObject:result mergeChanges:YES];
the object reverts back to the original property values. This means that the context is there and that it is the same context from which the record was fetched. And it means that the new property values are never written tot he database.
Some Code
Here's the code where I'm poking around with all of these things. There are other places in my code where I'm making property changes, but the changes are never saved.
- (IBAction)statusControlChanged:(UISegmentedControl *)control {
WCAAssessmentResult *result = [self currentResult];
/* printing the existing property values */
if (![result.complete boolValue]) NSLog(#"result is in progress!");
else if ([result.passed boolValue]) NSLog(#"result is passed!");
else NSLog(#"result is not passed!");
/* changing the property values */
switch (control.selectedSegmentIndex) {
case 0:
NSLog(#"setting incomplete");
result.complete = [NSNumber numberWithBool:NO];
break;
case 1:
NSLog(#"setting passed");
result.passed = [NSNumber numberWithBool:YES];
result.complete = [NSNumber numberWithBool:YES];
break;
case 2:
NSLog(#"setting failed");
result.passed = [NSNumber numberWithBool:NO];
result.complete = [NSNumber numberWithBool:YES];
break;
default:
break;
}
/* this method always returns an empty dictionary */
NSLog(#"%#", [result changedValues]);
/* this method returns the values that I just set */
NSLog(#"%#", [result committedValuesForKeys:[NSArray arrayWithObjects:#"complete", #"passed", nil]]);
/* isUpdated returns false */
if (![result isUpdated]) {
NSLog(#"result is not updated?! WTF!?!?");
}
/* hasChanges returns false */
if (![[result managedObjectContext] hasChanges]) {
NSLog(#"context has no changes!? WTF!?!?");
}
/* saving the context produces no error */
NSError *error = nil;
if (![[result managedObjectContext] save:&error]) {
NSLog(#"save failed");
NSLog(#"%#",[error description]);
}
}
A Twist
If I create a new result object by inserting a new record into the context, I can set that object's properties and they are saved successfully. In the above code, I'm fetching the object as a member of a to-many association from another object. Is that a clue?
I'm tearing my hair out over this. What the hell could be going wrong here?
What's NOT The Problem
I've logged the object's class, and it is indeed the correct class
I've made sure that the managedObjectContext I'm saving is the same as the object's context
I haven't made any changes to the auto-generated setter/getter methods of my managed object classes
I've tried using the setValue:forKey: method instead of object's properties
I've used the -com.apple.CoreData.SQLDebug 1 argument to log Core Data SQL, and no SQL is logged when I update and save the object's properties
I do not really understand your statement
WCAAssessmentResult *result = [self currentResult];
Indeed, if you are accessing a to-many relationship from an object, you should get back a set, not an object. Anyway, without seeing the code it's hard to tell. The problem you are experiencing may or may not lie there.
I would rather expect in your code something like the following snippet to access objects belonging to a to-many relationship. I assume that yourObject is the object you use to access the WCAAssessmentResult objects in the to-many relationship, which I call results.
NSMutableSet *resultObjects = [yourObject mutableSetValueForKey:#"results"];
NSPredicate *predicate = ...
[resultObjects filterUsingPredicate:predicate];
for(WCAAssessmentResult *result in resultObjects){
// modify as needed the current result object
}
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"save failed");
NSLog(#"%#",[error description]);
}
Did you verify that the managedObjectContext you are using to save the object is valid (not nil) ?
Some ideas in no particular order:
I would log the class of the result object and make sure it is the class you think it is. Some confusion with super/sub classes could result in certain values not being saved.
If you made any alterations in the setter/getter methods in any class in the hierarchy, look closely at those methods especially if you used a primativeValue method. Simply leaving out willChangeValue and didChangeValue can cause changes to be invisible to the context and sometimes to the object itself.
I would log the context you are saving as well as managedObjectContext property of the result object. Confirm they are indeed the same context.
Skip using the property accessors (the dot notation) and use setValue:forKey and see if that makes any difference. If so, you have an accessor problem. Likewise, you could try setPrimativeValue:forKey to also check for an accessor problem.
If I had to bet, I would put my money on you assigning the objects returned by a fetch to the wrong class.