replacing objects in array while enumerating? - ios4

I am sending an update for my app that will require that the users databases be updated. I am storing data in a property list. Basically a every point in the array are NSMutableDictionaries and I need to add keys, replace keys etc.
I attempted the following, however it generates an NSException,
for (NSMutableDictionary *dict in myArray) {
if ([dict objectForKey:#"someKey"] == nil) {
//Extract the value of the old key and remove the old key
int oldValue = [[dict objectForKey:#"key1"] intValue];
[dict removeObjectForKey:#"key1"];
[dict setValue:[NSString stringWithFormat:#"%d pts", oldValue] forKey:#"newKey"];
//Add new keys to dictionnary
[dict setValue:#"some value" forKey:#"key2"];
[dict setValue:#"some value" forKey:#"key3"];
[dict setValue:#"some value" forKey:#"key4"];
[self.myArray replaceObjectAtIndex:index withObject:dict];
}
What should I do to update my data in the above manner?

The problem is that you cannot modify the array you are iterating over with fast enumeration.
The code snippet has no need for that replaceObjectAtIndex:withObject: call at all, as you replace the object with the very same object! So if you remove that line everything should work.
Generally, you can avoid similar problems if you use the plain old for loop with indexing, i.e.
for (int i = 0; i < [array count]; i++) {
id obj = [array objectAtIndex:i];
// ...
}
as this will not mess up with fast enumeration.

Create a copy of the array and enumerate through the copy. In this way you can safely modify the original one:
for (id obj in [NSArray arrayWithArray:entries]) {
[entries removeObject:obj];
}
Do not use:
for (int i = 0; i < [array count]; i++) {
id obj = [array objectAtIndex:i];
[array removeObject:obj];
}
Do this because, after the removal the array indexes will be offset!

First, make sure that myArray is an NSMutableArray. If so, you'll probably see some error if you debug the code that says something like _NSArrayI unrecognized selector sent to instance _NSArrayI means it's an immutable array. This is very annoying, but try to test by doing this. You can then just replace your myArray with the mutableArray.
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:self.myArray];
for (NSMutableDictionary *dict in mutableArray) {
if ([dict objectForKey:#"someKey"] == nil) {
//Extract the value of the old key and remove the old key
int oldValue = [[dict objectForKey:#"key1"] intValue];
[dict removeObjectForKey:#"key1"];
[dict setValue:[NSString stringWithFormat:#"%d pts", oldValue] forKey:#"newKey"];
//Add new keys to dictionnary
[dict setValue:#"some value" forKey:#"key2"];
[dict setValue:#"some value" forKey:#"key3"];
[dict setValue:#"some value" forKey:#"key4"];
[mutableArray replaceObjectAtIndex:index withObject:dict];
}
}

Related

Fetching objects form core data by IndexPath

I have a TableView. By tapping an entry in this table, another view opens in which that entry is editable.
What's the proper way to handle this? I am aware that I can fetch entries from Core Data via predicates like this:
func fetchEntriesStartingWith(startingDate:Date) -> [FuelEntry] {
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "FuelEntry")
fetchRequest.predicate = NSPredicate(format: "date >= %#", startingDate as CVarArg)
let sort = NSSortDescriptor(key: #keyPath(FuelEntry.date), ascending: false)
fetchRequest.sortDescriptors = [sort]
do {
return try (CoreDataHandler.managedContext.fetch(fetchRequest) as? [FuelEntry] ?? [FuelEntry]())
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
However, this seems error prone: What if I have two entries with the exact same values (might happen in my application) and thus, the NSFetchRequest returns two entries?
Obviously, those two entries will have different IDs, but I'm unsure how to work with those and infer which entry will correspond to what indexPath.

CFRelease in for loop error: Incorrect decrement of the reference count of an object that is not owned at this point by the caller

When I release in for loop, Analyze show error:
Incorrect decrement of the reference count of an object that is not
owned at this point by the caller
What am I doing wrong?
for (size_t i = 0; i < frameCount; ++i) {
NSDictionary *dict = (NSDictionary*)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(gifSource, i, NULL));
NSDictionary *gifDict = [dict valueForKey:(NSString*)kCGImagePropertyGIFDictionary];
[delayTimes addObject:[gifDict valueForKey:(NSString*)kCGImagePropertyGIFDelayTime]];
totalTime = totalTime + [[gifDict valueForKey:(NSString*)kCGImagePropertyGIFDelayTime] floatValue];
CFRelease((__bridge CFTypeRef)(dict)); //Analyze showgin error here.
}
id __nullable CFBridgingRelease(CFTypeRef CF_CONSUMED __nullable X) {
return (__bridge_transfer id)X; }
I think I don't need CFRelease(dict) because I've transferred ownership to ARC in the __bridge_transfer

LHS and RHS both keypaths

Trying to find active guestCards where a prospectiveTenant matches all of the search queries. Possibilities are firstName, lastName, phoneNumber.
prospectiveTenants is a to-many on guestCard.
This code:
NSString *predicateString = [NSString stringWithFormat: #"active=1"];
if (self.searchHeader.firstNameTextField.text.length > 0) {
predicateString = [predicateString stringByAppendingFormat:#" AND (ANY prospectiveTenants.firstName CONTAINS[cd] %#)", self.searchHeader.firstNameTextField.text];
}
if (self.searchHeader.lastNameTextField.text.length > 0)
{
predicateString = [predicateString stringByAppendingFormat:#" AND (ANY prospectiveTenants.lastName CONTAINS[cd] %#)", self.searchHeader.lastNameTextField.text];
}
if (self.searchHeader.phoneNumberTextField.text.length > 0)
{
predicateString = [predicateString stringByAppendingFormat:#" AND (ANY prospectiveTenants.phone CONTAINS[cd] %#)", self.searchHeader.phoneNumberTextField.text];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
[self.fetchedResultsController.fetchRequest setPredicate:predicate];
produces this error
'unimplemented SQL generation for predicate : (ANY prospectiveTenants.lastName CONTAINS[cd] S) (LHS and RHS both keypaths)'
Gah.
So it looks like predicateWithFormat does special work to make sure quotes are inserted around strings. Should have been obvious.
In order to be able to use the work the NSPredicate init does, try building up the format string and the values to be placed in the string as two variables and then pass them both when creating the NSPredicate.
This has the advantage of taking care of any other special work NSPredicate might like to do.
Re-writing in Swift, this would become:
var predicateString = "active=1"
var predicateParameters = [Any]()
if !self.searchHeader.firstNameTextField.text.isEmpty {
predicateString.append(" AND (ANY prospectiveTenants.firstName CONTAINS[cd] %#)")
predicateParameters.append(self.searchHeader.firstNameTextField.text)
}
if !self.searchHeader.lastNameTextField.text.isEmpty {
predicateString.append(" AND (ANY prospectiveTenants.lastName CONTAINS[cd] %#)")
predicateParameters.append(self.searchHeader.lastNameTextField.text)
}
if !self.searchHeader.phoneNumberTextField.text.isEmpty {
predicateString.append(" AND (ANY prospectiveTenants.phone CONTAINS[cd] %#)")
predicateParameters.append(self.searchHeader.phoneNumberTextField.text)
}
let predicate = NSPredicate(format: predicateString, argumentArray:predicateParameters)
self.fetchedResultsController.fetchRequest.predicate = predicate

replaceObjectAtIndex:i withObject:str is giving bad access error (also with remove object - even do i can addObject atIndex

I'm trying to replace a string in an array(update array) with a string found at an index of array(other array). Both arrays are initialized in init and i can nslog them in this method. if [str isEqualToString:[otherArray objectAtIndex:i] i want to return true/YES
but the line: [updateArray replaceObjectAtIndex:i withObject:str]; is giving bad access to program. (its only a console program) "Program received signal: “EXC_BAD_ACCESS”.
any suggestions appreciated, thanks trev
-(BOOL)checker:(NSString *)str {
NSLog(#"update arrayobject at %d is %#",0, [updateArray objectAtIndex:0]);
NSLog(#"otherArray size is %d",[otherArray count]);
NSLog(#"str iput is %#",str);
int i;
for (i=0; i<[otherArray count]; i++) {
if ([str isEqualToString:[otherArray objectAtIndex:i]]) {
[updateArray replaceObjectAtIndex:i withObject:str];
return TRUE;
}
}
return FALSE;
}
once check your array is it allocated or not properly..i think there is nothing more than that thing..

Cocoa Bindings: Binding to the "many" end of a to-many relationship

Using the Employees-Departments example what I want to do is bind a column to "Departments.arrangedObjects.employees.#sum.hoursWorked" as outlined below:
Entity Employee
attributes: "firstName", "lastName", "hoursWorked"
relationship: "departments"
Entity Department
attributes: "name"
relationship: "employees"
I want a table which will display some summary info about departments.
I bind the first column to my "Departments" array controller, "arrangedObjects.name".
I can have a column displaying the number of employees in a department by binding to "arrangedObjects.employees.#count"
However I can't get a sum of the hoursWorked by employees as I assume I might by binding to "arrangedObjects.employees.#sum.hoursWorked"
The error I get is along the lines of "[<_NSFaultingMutableSet 0x1acea0> addObserver:forKeyPath:options:context:] is not supported. Key path: #sum.hoursWorked"
I believe this is because it is not possible to bind to the many end of a to-many relationship. If so how can I do what I want to do?
For extra credit, say each employee also has another attribute, "race", I would also like my summaries table to show the number of unique races in each department.
Thanks in advance.
I encountered the same errors you did. It seems that while you can get the set of employees and perform some set of aggregate operations on it by doing something like:
Department* dept = ;
NSSet* employees = dept.employees;
NSNumber* sumOfHoursWorked = [employees valueForKeyPath: #"#sum.hoursWorked"];
There's a difference when you bind. Bindings are asking to observe the key path, not evaluate it once. Given that, it kinda, sorta makes sense why you can't bind to these key paths. Kinda. Sorta.
Now. As for a solution, what I usually do in cases like this is write a tiny little NSValueTransformer subclass to do just what I need, and then plug that into IB. This way, I write the ten lines of code I need, but don't end up doing the whole NSTableView data source spiel for want of a simple aggregate. In this case, you might do something like this:
// Declaration
#interface MySumOfHoursWorkedTransformer : NSValueTransformer
#end
#interface MyNumberOfRacesTransformer : NSValueTransformer
#end
// Implementation
#implementation MySumOfHoursWorkedTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
double hoursWorked = 0.0;
for (Employee* employee in [value valueForKey: #"employees"])
{
NSNumber* hoursWorkedNumber = employee.hoursWorked;
hoursWorked += hoursWorkedNumber ? [hoursWorkedNumber doubleValue] : 0.0;
}
retVal = [NSNumber numberWithDouble: hoursWorked];
}
return retVal;
}
#end
#implementation MyNumberOfRacesTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
NSMutableSet* raceSet = [NSMutableSet set];
for (Employee* employee in [value valueForKey: #"employees"])
{
id raceVal = employee.race;
if (raceVal)
[raceSet addObject: raceVal];
}
retVal = [NSNumber numberWithUnsignedInteger: raceSet.count];
}
return retVal;
}
#end
Then, just bind those TableColumns to ArrayController.arrangedObjects and plug in the appropriate value transformer subclass. Now, you won't be able to edit those values, but what would it mean to edit an aggregate value anyway?
Hope that helps. I've used this approach a bunch, and it sure beats giving up on bindings.

Resources