Reputation: 1081
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
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
Documentation from Apple on InputStreams https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSInputStream_Class/Reference/Reference.html
Input stream programming guide http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html
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
Reputation: 573
You also can NSInputStream
, it will reduce your memory usage.
14MB on iOS device is quit a lot.
Upvotes: 0
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