Hasib Samad
Hasib Samad

Reputation: 1081

iOS Memory Cannot Be allocated

I am working in an ARC auto-converted project.

I have the content of a huge text file (14MB) stored in an NSString *_documentDataString.

Now I have this loop:

- (void)scanParts
{
    NSString *boundary = [boundaryPrefix stringByAppendingString:_header.boundary];

    NSMutableArray *parts = [NSMutableArray array];

    NSInteger currentLocation = [_documentDataString rangeOfString:boundary].location;

    BOOL reachedTheEnd = NO;
    while(!reachedTheEnd)
    {
        NSInteger nextBoundaryLocation = [[_documentDataString substringFromIndex:currentLocation + 1]
                                          rangeOfString:boundary].location;

        if(nextBoundaryLocation == NSNotFound)
        {
            reachedTheEnd = YES;
        }
        else
        {
            nextBoundaryLocation += currentLocation + 1;

            //[parts addObject:[_documentDataString substringWithRange:
              //                NSMakeRange(currentLocation, nextBoundaryLocation - currentLocation)]];

            currentLocation = nextBoundaryLocation;
        }
    }
}

However, I start getting those errors:

 malloc: *** mmap(size=6496256) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug

What is going wrong?

I even start getting this error when running this loop:

while(true)
{
    NSInteger nextBoundaryLocation = [[_documentDataString substringFromIndex:currentLocation + 1]
                                          rangeOfString:boundary].location;
}

Upvotes: 0

Views: 425

Answers (3)

Javier Quevedo
Javier Quevedo

Reputation: 2046

It appears that the document is simply too large to manipulate it as you would normally do with (reasonably) regular sized strings, reason for which you get an error during the memory allocation of the string object.

For your particular case you probably need to use the NSInputStream, with which essentially can read the data of the file byte by byte instead of all at once.

Have a look at the following question Objective-C: Reading a file line by line

In a nutshell, according to the documentation you will need to do something like this:

First open the file

- (void)setUpStreamForFile:(NSString *)path {
    // iStream is NSInputStream instance variable
    iStream = [[NSInputStream alloc] initWithFileAtPath:path];
    [iStream setDelegate:self];
    [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
        forMode:NSDefaultRunLoopMode];
    [iStream open];
}

Write the code to handle what to do when there are more bytes available in your stream (while it is not the end of the file)

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {

    switch(eventCode) {
        case NSStreamEventHasBytesAvailable:
        {
            if(!_data) {
                _data = [[NSMutableData data] retain];
            }
            uint8_t buf[1024];
            unsigned int len = 0;
            len = [(NSInputStream *)stream read:buf maxLength:1024];
            if(len) {
                [_data appendBytes:(const void *)buf length:len];
                // bytesRead is an instance variable of type NSNumber.
                [bytesRead setIntValue:[bytesRead intValue]+len];
            } else {
                NSLog(@"no buffer!");
            }
            break;
        }

Upvotes: 0

Vytautas
Vytautas

Reputation: 573

You also can NSInputStream, it will reduce your memory usage. 14MB on iOS device is quit a lot.

Upvotes: 0

Martin R
Martin R

Reputation: 539745

The substringFromIndex: in

NSInteger nextBoundaryLocation = [[_documentDataString substringFromIndex:currentLocation + 1]
                                      rangeOfString:boundary].location;

creates a (temporary, autoreleased) substring that is only deallocated when the current autoreleasepool is destroyed (e.g. when the program control returns to the main event loop).

You could replace that code with

NSInteger nextBoundaryLocation = [_documentDataString rangeOfString:boundary
             options:0
               range:NSMakeRange(currentLocation + 1, [_documentDataString length] - (currentLocation + 1))].location;

to (hopefully) avoid the creation of a temporary string. Note that nextBoundaryLocation is then relative to the start of the string, so that

nextBoundaryLocation += currentLocation + 1;

is not necessary anymore.

Alternatively, you could simply replace the complete loop by

NSArray *parts = [_documentDataString componentsSeparatedByString:boundary];

Upvotes: 5

Related Questions