JLT JLT - 3 months ago 15
Objective-C Question

iOS - Removing An Object In NSMutableArray Throws NSRangeException

I am using TCP short connection to send data to a host. When I want to send a data to a host I will instantiate a socket, store the socket instance to an array and then remove it later on when the data is written successfully.

This is how I did it:

- (void)connectAndSendData:(NSData *)data
{
GCDAsyncSocket *asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];

[_connectionDataToBeSentMap setObject:data forKey:[NSValue valueWithNonretainedObject:asyncSocket]];

NSError *error;

if(![asyncSocket connectToHost:_host onPort:TCP_COMMUNICATION_PORT withTimeout:TCP_CONNECTION_TIMEOUT error:&error])
{
NSLog(@"TCPShortConnection - failed when attempting to connect to host with IP %@", _host);
}

[_connectionList addObject:asyncSocket];
}


I will disconnect the socket immediately after writing the data. So the connection will be removed afterwards:

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
[_connectionList removeObject:sock];

if(err.code == GCDAsyncSocketConnectTimeoutError)
{
// NSLog(@"TCPShortConnection - on connection timed out");

[_delegate onTCPShortConnectionTimedOut];
}

else
{
// NSLog(@"TCPShortConnection - on disconnection of socket with host %@", _host);
}
}


This line
[_connectionList removeObject:sock];
is throwing NSRangeException. And I don't know why. Because I made a test by removing a non existing object in an empty array, no such exception is thrown.

Here's the log:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(0x258d9b0b 0x25096dff 0x257ea6d3 0x257ff033 0xfae43 0x1ca94d 0x4ccba7 0x4d8eff 0x4d87f1 0x2560ae0d 0x2560a9fc)
libc++abi.dylib: terminating with uncaught exception of type NSException


The screenshot:

enter image description here

EDIT:

Please take not that this is not 100% occuring.

CRD CRD
Answer

Guess: you are accessing your NSMutableArray from different threads

Explanation:

  1. NSMutableArray is not thread safe, concurrent reading and writing is not safe - as may occur if you are accessing the array from a GCD concurrent queue.

  2. GCDAsyncSocket as its name suggests uses GCD. In particular delegate calls are dispatched on a GCD queue set by the user and this queue may be concurrent.

  3. You are setting one of the global concurrent queues as the delegate queue.

Solution: You should first determine whether you should be using an array, we cannot determine that for you. If you need the array then it needs to be thread safe. A simple way to do this is:

  1. When you create the array also create a sequential GCD queue just for this array.

  2. To perform an array method which reads from the array perform a synchronous dispatch on the array's sequential queue. You can return the result from the dispatched block using a __block variable.

  3. To perform a write operation on the array use an asynchronous dispatch to the array's queue.

This simple model ensures that no two operations on the array can occur concurrently and all reads and writes occur in the order they are dispatched.

As I stated at the start, I'm guessing concurrent access to the array is your problem, you will need to determine that yourself. If it is this should fix it.

HTH