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;
}
Related
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.
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]];
}
Hey experts, I'm having a little trouble with NSThread. Xcode keeps on giving me "* __NSAutoreleaseNoPool(): Object 0x5694dc0 of class NSCFString autoreleased with no pool in place - just leaking" errors.
I'm correctly declaring the pool with the line
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
then at the end of my loop I use:
[pool release];
Is it because I'm using a delegate method as the performSelectorInBackground?
Thanks stackoverflow.
- (void)preFetch { //process filenames to be downloaded and assign types to each one
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *regions = [NSArray arrayWithObjects: #"dr_national", #"ds_ir", #"conus_FL360", #"FL360_conus", #"dr_nw", #"dr_nc", #"dr_ne", #"dr_sw", #"dr_sc", #"dr_se", #"ds_ir_nw", #"ds_ir_nc", #"ds_ir_ne", #"ds_ir_sw", #"ds_ir_sc", #"ds_ir_se", nil];
NSError* error;
for (NSString *regionDir in regions) {
NSLog(#"region now: %#", regionDir); foo = 0;
NSString *regUrl = [NSString stringWithFormat:#"http://someUrl/%#/index.lst", regionDir ];
NSString* text1 = [NSString stringWithContentsOfURL:[NSURL URLWithString:regUrl ] encoding:NSASCIIStringEncoding error:&error];
NSArray *listItems = [text1 componentsSeparatedByString:#"\n"];
for (int k=0; k<[listItems count]; k++) {
if ([[listItems objectAtIndex:k] length] != 0){
NSString *newpath = [NSString stringWithFormat:#"http://someUrl/%#", [listItems objectAtIndex:k]];
NSLog(#"newpath: %#",newpath);
[self performSelectorInBackground:#selector(moveProgressBar) withObject:nil];
[self fetchImages:newpath:type]; //pass multiple arguments to fetchImages, newpath and type
}
}
}
[pool release];
}
- (void)moveProgressBar{
[delegate increaseAmount];
}
You should just set up an autorelease pool in your method, since that's being called on a different thread.
- (void)moveProgressBar
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[delegate increaseAmount];
[pool drain];
}
Edit
Having said that, looking at the code itself, it seems that you might be trying to update the UI from a background thread? Any code that does that should be executed on the main thread.
If you have a long running process that you want to run which doesn't lock the UI, and keeps the user updated on progress, the typical pattern would be to do the processing itself on a background thread, and periodically update the UI using performSelectorOnMainThread:.
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.
I have a question on thread safety while using NSMutableDictionary.
The main thread is reading data from NSMutableDictionary where:
key is NSString
value is UIImage
An asynchronous thread is writing data to above dictionary (using NSOperationQueue)
How do I make the above dictionary thread safe?
Should I make the NSMutableDictionary property atomic? Or do I need to make any additional changes?
#property(retain) NSMutableDictionary *dicNamesWithPhotos;
NSMutableDictionary isn't designed to be thread-safe data structure, and simply marking the property as atomic, doesn't ensure that the underlying data operations are actually performed atomically (in a safe manner).
To ensure that each operation is done in a safe manner, you would need to guard each operation on the dictionary with a lock:
// in initialization
self.dictionary = [[NSMutableDictionary alloc] init];
// create a lock object for the dictionary
self.dictionary_lock = [[NSLock alloc] init];
// at every access or modification:
[object.dictionary_lock lock];
[object.dictionary setObject:image forKey:name];
[object.dictionary_lock unlock];
You should consider rolling your own NSDictionary that simply delegates calls to NSMutableDictionary while holding a lock:
#interface SafeMutableDictionary : NSMutableDictionary
{
NSLock *lock;
NSMutableDictionary *underlyingDictionary;
}
#end
#implementation SafeMutableDictionary
- (id)init
{
if (self = [super init]) {
lock = [[NSLock alloc] init];
underlyingDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) dealloc
{
[lock_ release];
[underlyingDictionary release];
[super dealloc];
}
// forward all the calls with the lock held
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
[lock lock];
#try {
return [underlyingDictionary performv:sel : args];
}
#finally {
[lock unlock];
}
}
#end
Please note that because each operation requires waiting for the lock and holding it, it's not quite scalable, but it might be good enough in your case.
If you want to use a proper threaded library, you can use TransactionKit library as they have TKMutableDictionary which is a multi-threaded safe library. I personally haven't used it, and it seems that it's a work in progress library, but you might want to give it a try.
Nowadays you'd probably go for #synchronized(object) instead.
...
#synchronized(dictionary) {
[dictionary setObject:image forKey:name];
}
...
#synchronized(dictionary) {
[dictionary objectForKey:key];
}
...
#synchronized(dictionary) {
[dictionary removeObjectForKey:key];
}
No need for the NSLock object any more
after a little bit of research I want to share with you this article :
Using collection classes safely with multithreaded applications
http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html
It looks like notnoop's answer may not be a solution after all. From threading perspective it is ok, but there are some critical subtleties. I will not post here a solution but I guess that there is a good one in this article.
I have two options to using nsmutabledictionary.
One is:
NSLock* lock = [[NSLock alloc] init];
[lock lock];
[object.dictionary setObject:image forKey:name];
[lock unlock];
Two is:
//Let's assume var image, name are setup properly
dispatch_async(dispatch_get_main_queue(),
^{
[object.dictionary setObject:image forKey:name];
});
I dont know why some people want to overwrite setting and getting of mutabledictionary.
Even the answer is correct, there is an elegant and different solution:
- (id)init {
self = [super init];
if (self != nil) {
NSString *label = [NSString stringWithFormat:#"%#.isolation.%p", [self class], self];
self.isolationQueue = dispatch_queue_create([label UTF8String], NULL);
label = [NSString stringWithFormat:#"%#.work.%p", [self class], self];
self.workQueue = dispatch_queue_create([label UTF8String], NULL);
}
return self;
}
//Setter, write into NSMutableDictionary
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = #(count);
}
});
}
//Getter, read from NSMutableDictionary
- (NSUInteger)countForKey:(NSString *)key {
__block NSUInteger count;
dispatch_sync(self.isolationQueue, ^(){
NSNumber *n = self.counts[key];
count = [n unsignedIntegerValue];
});
return count;
}
The copy is important when using thread unsafe objects, with this you could avoid the possible error because of unintended release of the variable. No need for thread safe entities.
If more queue would like to use the NSMutableDictionary declare a private queue and change the setter to:
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = #(count);
}
});
}
IMPORTANT!
You have to set an own private queue without it the dispatch_barrier_sync is just a simple dispatch_sync
Detailed explanation is in this marvelous blog article.
In some cases you might NSCache class. The documentation claims that it's thread safe:
You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
Here is article that describes quite useful tricks related to NSCache