I have to delete an object of an array every now and then, when I do it I get this error.
Collection < CALayerArray: 0xc4f3b20> was mutated while being enumerated
The error appears on this method, which is the accesor of the Array:
- (NSArray *)occupantsArray
{
if (dispatch_get_current_queue() == moduleQueue)
{
return occupantsArray;
}
else
{
__block NSArray *result;
dispatch_sync(moduleQueue, ^{ //ON THIS LINE
result = [occupantsArray copy];
});
return [result autorelease];
}
}
As you can see Im taking care of not returning the original array but a copy, but it still crashes.
Also the method where Im deleting elements of the array is this.
- (void)eraseJIDFromArray:(NSString*)jid{
dispatch_block_t block = ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i = 0;
for(NSString *jidAct in occupantsArray){
if([jidAct isEqualToString:jid]){
[occupantsArray removeObjectAtIndex:i];
}
i++;
}
[pool drain];
};
if (dispatch_get_current_queue() == moduleQueue)
block();
else
dispatch_async(moduleQueue, block);
}
The array can have upto 200 elements, so it can take some time to go through all of them, but I'm setting queues, dont know what else I can do.
Any ideas?
Thanks.
This code:
for(NSString *jidAct in occupantsArray){
if([jidAct isEqualToString:jid]){
[occupantsArray removeObjectAtIndex:i];
}
i++;
}
is probably causing your problems. You shouldn't be removing elements while enumerating the array. The way you avoid this is by using an NSMutableIndexSet:
NSMutableIndexSet *indexes = [NSMutableIndexSet set]; // assuming NSInteger i;
for(NSString *jidAct in occupantsArray){
if([jidAct isEqualToString:jid]){
[indexes addIndex:i];
}
i++;
}
[occupantsArray removeObjectsAtIndexes:indexes];
Related
I'm trying to set value after mapping data using Reactive Cocoa. Here is my code:
- (RACSignal *)getNews{
RACSignal *sign = [self.manager rac_GET:MAIN_URL_NEWS parameters:self.parameters];
return sign;
}
Then i do:
#weakify(self);
[[[self getNews] map:^id(NSDictionary *response) {
// Mapping
NSArray * array = [response valueForKey:#"data"];
NSMutableArray *localArray = [NSMutableArray new];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NewsParseer *news = [[NewsParseer alloc]init];
[news mts_setValuesForKeysWithDictionary:[[response valueForKey:#"data"]objectAtIndex:idx]];
NSLog(#"Object memory %#", news);
NSLog(#"Newnew %#", news.title);
[localArray addObject:news];
}];
return localArray;
}] subscribeNext:^(NSArray* x) {
NSLog(#"Data is %#", x);
}];
NSLog(#"data array %#", self.dataArray);
#"Data is %#" is output correct value, therefore, in subscribeNext we got correct value.
But, #"data array" output is
data array (
)
Why is that happening?
Of course i did initialize array:
self.dataArray = [NSMutableArray new];
Even provide getter method:
-(NSMutableArray*)dataArray{
if(!_dataArray){
_dataArray = [[NSMutableArray alloc] init];
}
return _dataArray;
}
It doesn't look like you're ever setting self.dataArray to equal to result of x in your subscribeNext block.
Try:
self.dataArray = x; after your log statement in subscribeNext.
Also, the signal is asynchronous - it's highly unlikely that your logging of self.dataArray will occur after your signal completes. It will probably happen prior to the completion of the signals work.
I'm trying to make a function for a NSMutableArray subclass that only uses integer, but I don't want to use "count." How do I do this?
-(NSMutableArrayWithIntegers*)initWithCount:(NSInteger)count numbers:(NSInteger)firstInt, ...
{
self = [super init];
if (self) {
va_list args;
va_start(args, firstInt);
NSInteger arg = firstInt;
for (int i = 0; i < count; i++)
{
arg = va_arg(args, NSInteger);
[self addObject: [NSNumber numberWithInteger:arg]];
}
va_end(args);
}
return self;
}
I know this doesn't answer your question but it's important to let you know. Don't ever subclass NSMutableAnything. Use a category and thank me later:
#interface NSMutableArray (ListOfIntegers)
+(NSMutableArray)mutableArrayWithIntegers:(NSInteger)i, ... {
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:whatever];
// do your thing
return array;
}
#end
First of all, the approach you currently have is just fine. Don't try getting rid of the count. There are alternatives, but they are only worse.
For example, you may use a sentinel value (which may not be inserted into the array) as the last argument, but in this case, you will have to make sure that you are not actually trying to insert this value to the array at all:
- (id)initWithIntegers:(NSInteger)first, ...
{
if (!(self = [super init])) return nil;
va_list args;
va_start(args, first);
NSInteger n;
if (first != NSIntegerMax) {
[self addObject:#(first)];
while ((n = va_arg(args, NSInteger)) != NSIntegerMax) {
[self addObject:#(n)];
}
}
va_end(args);
return self;
}
But really, this unnecessarily narrows the range of values that can be added - using that count argument is not a big deal.
i am trying to remove duplicate objects from array.
NSMutableArray* filterResults = [[NSMutableArray alloc] init];
BOOL copy;
// remove duplicate
if (![arrSelectedVehicle count] == 0)
{
for (Vehicles *a1 in arrSelectedVehicle) {
copy = YES;
for (Vehicles *a2 in filterResults) {
if ([a1.Vehicle_id isEqualToString:a2.Vehicle_id]) {
copy = NO;
[arrSelectedVehicle removeObjectIdenticalTo:a2];
break;
}
}
if (copy) {
[filterResults addObject:a1];
}
}
}
i am adding two object which is already their in the array
you cannot modify an array when you are enumerating it. you can do the following:
NSMutableArray* filterResults = [[NSMutableArray alloc] init];
BOOL copy;
// remove duplicate
if (![arrSelectedVehicle count] == 0)
{
NSArray* arraycopy = [arrSelectedVehicle copy];
for (Vehicles *a1 in arraycopy) {
copy = YES;
for (Vehicles *a2 in filterResults) {
if ([a1.Vehicle_id isEqualToString:a2.Vehicle_id]) {
copy = NO;
[arrSelectedVehicle removeObjectIdenticalTo:a2];
break;
}
}
if (copy) {
[filterResults addObject:a1];
}
}
[arraycopy release];
}
You can't modify an array while using it with fast enumeration. That's what the error is telling you. You need to change the loops
for (NSUInteger i = 0; i < arrSelectedVehicle.count; i++) {
Vehicles *a1 = arrSelectedVehicle[i];
copy = YES;
for (NSUInteger j = 0; j < filterResults.count; j++) {
Vehicles *a2 = filterResults[j];
if ([a1.Vehicle_id isEqualToString:a2.Vehicle_id]) {
copy = NO;
[arrSelectedVehicle removeObjectIdenticalTo:a2];
break;
}
}
if (copy) {
[filterResults addObject:a1];
}
}
There are several problems with your code. Anyway, the easiest way to remove duplicates, if you don't care about the order of the elements, is using an NSSet, because an NSSet doesn't allow duplicates:
NSArray *uniqueObjects = [[NSSet setWithArray:arrSelectedVehicle] allObjects];
The error message says it pretty much: you can't modify the contents of a mutable collection while you're using fast enumeration on it (because that's erroneous). You have to make a mutable copy of it, and remove the duplicates from that copy.
you can not use a for loop and then add or remove objects from the array that you are iterating through (arrSelectedVehicle). Instead try building up a new array with the objects that are OK. At the end of the loop you could assign that array back to arrSelectedVehicle.
I need to read 3 values from a database and return them in a method. I'm having trouble to understand how to return values using NSInteger types. This is the code:
NSString* GetVerbInfinitive(FMDatabase* mydb, NSString* myConjugation, NSInteger verbal_time, NSInteger verbal_person)
{
NSMutableString *ret = [[NSMutableString alloc] initWithString:#""];
FMResultSet *rs = [mydb executeQuery:#"select verb_text, conjugation_verbal_time, conjugation_verbal_person from verbs where verb_conjugation = ?",[[myConjugation lowercaseString] precomposedStringWithCanonicalMapping]];
if ([rs next])
{
if (![rs columnIndexIsNull:0])
{
[ret setString:[rs stringForColumn:#"verb_text"]];
verbal_time = [rs intForColumn:#"conjugation_verbal_time"];
verbal_person = [rs intForColumn:#"conjugation_verbal_person"];
}
else
{
NSLog(#"GetVerbInfinitive: verb '%#' has no infinitive defined", myConjugation);
}
}
[rs close];
return [ret autorelease];
}
The NSInteger values work only inside the method when I return to the calling method they are lost. I believe I should pass these NSInteger by reference, but I don't know how. I don't want to create a type structure for this.
Thanks,
Miguel
You have to dereference them as pointers. Here's an example (this goes in the code when you're calling the method):
NSInteger verbal_time, verbal_person;
GetVerbInfinitive(....., &verbal_time, &verbal_person);
Now in your getverbinfinitivemethod:
NSString* GetVerbInfinitive(FMDatabase* mydb, NSString* myConjugation, NSInteger *verbal_time, NSInteger *verbal_person)
{
...
...
*verbal_time = [rs intForColumn:#"conjugation_verbal_time"];
*verbal_person = [rs intForColumn:#"conjugation_verbal_person"];
...
...
}
Notice the change in the signature line to make those two NSIntegers pointers.
I'm working through Cocoa Programming for Mac OS X (3rd ed) and in chapter 4 I wrote this app:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//create the date object
NSCalendarDate *now = [[NSCalendarDate alloc] init];
//seed random # generator
srandom(time(NULL));
NSMutableArray *array;
array = [[NSMutableArray alloc] init];
int i;
for (i=0; i<10; i++){
//create a date/time that is 'i' weeks from now
NSCalendarDate *iWeeksFromNow;
iWeeksFromNow = [now dateByAddingYears:0
months:0
days:(i*7)
hours:0
minutes:0
seconds:0];
//create a new instance of lottery entry
LotteryEntry *entry = [[LotteryEntry alloc] init];
[entry setEntryDate:iWeeksFromNow];
[array addObject:entry];
[entry release];
}
[now release];
now = nil;
for (LotteryEntry *entryToPrint in array) {
NSLog(#"%#", entryToPrint);
}
[array release];
array = nil;
NSLog(#"about to drain the pool... (%#)", pool);
[pool drain];
NSLog(#"done");
NSLog(#"GC = %#", [NSGarbageCollector defaultCollector]);
return 0;
}
The LotteryEntry class looks like this:
#implementation LotteryEntry
- (void)setEntryDate:(NSCalendarDate *)date
{
entryDate = date;
}
- (NSCalendarDate *)entryDate
{
return entryDate;
}
- (int)firstNumber
{
return firstNumber;
}
- (int)secondNumber
{
return secondNumber;
}
- (id)init
{
return [self initWithDate:[NSCalendarDate calendarDate]];
}
- (id)initWithDate:(NSCalendarDate *)date
{
if(![super init])
return nil;
NSAssert(date != nil, #"Argument must be non-nil");
firstNumber = random() % 100 + 1;
secondNumber = random() % 100 + 1;
entryDate = [date retain];
return self;
}
- (NSString *)description
{
NSString *result;
result = [[NSString alloc] initWithFormat:#"%# = %d and %d",
[entryDate descriptionWithCalendarFormat:#"%b %d %Y"],
firstNumber,
secondNumber];
return result;
}
- (void)dealloc
{
NSLog(#"deallocating %#", self);
[entryDate release];
[super dealloc];
}
#end
As you can see I'm retaining and releasing the objects here. I'm pretty sure my code matches the book's, however when I run the app, at the [pool drain] I get this message:
Program received signal:
“EXC_BAD_ACCESS”.
I'm not sure what's causing this. I'm hoping it's something stupid that I missed, but I'd sure appreciate a few other pairs of eyes on it. Thanks in advance!
(side note: I'm a .NET developer, so ref counting is pretty foreign to me!)
It also looks like you have a bug in this method:
- (id)initWithDate:(NSCalendarDate *)date
{
if(![super init])
return nil;
NSAssert(date != nil, #"Argument must be non-nil");
firstNumber = random() % 100 + 1;
secondNumber = random() % 100 + 1;
entryDate = [date retain];
return self;
}
You are essentially discarding the results from [super init], while it may not be a problem in this instance, it could cause serious problems in others. You should 'always' structure you init methods like this:
- (id)initWithDate:(NSCalendarDate *)date
{
if(self = [super init]) {
NSAssert(date != nil, #"Argument must be non-nil");
firstNumber = random() % 100 + 1;
secondNumber = random() % 100 + 1;
entryDate = [date retain];
}
return self;
If you are not going to return self from an init method (for instance it is a factory or something odd like that), you should remember to release self. It has been alloc'ed, and if you don't return it, it cannot be released properly. Example:
- (id) init
{
NSObject* newSelf = [[NSObject alloc] init];
[self release];
return newSelf;
}
doh! Just typing the code made me realize my problem. Doncha love it when that happens?
I am retaining the date in my init, but I still had that extra setEntryDate method that was not calling retain. Removing this and calling the initWithDate method instead seemed to fix the problem.
It's not related to your issue at hand but you should avoid using NSCalendarDate, it's been deprecated for a while now and will probably be removed from the API entirely soon.