Memory Management with NSStatusItem : setToolTip - objective-c

I have a Singleton class with static NSStatusItem and a NSStream client. Whenever I receive a message in Stream I pass it to another thread to change NSStatusItem toolTip.
case NSStreamEventHasBytesAvailable:
{
if(stream == inputStream)
{
//InputStream ready
uint8_t buf[1024];
unsigned int len = 0;
len = [inputStream read:buf maxLength:1024];
if(len > 0)
{
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes: (const void *)buf length:len];
NSString *msgRcvd = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"Message Recieved in StreamUtil ::: %#",msgRcvd);
[self ProcessMessage:msgRcvd];
[data release];
[msgRcvd release];
}
}
break;
}
#implementation SBNSStatusItem
static SBNSStatusItem *sbNSStatusItem = NULL;
+(SBNSStatusItem *) GetSBNSStatusItem
{
#synchronized(self)
{
if (sbNSStatusItem== NULL)
{
sbNSStatusItem= [[self alloc] init];
}
}
return(sbNSStatusItem);
}
-(void) CreateNSStatusItem
{
// Initalization of NSStatusItem with NSMenu and Image setting
}
-(void) SetToolTip:(NSString *) toolTip
{
NSLog(#"%#",toolTip); // Shows Correct Message
[statusItem setToolTip:toolTip]; // But the ToolTip has some junk at the end of ori msg
//[toolTip release]; // EXC_BAD_ACCESS
}
#end
Now the Threadfrom StreanUtil calls the setToolTip with the message to be set. But after 2 or 3 calls, the tooltip starts showing some junk text at the end of the original tool tip message.
What am I doing wrong here?

Since the toolTip item you pass into SetToolTip: is just a pointer its probably getting set or released somewhere else.
try something like this
- (void)SetToolTip:(NSString *) toolTip
{
NSString *toolTipCopy = [toolTip copy];
[statusItem setToolTip:toolTipCopy];
[toolTipCopy release];
}
Also if your targeting iOS 4 or OS X 10.6 or later I would consider moving to ARC.
Then the code would then become this.
- (void)SetToolTip:(NSString *) toolTip
{
[statusItem setToolTip:[toolTip copy]];
}

Related

Memory Leak Using IOKit (Adapted From USBPrivateDataSample)

Well. I'm back with another memory leak question. I recently asked a similar question here, but it turns out that code doesn't entirely do what I am trying to accomplish (which is to perform an action whenever any USB device connects/disconnects, and not just when an HID devices does).
I've started over from scratch, and have adapted Apple's USBPrivateDataSample and some of GitHub project ORSSerialPort into something that seems to work just like I want it to -- except --there are memory leaks. :(
I've tried applying the knowledge gained from my previous posts about dealing with memory leaks, but I'm stuck again. What am I missing? Thanks very much in advance.
Here's the code:
**AppDelegate.h:**
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (strong) NSMutableDictionary *deviceDictionary;
#end
**AppDelegate.m:**
#import "AppDelegate.h"
#include <IOKit/usb/IOUSBLib.h>
typedef struct DeviceData
{
io_object_t notification;
CFStringRef deviceName;
CFStringRef serialNum;
} DeviceData;
static IONotificationPortRef gNotifyPort;
static io_iterator_t gAddedIter;
static CFRunLoopRef gRunLoop;
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.deviceDictionary = [[NSMutableDictionary alloc] init];
CFMutableDictionaryRef matchingDict;
CFRunLoopSourceRef runLoopSource;
kern_return_t kr;
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (matchingDict == NULL)
{
fprintf(stderr, "IOServiceMatching returned NULL.\n");
}
// Create a notification port and add its run loop event source to our run loop
// This is how async notifications get set up.
gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
gRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode);
// Now set up a notification to be called when a device is first matched by I/O Kit.
kr = IOServiceAddMatchingNotification(gNotifyPort, // notifyPort
kIOFirstMatchNotification, // notificationType
matchingDict, // matching
DeviceAdded, // callback
NULL, // refCon
&gAddedIter // notification
);
// Iterate once to get already-present devices and arm the notification
DeviceAdded(NULL, gAddedIter);
// Start the run loop. Now we'll receive notifications.
CFRunLoopRun();
// can't do these, causes EXC_BAD_ACCESS crash
//CFRelease(matchingDict);
//CFRelease(gNotifyPort);
//CFRelease(kr);
CFRelease(runLoopSource);
CFRelease(gRunLoop);
}
void DeviceNotification(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
{
kern_return_t kr;
DeviceData *deviceDataRef = (DeviceData *) refCon;
if (messageType == kIOMessageServiceIsTerminated)
{
kr = IOObjectRelease(deviceDataRef->notification);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
if ((deviceDataRef->serialNum) && (deviceDataRef->deviceName))
{
// remove device from dict
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
NO];
}
free(deviceDataRef);
}
}
void DeviceAdded(void *refCon, io_iterator_t iterator)
{
kern_return_t kr;
io_service_t usbDevice;
while ((usbDevice = IOIteratorNext(iterator)))
{
io_name_t deviceName;
CFStringRef deviceNameCF;
DeviceData *deviceDataRef = NULL;
// Add some app-specific information about this device.
// Create a buffer to hold the data.
deviceDataRef = malloc(sizeof(DeviceData));
bzero(deviceDataRef, sizeof(DeviceData));
// Get the USB device's name.
kr = IORegistryEntryGetName(usbDevice, deviceName);
if (KERN_SUCCESS != kr)
{
deviceName[0] = '\0';
}
CFMutableDictionaryRef usbProperties = 0;
if (IORegistryEntryCreateCFProperties(usbDevice, &usbProperties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS)
{
IOObjectRelease(usbDevice);
continue;
}
NSDictionary *properties = CFBridgingRelease(usbProperties);
NSString *serialNum = properties[(__bridge NSString *)CFSTR("USB Serial Number")];
NSNumber *builtIn = properties[(__bridge NSString *)CFSTR("Built-In")];
deviceNameCF = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, kCFStringEncodingASCII);
if (builtIn.integerValue != 1)
{
// Save the device's name to our private data.
deviceDataRef->deviceName = deviceNameCF;
deviceDataRef->serialNum = (__bridge CFStringRef)(serialNum);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
YES];
}
// Register for an interest notification of this device being removed. Use a reference to our
// private data as the refCon which will be passed to the notification callback.
kr = IOServiceAddInterestNotification(gNotifyPort, // notifyPort
usbDevice, // service
kIOGeneralInterest, // interestType
DeviceNotification, // callback
deviceDataRef, // refCon
&(deviceDataRef->notification) // notification
);
// Done with this USB device; release the reference added by IOIteratorNext
kr = IOObjectRelease(usbDevice);
}
}
- (void) updateDeviceDict : (NSString *) deviceSerial : (NSString *) deviceName : (bool) keepDevice
{
if (deviceName)
{
if ((!deviceSerial) || ([deviceSerial isEqualToString:#""])) deviceSerial = deviceName;
#try
{
//#throw [[NSException alloc] initWithName:#"test" reason:#"test" userInfo:nil];
// if the device needs to be added
if ((keepDevice) && (deviceSerial))
{
if (![self.deviceDictionary objectForKey:deviceSerial])
{
[self.deviceDictionary setObject:[NSDictionary dictionaryWithObjectsAndKeys:deviceName, #"name", nil] forKey:deviceSerial];
NSLog(#"Device added: %#", deviceName);
}
}
else // if the device needs to be removed
{
[self.deviceDictionary removeObjectForKey:deviceSerial];
NSLog(#"Device removed: %#", deviceName);
}
}
#catch (NSException *exception)
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
#finally
{
NSLog(#"\n%#", self.deviceDictionary);
}
}
else // name is nil
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
}

CHCSV importer memory issue on NSBlockOperation

I'm on XCode 8.2, Objective-C, OSX (not iOS), ARC enabled.
I'm importing a large CSV file in a custom NSBlockOperation with DaveDelongs CHCSV parser. My problem: The memory is not freed after the operation is done. Even if i do NOT save the parsed NSArray* the same happens.
Here is the essential part of the code:
SHImportDataOperation.m (custom NSBlockOperation)
// CHCSV parser delegate
#interface Delegate : NSObject <CHCSVParserDelegate>
#property (readonly) NSArray *lines; // <-- parsing result is stored here
#property (readonly) NSString *errorMessage;
#property unsigned long long filesize;
#property SHGlobalAppData* global;
#end
#implementation SHImportDataOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (void)main
{
// ###############################
// UI stuff
dispatch_async(dispatch_get_main_queue(), ^{
// Hide controls
[_global.dataFile setFileDropped:NO]; // _global.dataFile is an NSObject with relevant fileInformation
});
// ###############################
// Import CSV
if (![self importCSV:_global.dataFile])
{
[self breakImportData]; // Cleanup
return;
}
}
// ###############################
// Finishing Import Data
// UI stuff
dispatch_async(dispatch_get_main_queue(), ^{
// Show controls
[_global.dataFile setFileDropped:YES];
// Show OK
[_global.dataFile setImported:YES];
// Cleanup
[self finishOperation];
});
}
// ################################################
// cleanup
- (void)finishOperation
{
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
// ################################################
- (BOOL)importCSV:(SHDataModelData *)dataFile
{
encoding = NSMacOSRomanStringEncoding;
NSString* delimiter = #",";
unichar delimiterUnichar = [delimiter characterAtIndex:0];
// ###############################
NSInputStream* stream = [NSInputStream inputStreamWithFileAtPath:dataFile.filePath];
CHCSVParser* p = [[CHCSVParser alloc] initWithInputStream:stream usedEncoding:&encoding delimiter:delimiterUnichar];
Delegate * d = [[Delegate alloc] init];
[d setGlobal:_global]; // Reference needed for UI progress bar
[p setDelegate:d];
[p setFilepath:dataFile.filePath];
[p parse];
//NSLog(#"Result: %#",d.lines);
// Save imported data
//[dataFile setImportData:d.lines]; // Even if not saved, memory is not free'd after operation
// Even if i nil everything, memory is not freed.
[d setGlobal:nil];
[p setDelegate:nil];
d = nil;
p = nil;
return true;
}
And this is how the operation is started:
NSOperationQueue* operationImportQueue = [NSOperationQueue new];
SHImportLayoutOperation *importLayoutOperation = [[SHImportLayoutOperation alloc] init];
[importLayoutOperation setGlobal:[self theAppDataObject]];
SHImportDataOperation *importDataOperation = [[SHImportDataOperation alloc] init];
[importDataOperation setGlobal:[self theAppDataObject]];
NSBlockOperation *importDoneCompletionOperation = [NSBlockOperation blockOperationWithBlock:^{
[self continueWithStuff];
}];
[importDoneCompletionOperation addDependency:importDataOperation];
[importDoneCompletionOperation addDependency:importLayoutOperation];
[operationImportQueue addOperation:importDoneCompletionOperation];
[operationImportQueue addOperation:importDataOperation];
[operationImportQueue addOperation:importLayoutOperation];
Edit 1:
After further testing i can confirm the NSBlockOperation is successfully performed and removed from memory.

Objective C memory management issue

I'm having this issue with some objective C code to load the images on a IKImageBrowserView.
I'm following the image browser example from apple, but I still fail at some point and Im guessing its memory management.
Here is some of the code:
/* Our datasource object */
#interface myImageObject : NSObject
{
NSString *_path;
}
#end
#implementation myImageObject
- (void)dealloc
{
[_path release];
[super dealloc];
}
/* our datasource object is just a filepath representation */
- (void)setPath:(NSString *)path
{
NSLog(#"setting path for %#",path);
if(_path != path)
{
[_path release];
_path = [path retain];
}
}
Now, It seems I'm properly retaining the path value within the object.
now on to the controller code:
-(IBAction)loadMosaicImages:(id)sender
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *urls = [openFiles() retain];
NSInteger n;
n = [urls count];
NSURL *url = [urls objectAtIndex:0];
[self parsePathForImages:[url path]];
[urls release];
[pool drain];
}
- (void)updateDatasource
{
NSLog(#" UDS-> _importedImages length : %#",[_importedImages count]);
//-- update our datasource, add recently imported items
[_images addObjectsFromArray:_importedImages];
//-- empty our temporary array
[_importedImages removeAllObjects];
NSLog(#" UDS-> _images length : %#",[_images count]);
//-- reload the image browser and set needs display
[_imageBrowser reloadData];
}
-(void)parsePathForImages:(NSString *)path{
NSLog(#"Directory within thread method is %#",path);
NSArray *content = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
myImageObject *p;
for(int i=0;i<content.count;i++)
{
// NSLog(#"%#",(NSString *)[content objectAtIndex:i]);
NSLog(#"Complete : %#",[path stringByAppendingPathComponent:(NSString *)[content objectAtIndex:i]]);
/* add a path to our temporary array */
p = [[myImageObject alloc] init];
[p setPath:[path stringByAppendingPathComponent:[content objectAtIndex:i]]];
[_importedImages addObject:p];
[p release];
}
[self updateDatasource];
}
and that's all the relevant code. _images and _importedImages are NSMutableArrays alloced and inited in the awake from nib method, and openFiles() is a static method that opens an NSOpenpanel and returns an NSArray of the paths.
the debug output for this does this:
Directory within thread method is /Users/cromestant/Code/images/
Complete : /Users/cromestant/Code/images/1.jpg
setting path for /Users/cromestant/Code/images/1.jpg
Complete : /Users/cromestant/Code/images/2.jpg
setting path for /Users/cromestant/Code/images/2.jpg
Complete : /Users/cromestant/Code/images/3.jpg
setting path for /Users/cromestant/Code/images/3.jpg
.
.
.
then stops crashes at the first line of the method updateDataSource, on the NSLog with an 'EXEC_BAD_ACCESS'so where am I going wrong with the memory management?
I seem to be creating an autoreleasePool so ht I have time to retain somewhere else, I release my objects.. I really don't have a clue where the problem could be.
thanks in advance.
Actually, I think your problem might not be memory management. It might be here:
NSLog(#" UDS-> _importedImages length : %#",[_importedImages count]);
It should be
NSLog(#" UDS-> _importedImages length : %d",[_importedImages count]);
because the count method returns an integer, not an object.

Stop NSRunLoop On Keypress

I'm writing a command line foundation tool in Mac OS X and would like the tool to quit on a keypress such as 'q'. The code is launching an asynchronous request for retrieving data from a remote server. This necessitates the NSRunLoop. At least that's what I understand I need to do.
Can someone tell me how to stop the runloop on the specific keypress?
Below is the code snippet.
int main (int argc, const char * argv[]) {
BOOL keepRunning = YES;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Requestor *myRequestor = [[Requestor alloc] init];
[myRequestor GetData];
NSRunLoop *runLoop;
runLoop = [NSRunLoop currentRunLoop];
while (keepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
[pool drain];
return 0;
}
Thank you!
I haven't done this myself, but I would expect that you want to use [[NSFileHandle fileHandleWithStandardInput] readInBackgroundAndNotify] and register to receive the NSFileHandleReadCompletionNotification notification. If you receive a 'q', do what ever cleanup you need to and call exit().
If you haven't already considered libcurses, perhaps that will help you. It's really straightforward to catch keypresses with it, but what I'm not 100% about is if you can get it to work without the entire terminal window being used.
The curses bit alone is just:
#include <ncurses.h>
initscr();
/* snip */
char c;
while (c = getch()) {
if (c == 'q') {
// Put your cleanup and shutdown logic here
}
/* any other keypresses you might want to handle */
}
EDIT | You probably don't wanna put that tight loop inside your run loop... just call getch() each time the runloop ticks over.
Well, my initial theories and experiments starting with your existing code didn't turn up much in the way of usable code.
I'm imagining that what you're looking for is something like how, on Windows, you can run something in a command line shell window, and when the process has finished, it says something like "Press the Q key to continue...". When you bring the window forward (if it isn't already frontmost), and press the Q key, the window closes.
Are you planning on calling this command line tool from your primary application, or is this command line tool something the end-user will be interacting with directly? (For example, if the latter, they'd be calling it from a Terminal window, hmm, then I think Ken's code could probably be combined with mine to make the following. Note that in its current form, this only works after you press Q and then hit Return?
#import <Cocoa/Cocoa.h>
#interface Requestor : NSObject <NSApplicationDelegate> {
BOOL gotData;
NSFileHandle *stdIn;
}
- (void)getData;
- (void)requestorGotData:(id)sender;
#end
#implementation Requestor
- (id)init {
if (self = [super init]) {
gotData = NO;
stdIn = nil;
[NSApp setDelegate:self];
}
return self;
}
- (void)getData {
NSLog(#"getting data.........");
gotData = NO;
[self performSelector:#selector(requestorGotData:)
withObject:nil
afterDelay:5.0];
}
break
- (void)requestorGotData:(id)sender {
NSLog(#"got data");
gotData = YES;
NSLog(#"Press 'Q' key to continue...");
stdIn = [[NSFileHandle fileHandleWithStandardInput] retain];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(fileHandleReadCompletion:)
name:NSFileHandleReadCompletionNotification
object:stdIn];
[stdIn readInBackgroundAndNotify];
}
- (void)fileHandleReadCompletion:(NSNotification *)notification {
NSLog(#"fileHandleReadCompletion:");
NSData *data = [[notification userInfo]
objectForKey:NSFileHandleNotificationDataItem];
NSLog(#"data == %#", data);
NSString *string = [[[NSString alloc]
initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
if (string) {
string = [string stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([[string lowercaseString] isEqualToString:#"q"]) {
[stdIn closeFile];
[stdIn release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[NSApp terminate:nil];
} else {
[stdIn readInBackgroundAndNotify];
}
}
}
#end
break
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
Requestor *requestor = [[Requestor alloc] init];
[requestor getData];
[NSApp run];
[requestor release];
[pool drain];
return 0;
}

Can't receive NSInputStream events in OCUnitTest

I'm trying to learn how to use the NSInputStream class on the iPhone using a unit test. I can get the NSStream to read data from a file using the polling method but for some reason the delegate/event method is not working.
I've posted the relevant code below. Please ignore memory leak errors and such since I'm just trying to ensure I know how to use the NSStream class in a sandboxed environment before rolling it into my larger project.
I'm wondering if maybe I'm missing something with regards to how the run loops work?
This is the logic test that creates a streamer class to read from a file.
#import "StreamingTests.h"
#import "Streamer.h"
#implementation StreamingTests
- (void) testStream {
NSLog(#"Starting stream test.");
Streamer * streamer = [[Streamer alloc] init];
streamer.usePolling = NO;
streamer.readingStream = YES;
NSThread * readThread = [[NSThread alloc] initWithTarget:streamer selector:#selector(startStreamRead:) object:nil];
[readThread start];
while(streamer.readingStream) {
[NSThread sleepForTimeInterval:0.5];
}
[readThread cancel];
}
#end
This is a simple test helper object that reads from an NSStream. When usePolling == YES it read data and outputs the appropriate NSLog messages. However, if usePolling == NO the delegate stream event function is never called.
#implementation Streamer
#synthesize readingStream, usePolling;
- (void) startStreamRead:(NSObject*) context {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"starting stream read.");
readingStream = YES;
/*
NSURL * url = [NSURL URLWithString:#"http://www.google.com"];
NSLog(#"Loading: %#",[url description]);
NSInputStream * inStream = [[NSInputStream alloc] initWithURL:url];
*/
NSInputStream * inStream = [[NSInputStream alloc] initWithFileAtPath:#"sample.ttc"];
if(!usePolling) {
[inStream setDelegate: self];
[inStream scheduleInRunLoop: [NSRunLoop currentRunLoop]
forMode: NSDefaultRunLoopMode];
}
[inStream open];
if(usePolling) {
while(1) {
if([inStream hasBytesAvailable]) {
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)inStream read:buf maxLength:1024];
NSLog(#"Read: %d",len);
}
NSStreamStatus status = [inStream streamStatus];
if(status != NSStreamStatusOpen && status != NSStreamStatusOpening) {
NSLog(#"Stream not open.");
break;
}
}
readingStream = NO;
NSStreamStatus status = [inStream streamStatus];
NSError * error = [inStream streamError];
NSLog(#"Status: %d Error Desc: %# Reason: %#",(int)status,[error localizedDescription], [error localizedFailureReason]);
[pool release];
}
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSMutableData * _data = nil;
NSNumber * bytesRead = nil;
NSLog(#"Event fired.");
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];
NSLog(#"Read %d bytes",len);
} else {
NSLog(#"no buffer!");
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
stream = nil; // stream is ivar, so reinit it
readingStream = NO;
break;
}
default:
{
NSLog(#"Another event occurred.");
break;
}
// continued ...
}
}
#end
Thanks in advance,
b
The reason for it should be that the run loop is blocked since the unit test is executing. You could refer to the NSRunLoop documentation where the method
runUntilDate:
might help you to run the main run loop in the thread of execution of the unit test like this:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This lets the run loop run for 1 second giving it time to process part of your file. It should be noted that this does not provide a reliable way for unit testing (since the time interval might differ depending on run loop size) and may then be unsuitable. By giving your unit an interface that could be used to check the status of the input stream read operation (with a reading finished state) such as
-(BOOL)hasFinishedReadingFile
the unit test could repeatedly execute the run loop until the above method returns TRUE and the file is read completely.
Addition: This question on stackoverflow also deals with the problem in a different way.