Take the 2-minute tour ×

I have a Cocoa program that is editing hundreds (sometimes thousands) of third-party files and I want to create a log-type output for the end user to see (although I don't need things like timestamps). Currently I am simply appending strings to the outlet:

@property (unsafe_unretained) IBOutlet NSTextView *finalText;
self.finalText.string = [self.finalText.string stringByAppendingFormat:@"Final results:\n"];

but this is extremely inefficient. When I run that code on 700 files with the above code (all of the commenting) turned off it takes 4 seconds to execute, with the above code turned on it takes 40 seconds to create the necessary 8,000 lines of output. Oh, and did I mention that Xcode says that my memory usage spikes to over 2GB during the processing? Yikes!

I understand that what I am doing is inefficient but I don't know the best way to be efficient. What is the best way to create 8,000 lines of text that the end-user can see at the end? Would something like Lumberjack be the best solution?

share|improve this question
Need to see more code to comment on the efficiency. You may just be blocking if you're doing everything on the main thread. –  uchuugaka Jun 30 '13 at 23:56
How about creating a string, and when it reaches a certain size, you write it to a file, then provide the user with a filename to look at? –  7stud Jun 30 '13 at 23:56
I saw someone suggest on another question to use textStorage but I need to use formats (adding variables to the output) and I don't see a way to use formats with textStorage items. –  Matthew Kelling Jul 1 '13 at 0:07
Make a string using stringWithFormat, then make an attributed string out of that and append it. Be sure to clean up the autoreleased temporary objects though. –  Catfish_Man Jul 1 '13 at 2:14
Go download Hex fiend (source available) and study how it does I/O. The answers below are only likely to work by coincidence and, even then, may not meet your performance needs. This is a tricky area, btw, but also a very interesting one. The key is to reduce I/O, be it memory or, especially, disk. Part of that is avoiding autorelease pools, a part is using the I/O capabilities of the system efficiently. –  bbum Jul 1 '13 at 3:07
show 1 more comment

3 Answers

up vote 0 down vote accepted

[self.textView.textStorage appendAttributedString: ... ]

That will do much less work. Also, wrap that in @autoreleasepool to clean up the temporary objects promptly.

share|improve this answer
Thanks! I ended up creating some textStorage variables that I dumped into the textView at the end of the code. Now there is no noticeable impact on time or memory usage when I create the needed output! –  Matthew Kelling Jul 4 '13 at 7:15
add comment

Lumberjack is great for logging. Also, you should use NSMutableString to avoid creating copies of same string again and again.

share|improve this answer
I had Lumberjack working but I didn't know how to read back in the .log files (how to find it on the user's HD). Also the .log file includes the timestamp and such, is there a way to not include that? –  Matthew Kelling Jul 1 '13 at 0:15
add comment

You should use this:

NSData *data = [[NSData alloc] initWithContentsOfURL:url];

That will take one or two milliseconds with a 300GB file, and will only load the entire file into RAM if there is enough RAM available. If there isn't enough RAM available it will only load the parts of the file you actually read and also heuristically/predictively read parts the operating system thinks you are going to read.

NSString is a convenience wrapper around CFString. If you drop down to the lower level CFString API you will be able to write your code so that it uses less RAM (for example, by partially decoding large files). Partially reading a string from a file is complicated, you will need to learn how UTF-8 works and make sure you don't cut the data half way through a UTF-8 sequence. I would try to avoid CFString if you can get acceptible performance with NSString.

Also, do you have a for or while loop iterating through all of the files? Make sure you have an "autorelease" pool in your loop:

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {

    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */

(from http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-SW2)

share|improve this answer
Thanks, will take a look at that. To be clear the information that I am sending to the end user is not directly tied to what is in the files, IE: I am reading a file, doing something to the file, then telling the user what I did to the file. What is in the file is not being shown to the user. –  Matthew Kelling Jul 1 '13 at 0:51
Are you sure about that? The docs give no suggestion that -initWithContentsOfURL: will check the amount of RAM available, and it don't expect it to –  Mike Abdullah Jul 5 '13 at 15:36
Yes, I'm sure - I have tested it. When I init an NSData object with a file that does't fit in RAM, it takes a fraction of a millisecond to create the object (it would take a few minutes if reading the entire file). I can access amy part of the file immediately. If the file fits in RAM (maybe it's only 8GB and my system has 12GB) then my app will use that much. It is being mapped as virtual memory/swap and the kernel tries to keep those files in RAM but chucks them out (based on least recently used areas of the file) if the RAM is needed for anything else. –  Abhi Beckert Jul 5 '13 at 22:18
I wonder why I was downvoted? My answer is the result of years of experience/experimentation to find the best way to do what the question asks. –  Abhi Beckert Jul 5 '13 at 22:25
add comment

Your Answer


