Removing Objects From NSMutableArray - objective-c

I have a NSMutableArray that contains all the calendars on my system (as CalCalendar objects):
NSMutableArray *calendars = [[CalCalendarStore defaultCalendarStore] calendars];
I want to remove from calendars any CalCalendar objects whose title does not include the string #"work".
I've tried this:
for (CalCalendar *cal in calendars) {
// Look to see if this calendar's title contains "work". If not - remove it
if ([[cal title] rangeOfString:#"work"].location == NSNotFound) {
[calendars removeObject:cal];
}
}
The console is complaining that:
*** Collection <NSCFArray: 0x11660ccb0> was mutated while being enumerated.
And things go bad. Obviously it would seem you can't do what I want to do this way so can anyone suggest the best way to go about it?
Thanks,

While you can not remove items in an array that you are using fast enumeration on, you have some options:
filter the array using -filterUsingPredicate:
use index-based iteration
remove via index sets, e.g. using -indexesOfObjectsPassingTest:
build an array of the objects to remove and use e.g. -removeObjectsInArray:
As markhunte noted, -calendars doesn't neccessarily return a mutable array - you'd have to use -mutableCopy to get a mutable array which you can filter:
NSMutableArray *calendars = [[[[CalCalendarStore defaultCalendarStore]
calendars] mutableCopy] autorelease];
... or e.g. -filteredArrayUsingPredicate: for a immutable filtered copy.
NSArray *calendars = [[CalCalendarStore defaultCalendarStore] calendars];
calendars = [calendars filteredArrayUsingPredicate:myPredicate];

You can not change an array/list you are enumerating (in any language I know of). You will need to create a second list that you will add the calendars that you want to remove, to. Then iterate round the second list, removing the objects from the first. You can then dispose of the second list leaving just the original list with only the calendars you wish to keep hold of.

I noticed NSMutableArray will give the expected results from [[CalCalendarStore defaultCalendarStore] calendars]].
But the returned array from [[CalCalendarStore defaultCalendarStore] calendars]] is actually a NSArray and not an NSMutableArray. (also has a warning come up about expected structure)
This is all new to me so am I missing something here?
or is this the correct way of going about this task..
NSMutableArray *workCals= [[NSMutableArray alloc] initWithCapacity:2];
[workCals addObjectsFromArray: [[CalCalendarStore defaultCalendarStore] calendars]];
NSString *title = #"work";
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"title contains[c] %#",title ];
[workCals filterUsingPredicate:predicate];
NSLog(#"workCals %#",workCals );
[workCals release];
Cheers.

Related

Efficiency + safety: declare new NSArray var, or overwrite existing NSMutableArray with mutableCopy?

I need to sort a mutable array, but in this specific case when it comes time to sort, I don't need it to be mutable anymore. The sortedArrayUsingSelector: method returns an NSArray * even when called by an NSMutableArray * object. I have 3 options:
1) I can make a mutableCopy of the returned NSArray * and store it in the var I already have
NSMutableArray *mutableArray = [NSMutableArray array];
// add a bunch of stuff to the array
mutableArray = [[mutableArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] mutableCopy];
2) I can make a new NSArray * var to hold the returned NSArray *
NSMutableArray *mutableArray = [NSMutableArray array];
// add a bunch of stuff to the array
NSArray *array = [mutableArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
3) I can ignore Xcode's warnings and store the returned NSArray * object in an NSMutableArray * var
NSMutableArray *mutableArray = [NSMutableArray array];
// add a bunch of stuff to the array
mutableArray = [mutableArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I realize that for most cases the difference is negligible, but considering available resources, efficiency, and safety, which would be the overall "best" option?
EDIT: I hadn't considered that the best option might be to create a non-mutable array from the mutable one before sorting it. Not sure if this would be, in fact, the best option, but something I figured I'd mention.
Since you have a mutable array already, just use the following method on NSMutableArray:
- (void)sortUsingSelector:(SEL)comparator
That way you're not recreating anything. This is likely more efficient than creating a new, sorted array from the original and then creating a mutable copy of that. Part of the point of having a mutable array in the first place is that you can change stuff around without needing to recreate it each time, so sorting is a very obvious thing to have here.
The array you get back from sortedArrayUsingSelector won't be a deep copy - it will contain the same pointers as the original array, just in a different order. These array methods in foundation will be well optimized, so I wouldn't worry about it too much.
Instead, just decide what you want. If you want a sorted NSArray * that won't look like an NSMutableArray *, just use sortedArrayUsingSelector, which returns an NSArray *
If you don't need the original unsorted array anymore, just sort the mutable array, like #Gavin suggests.

Filtering objects from array by a property, using the method filteredArrayUsingPredicate

I can't understand how to use predicate, I have a very long code to filter objects from array by property "type" and suddenly I saw method "filteredArrayUsingPredicate" that can make my life better. I try write predicates but I always get errors; can someone show me how to write it right?
I have method - (void) filterData: (NSString *)filteredWord:
I also have array with objects (Event): NSArray *eventsArray.
I want use a filteredArrayUsingPredicate to get new array with objects (Event) where their property (type) is equal filterWord. Note that Event is Core Data Managed subclass.
Is it even possible to do this with predicate?
One of my attempts:
NSString *propertyName = #"type";
NSArray *eventsArray = [[[[self currentPerson] events] objectEnumerator]allObjects];
NSPredicate *predicte = [NSPredicate predicateWithFormat:#"%k like '%#'",propertyName,filteredWord];
[eventsArray filteredArrayUsingPredicate:predicte];
Try this:
NSString *propertyName = #"type";
NSArray *eventsArray = [[[self currentPerson] events] allObjects];
NSPredicate *predicte = [NSPredicate predicateWithFormat:
#"%k like %#",propertyName, filteredWord];
NSArray *filteredArray = [eventsArray filteredArrayUsingPredicate:predicte];
You ignore the filtered result. The filteredArrayUsingPredicate:
returns a new instance of NSArray. It doesn't filter the original
array in place, because NSArray objects are immutable. You either
have to make an
NSMutableArray
and then use filterUsingPredicate: to filter the array in place, or
you have to do something with the returned array of
filteredArrayUsingPredicate: (log it, save it etc...)
Don't use single quotes around the property. (thanks for the clue #MartinR)

How to copy NSArray to another NSArray?

I have many different NSArrays, and according to the users choice I want one of them to be copied to a new NSArray. How do I copy one NSArray to another?
There can be several ways for this-
array1 = [array2 copy];
Use initWithArray method.
You can also use initWithArray:copyItems: method. (This if for NSMutableArray)
you can use the
NSArray *_newArray = [NSArray arrayWithArray:_oldArray];
or if you prefer better, you can use:
NSArray *_newArray = [[NSArray alloc] initWithArray:_oldArray];
(in that case the object of the first array won't be copied, that get only a retain front he second NSArray, you can remove the object from any array it won't affect the other array, but if you change any object in any NSArray it will be changed in the other one as well because there is both of the old and the new array is working with the same instance of the objects.)
if your plan is to make another instance of the old objects in the new array:
NSArray *_newArray = [[NSArray alloc] initWithArray:_oldArray copyItems:true];
if you are using the ARC, you won't need to do anything else, if you are not, in the case of both -initWithArray: or -initWithArray:copyItems: you should use the [_newArray release]; to release the array after you don't want to use anymore.
As well as
NSArray *newArray = [oldArray copy];
if you need to add/remove from the new array, the simplest way to make a mutable copy is:
NSMutableArray *editableArray = [oldArray mutableCopy];
The above functions both make shallow copies, for deep copy it's as #holex and #rishi mentioned
NSArray *newArray = [[NSArray alloc] initWithArray:oldArray copyItems:true];
NSMutableArray *editableArray = [[NSMutableArray alloc] initWithArray:oldArray copyItems:true];

Sorting a mutable array using a dictionary key

I am trying to create a simple mutable array with a single key ("dayCounter") that I intend to use for sorting. I've read loads of examples on line, but no joy.
So I create this array. Note the first entry is a NSDictionary object. (The other objects are text)
cumArray = [NSMutableArray arrayWithObjects:[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter] forKey:#"dayCounter"],[[dailyArray objectAtIndex:x]objectAtIndex:0],[[dailyArray objectAtIndex:x]objectAtIndex:1],[[dailyArray objectAtIndex:x]objectAtIndex:2], nil],nil];
I save the array in a plist and everything looks great after the load.
However, when I come to sort the array, the program crashes. I have tried every combination of the following:
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dayCounter" ascending:YES];
[cumArray sortUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
Do I need a dictionary item to act as a key? Can I sort on the first object any easier? Any help is much appreciated.
Sometimes using too many nested expressions can obscure what's really going on. For example, the 'simple' mutable array you created actually contains a nested mutable array, rather than directly containing the dictionaries you're trying to sort.
So instead of this:
cumArray = [NSMutableArray arrayWithObjects:[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter] forKey:#"dayCounter"],[[dailyArray objectAtIndex:x]objectAtIndex:0],[[dailyArray objectAtIndex:x]objectAtIndex:1],[[dailyArray objectAtIndex:x]objectAtIndex:2], nil],nil];
try doing this
NSDictionary *dict1 = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter]
forKey:#"dayCounter"]
NSArray *objs = [dailyArray objectAtIndex:x];
NSDictionary *dict2 = [objs objectAtIndex:0];
NSDictionary *dict3 = [objs objectAtIndex:1];
NSDictionary *dict4 = [objs objectAtIndex:2];
// Note: You might want to temporarily log the values of dict2 - 4 here to make sure they're
// really dictionaries, and that they all actually contain the key 'dayCounter'.
cumArray = [NSMutableArray arrayWithObjects:dict1, dict2, dict3, dict4, nil];
Assuming that you really have a mutable array of dictionaries, each of which contains the key dayCounter, the sort descriptor you showed in your example should work just fine.
Your setup makes no sense. You are saying yourself that only the first object in the array is a dictionary that contains the key `#"dayCounter" ("The other objects are text"). How is it supposed to be sorted if only one object contains the sort criteria?
You need to sort the array with a method, like - (NSComparisunResult)compareDict
If you have to compare 2 dictionaries and determine which one should be ordered above the other ( NSOrderedAscending ) then you need to "extend" NSDictionary:
#interface NSDictionary (SortingAdditions) {}
- (NSComparisonResult)compareTo:(NSDictionary *)other;
#end
#implementation NSDictionary (SortingAddictions)
- (NSComparisonResult)compareTo:(NSDictionary *)other
{
if( [self count] > [other count] )
{ return NSOrderedAscending; }
}
#end
This method will sort NSDictionaries according to the amount of objects that they contain.
Other values you can return here are: NSOrderedDescending and NSOrderedSame.
Then you can sort the mutable array with:
[SomeMutableArray sortUsingSelector:#selector(compareTo:)];
Keep in mind that every object in the array will need to be an NSDictionary, otherwise you will get an exception: unrecognized selector sent to instance blabla
You can do the same thing for any type of object, if the array contains both NSStrings, NSNumbers and NSDictionaries you should take a different approach

Terminology question regarding looping thru an NSArray in Objective-C

When you have an NSArray and you want to evaluate and change the elements, you can't change the array from inside the loop. So, you create a mutable copy that can be changed.
code example:
NSMutableArray *bin = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", #"5", #"6", #"7", nil];
NSMutableArray *list = [NSMutableArray arrayWithObjects:#"a1", #"b2", #"c3", #"e4", nil];
NSMutableArray *listHolder = list; // can't mutate 'list' within loop so create a holder
for (int i = 0; i < [list count]; i++) {
[listHolder replaceObjectAtIndex:i withObject:[bin objectAtIndex:i]];
}
What is that second array listHolder called? I mean, what term is used to refer to an array in this context.
This is perfectly valid:
NSMutableArray *bin = [NSMutableArray arrayWithObjects:#"0", #"1", …, #"7", nil];
NSMutableArray *list = [NSMutableArray arrayWithObjects:#"a1", …, #"e4", nil];
// NSInteger should be used instead of int
for (NSInteger i = 0; i < [list count]; i++) {
[list replaceObjectAtIndex:i withObject:[bin objectAtIndex:i]];
}
You're not allowed to change the array inside a for … in or NSEnumerate loop, but using an index is perfectly valid.
What troubles me is your misunderstanding of pointers.
If it were a loop in which you weren't allowed to mutate the array this wouldn't copy the array but only the pointer to the array, effectively modifying the array you're not allowed to. (I'm not even sure if this works.)
Instead of just copying the pointer
// can't mutate 'list' within loop so create a holder
NSMutableArray *listHolder = list;
make a true copy:
NSMutableArray *copy = [[list mutableCopy] autorelease];
In case I really have to make a copy I try to name it according to its content. For example:
NSMutableArray *views;
NSMutableArray *reorderedViews = [views mutableCopy];
// reorder reorderedViews
Sometimes it's hard to find a good enough name, then I usually just use nameCopy.
In this context listHolder would be called a copy.
Your code has a bug though. This line is not actually making a copy, it is only letting listHolder and list both reference the same array object:
NSMutableArray *listHolder = list;
This would be an actual copy:
NSMutableArray *listHolder = [list mutableCopy];
Make sure that you use mutableCopy and not just copy if you want the copy to be mutable. The copy method will return immutable variants on all mutable classes such as NSMutableSet, NSMutableDictionary, and so forth.
Also as others have noted it is only inside the for (item in collection) loop that the enumerated collection can not be mutated. In a normal for (;;) mutation is perfectly ok, but can lead to strange result if the number of items in the collection changes.
There is not specific stylistic or common name for this that is universally used, it is your code afterall, and if there appropriate terms for them use them.
Having said that generally if you don't have specific names in this sort of situation then people refer to the original list as the "source" (src) and the final list as "destination" (dst), just like in a memory blitting style operation.
A temporary mutable copy of the original NSArray would be how I would refer to it.