0

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;
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Hasib Samad
  • 1,081
  • 1
  • 20
  • 39
  • Try with `- (void)scanParts { @autoreleasepool{ // Your code HERE } }` – Amar May 22 '13 at 13:05
  • If possible post code where you are reading the file contents in `_documentDataString` – Amar May 22 '13 at 13:07
  • What are you trying to accomplish with this code? – Marcus Adams May 22 '13 at 13:13
  • _documentsDataString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] – Hasib Samad May 22 '13 at 13:13
  • @MarcusAdams I am trying to parse an .MHT file (separating the parts) – Hasib Samad May 22 '13 at 13:17
  • @user1423640 Can you make use of `NSError *error=nil; _documentsDataString = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];`? – Amar May 22 '13 at 13:19
  • Try removing the line `nextBoundaryLocation += currentLocation + 1;`. I think that's in error. – Marcus Adams May 22 '13 at 13:23
  • No error (null), I am also able to NSLog the content of _documentDataStrig. It is fine. Still does not work. – Hasib Samad May 22 '13 at 13:24
  • You're causing the stack to overflow with that infinite loop in the second example (why exactly is it infinite, anyhow?) – CodaFi May 22 '13 at 13:25
  • @MarcusAdams There aren't any logical errors in this method. The method works just fine with smaller amounts of data. – Hasib Samad May 22 '13 at 13:25
  • @CodaFi The first loop is not infinite. It ends when the end of file is reached. The other one is only for demonstrative issues. – Hasib Samad May 22 '13 at 13:26
  • 1
    @user Well there's your problem. Look at the error: You've requested that the OS allocate you a huge chunk of memory to read a large file. Chunk your read requests, then release the chunks as you finish with them so you don't have to alloc the entire thing in one go. – CodaFi May 22 '13 at 13:31

3 Answers3

5

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];
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Simple, yet effective. It is a shame I totally forgot about the componontsSeparatedByString method! – Hasib Samad May 22 '13 at 13:53
  • 1
    @user1423640: `componentsSeparatedByString` is often (ab-)used as a universal remedy for all kind of problems. But here it seems to be appropriate. I'm glad that it helped! – Martin R May 22 '13 at 13:57
0

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;
        }
Community
  • 1
  • 1
Javier Quevedo
  • 2,066
  • 1
  • 17
  • 27
0

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

Vytautas
  • 573
  • 3
  • 8