Can someone help me spot a leak in this NSPipe/NSFileHandle code? - objective-c

So I'm having this issue where once this NSFileHandle/NSPipe gets triggered... my CPU use and memory start going crazy. Problem is I'm finding it hard to find this leak. Any advice or help is appreciated. Cheers.
.h
NSTask *task;
NSPipe *pipe;
NSFileHandle *fileHandle;
#property (weak) IBOutlet NSTextField *commandInputTextField;
#property (unsafe_unretained) IBOutlet NSTextView *nsTastOutput;
#property (weak) IBOutlet NSButton *useOutputSwitch;
.m
- (id)init
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(readPipe:)
name:NSFileHandleReadCompletionNotification
object:nil];
}
- (void)tasker
{
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/bash"];
NSArray *argumentBuilder = [[_commandInputTextField stringValue] componentsSeparatedByString:#" "];
[task setArguments:argumentBuilder];
// Pipe output to ScrollView
if (_useOutputSwitch.state == YES)
{
if (!pipe)
{
pipe = [[NSPipe alloc] init];
}
fileHandle = [pipe fileHandleForReading];
[fileHandle readInBackgroundAndNotify];
[task setStandardOutput:pipe];
[task setStandardError:pipe];
}
// Launch task
[task launch];
}
- (void)readPipe:(NSNotification *)notification
{
NSData *data;
NSString *text;
if( [notification object] != fileHandle )
{
return;
}
data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSScroller *scroller = [[_nsTastOutput enclosingScrollView] verticalScroller];
BOOL shouldScrollToBottom = [scroller doubleValue] == 1.0;
NSTextStorage *ts = [_nsTastOutput textStorage];
[ts replaceCharactersInRange:NSMakeRange([ts length], 0) withString:text];
if (shouldScrollToBottom)
{
NSRect bounds = [_nsTastOutput bounds];
[_nsTastOutput scrollPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
}
if (data != 0)
{
[fileHandle readInBackgroundAndNotify];
}
}

I met a similar problem while using the readabilityHandler. I finally find out closing the fileHandle after task complete resolves the problem. Wish it helps your case.

Related

Objective-C: How to keep an application in dock?

I am trying to keep my application in dock, however I am not able to do this.
How I am trying to do?
#interface UserDefaultsHelper : NSUserDefaults
- (BOOL)addApplicationToDock:(NSString *)path;
- (BOOL)removeApplicationFromDock:(NSString *)name;
#end
#implementation UserDefaultsHelper
- (BOOL)addApplicationToDock:(NSString *)path {
NSDictionary *domain = [self persistentDomainForName:#"com.apple.dock"];
NSArray *apps = [domain objectForKey:#"persistent-apps"];
NSArray *matchingApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K CONTAINS %#", #"tile-data.file-data._CFURLString", path]];
if ([matchingApps count] == 0) {
NSMutableDictionary *newDomain = [domain mutableCopy];
NSMutableArray *newApps = [apps mutableCopy];
NSDictionary *app = [NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObjectsAndKeys:path, #"_CFURLString", [NSNumber numberWithInt:0], #"_CFURLStringType", nil] forKey:#"file-data"] forKey:#"tile-data"];
[newApps addObject:app];
[newDomain setObject:newApps forKey:#"persistent-apps"];
[self setPersistentDomain:newDomain forName:#"com.apple.dock"];
return [self synchronize];
}
return NO;
}
- (BOOL)removeApplicationFromDock:(NSString *)name {
NSDictionary *domain = [self persistentDomainForName:#"com.apple.dock"];
NSArray *apps = [domain objectForKey:#"persistent-apps"];
NSArray *newApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"not %K CONTAINS %#", #"tile-data.file-data._CFURLString", name]];
if (![apps isEqualToArray:newApps]) {
NSMutableDictionary *newDomain = [domain mutableCopy];
[newDomain setObject:newApps forKey:#"persistent-apps"];
[self setPersistentDomain:newDomain forName:#"com.apple.dock"];
return [self synchronize];
}
return NO;
}
#end
I found the above code from here. Code Source
the above code is getting compiled and running, however it's adding the application in the application's dock.
I am a newbie here. Please be lenient towards me as I do not have OS level of knowledge.
Many thanks in advance for your help and attention.
The code provided in question is correct the only problem was that the dock was not getting refreshed. So we just need to refresh the DOCK
In order to refresh dock I am calling a terminal command from obj-c
Terminal Command killall dock
....code...
[self runCommand:#"killall Dock"];
}
-(NSString*)runCommand:(NSString*)commandToRun;
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects:
#"-c" ,
[NSString stringWithFormat:#"%#", commandToRun],
nil];
NSLog(#"run command: %#",commandToRun);
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *output;
output = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
return output;
}

NSTask data split by newline \n

I'm new to objective-C, so please forgive me if I'm missing something. But we all have to start somewhere :)
I have a snippet of code I got from another open source project that executes a command and passes the result to another method. What I need to do is listen for each new line printed to stdout and do something with each line.
The code snippet I'm working with is the following:
NSMutableArray *args = [NSMutableArray array];
NSString *input = [details valueForKey:#"input"];
for (NSString *i in [input componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]) {
[args addObject:i];
}
NSTask *scriptTask = [NSTask new];
NSPipe *outputPipe = [NSPipe pipe];
if ([_NSFileManager() isExecutableFileAtPath:scriptPath] == NO) {
NSArray *chmodArguments = #[#"+x", scriptPath];
NSTask *chmod = [NSTask launchedTaskWithLaunchPath:#"/bin/chmod" arguments:chmodArguments];
[chmod waitUntilExit];
}
[scriptTask setStandardOutput:outputPipe];
[scriptTask setLaunchPath:scriptPath];
[scriptTask setArguments:args];
NSFileHandle *filehandle = [outputPipe fileHandleForReading];
[scriptTask launch];
[scriptTask waitUntilExit];
NSData *outputData = [filehandle readDataToEndOfFile];
NSString *outputString = [NSString stringWithData:outputData encoding:NSUTF8StringEncoding];
if (NSObjectIsNotEmpty(outputString)) {
[self.world.iomt inputText:outputString command:IRCPrivateCommandIndex("privmsg")];
}
So, rather than waiting for the process to complete then doing something with the result, I need to wait for each new line that gets printed by the command to stdout.
My background is mostly in web dev, so I guess if you were using Node.js and event emitters my aim would look similar to the following:
task = new Task("ls");
task.addListener("newline", function(data) {
somethingElse("sendmsg", data);
});
task.run();
Hopefully you understand what I'm tying to achieve. Thanks!
What you can do is add an observer to the NSFileHandle to notify you when it reads something.
example:
[fileHandler readInBackgroundAndNotify];
//Need to set NSTask output pipes
[self.task setStandardOutput: outputPipe];
[self.task setStandardError: outputPipe];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(readFromTask:) name:NSFileHandleReadCompletionNotification object:fileHandler];
-(void)readFromTask:(NSNotification *)aNotifcation;
{
NSData *data = [[aNotifcation userInfo] objectForKey: NSFileHandleNotificationDataItem];
if (data != 0) {
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// Do something with the text
[text release];
[[aNotifcation object] readInBackgroundAndNotify];
}
}
Then there's a notififcation you can add to the task to indicate when its completed so you can do clean up.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskEnded:) name:NSTaskDidTerminateNotification object:task];
- (void)taskEnded:(NSNotification *) aNotifcation
{
NSData *data = [[aNotifcation userInfo] objectForKey: NSFileHandleNotificationDataItem];
if (data != 0) {
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[text release];
}
[self.task release];
}
So from your code, you could remove:
[scriptTask waitUntilExit];
And move the data handling into the notifications.
NSData *outputData = [filehandle readDataToEndOfFile];
NSString *outputString = [NSString stringWithData:outputData encoding:NSUTF8StringEncoding];
if (NSObjectIsNotEmpty(outputString)) {
[self.world.iomt inputText:outputString command:IRCPrivateCommandIndex("privmsg")];
}
That's a rough idea, there's a good post at
http://www.cocoabuilder.com/archive/cocoa/306145-nstask-oddity-with-getting-stdout.html

applicationWillResignActive notification uncaught on device

I'm trying to save a simple string on a simple .plist file when the application is closing.
This is my code:
- (void)viewDidLoad {
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
self.kmOld.text = [array objectAtIndex:0];
[array release];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:NULL];
[super viewDidLoad];
}
Then follow the applicationWillResignActive method:
- (void)applicationWillResignActive:(NSNotification *)notification {
NSString *text = [[NSString alloc]initWithFormat:#"%#",kmNew.text];
NSLog(#"%#",text);
if ([text intValue]>0) {
NSArray *array = [[NSArray alloc] initWithObjects:text, nil];
BOOL success = [array writeToFile:[self dataFilePath] atomically:YES];
NSLog(#"%d",success);
[array release];
}
[text release];
}
This work fine on the simulator, but it seems to be ignored by the device...
Where is the problem? Thanks...
Take a look at http://www.cocoanetics.com/2010/07/understanding-ios-4-backgrounding-and-delegate-messaging/ for a good overview of the UIApplication lifecycle. Depending on the iOS version your app is running on and the action causing your app to terminate/background you may not be listening for the correct notification and may need to observe UIApplicationWillTerminateNotification as well.

NSTextView scrolling behavior and selecting behavior

I have an NSTextView that I'm outputting text from NSTask. Everything works as expected except the scrolling and selecting behaviors.
1: If I try to scroll up, the position of my scroll snaps back to the bottom instantly after I let go. Any ideas? I've looked through quite a bit of documentation about this and can't find anything about it.
2: If I select text, it removes it. I just want it to select so I can copy and paste. Lost on this one too.
Any tips or pointers would be most welcome. Thanks.
- (id)init
{
[super init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(readPipe:)
name:NSFileHandleReadCompletionNotification
object:nil];
return self;
}
- (void)kicked
{
task = [[NSTask alloc] init];
[task setLaunchPath:[self.kickLocationTextField stringValue]];
[task setArguments:kickBuild];
NSPipe *pipe = [[NSPipe alloc] init];
fileHandle = [pipe fileHandleForReading];
[fileHandle readInBackgroundAndNotify];
[task setStandardOutput:pipe];
[task setStandardError:pipe];
[task launch];
[task release];
[pipe release];
}
- (void)readPipe:(NSNotification *)notification
{
NSData *data;
NSString *text;
if( [notification object] != fileHandle )
{
return;
}
data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
[nsTaskOutput insertText:text];
[text release];
if (data != 0)
{
[fileHandle readInBackgroundAndNotify];
}
}
Try this instead of insertText::
NSScroller *scroller = nTaskOutput.enclosingScrollView.verticalScroller;
BOOL shouldScrollToBottom = scroller.doubleValue == 1.0;
NSTextStorage *ts = nTaskOutput.textStorage;
[ts replaceCharactersInRange:NSMakeRange(ts.length, 0) withString:text];
if (shouldScrollToBottom) {
NSRect bounds = nTaskOutput.bounds;
[nTaskOutput scrollPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
}

XML Parsing Class Leaking Strings on Rerun

I've been at this issue for two days, and no matter what I do, I cannot get it to stop leaking strings.
The class is a XML parser (using TouchXML), that is designed to run repeatedly throughout the life time of the application. The first time it runs, there are no leaks, everything cleans up perfectly. On the second run, it begins to leaks, almost always where ever strings are.
Some images from Instruments:
http://www.producerstudio.net/1.png
http://www.producerstudio.net/2.png
.h
#import <Foundation/Foundation.h>
#import "TouchXML.h"
#protocol RSSParsingComplete
-(void)parsingFinished;
#end
#interface RSS : NSObject<NSXMLParserDelegate>{
NSArray *rssURLArray;
NSMutableData *xmlData;
NSMutableArray *articles;
NSMutableArray *arrayOfArticles;
int numberOfFeeds;
NSDateFormatter *inputFormatter;
NSDateFormatter *outputFormatter;
id<RSSParsingComplete> delegate;
}
#property (nonatomic, retain) NSArray *rssURLArray;
#property (nonatomic, retain) NSMutableData *xmlData;
#property (nonatomic, retain) id<RSSParsingComplete> delegate;
#property (nonatomic, retain) NSMutableArray *articles;
#property (nonatomic, retain) NSMutableArray *arrayOfArticles;
#property (nonatomic, retain) NSDateFormatter *inputFormatter;
#property (nonatomic, retain) NSDateFormatter *outputFormatter;
-(id)initWithRSSArray:(NSArray *)inputURLArray;
-(void)connect;
-(NSArray *)feedArticles;
#end
.m
#import "RSS.h"
#implementation RSS
#synthesize xmlData, rssURLArray, articles, arrayOfArticles, delegate, inputFormatter, outputFormatter;
-(void)connect{
self.xmlData = [[[NSMutableData alloc] init] autorelease];
NSURL *rssURL = [[NSURL alloc] initWithString:[self.rssURLArray objectAtIndex:numberOfFeeds-1]];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:rssURL] delegate:self];
[urlConnection release];
[rssURL release];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self.xmlData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.xmlData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
[xmlData release];
[connection release];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
CXMLDocument *xmlDoc = [[[CXMLDocument alloc] initWithData:xmlData options:0 error:nil] autorelease];
self.articles = [[[NSMutableArray alloc] init] autorelease];
self.inputFormatter = [[[NSDateFormatter alloc] init] autorelease];
self.outputFormatter = [[[NSDateFormatter alloc] init] autorelease];
[self.inputFormatter setDateFormat:#"EEE, dd MMM yyyy HH:mm:ss zzz"];
[self.inputFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"] autorelease]];
[self.inputFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
[self.outputFormatter setDateFormat:#"dd.MM.yyyy HH:mm:ss"];
[self.outputFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"] autorelease]];
[self.outputFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSArray *itemNodes = [xmlDoc nodesForXPath:#"//item" error:nil];
for(CXMLElement *node in itemNodes){
NSMutableDictionary *article = [[NSMutableDictionary alloc] init];
for(int counter = 0; counter < [node childCount]; counter++){
if([[[node childAtIndex:counter] name] isEqualToString:#"title"]){
[article setObject:[[node childAtIndex:counter] stringValue] forKey:#"title"];
}
if([[[node childAtIndex:counter] name] isEqualToString:#"link"]){
[article setObject:[[node childAtIndex:counter] stringValue] forKey:#"url"];
}
if([[[node childAtIndex:counter] name] isEqualToString:#"description"]){
[article setObject:[[node childAtIndex:counter] stringValue] forKey:#"description"];
}
if([[[node childAtIndex:counter] name] isEqualToString:#"pubDate"]){
NSDate *tempDate = [self.inputFormatter dateFromString:[[node childAtIndex:counter] stringValue]];
[article setObject:[self.outputFormatter stringFromDate:tempDate] forKey:#"name"];
}
}
[self.articles addObject:article];
[article release];
}
NSArray *feedTitleNode = [xmlDoc nodesForXPath:#"//title" error:nil];
NSString *feedTitle = [[NSString alloc] initWithString:[[[feedTitleNode objectAtIndex:0] childAtIndex:0] stringValue]];
[self.articles addObject:feedTitle];
[feedTitle release];
[self.arrayOfArticles addObject:[articles copy]];
[self.articles removeAllObjects];
[inputFormatter release];
[outputFormatter release];
numberOfFeeds--;
if(numberOfFeeds > 0){
[self connect];
}else{
[delegate parsingFinished];
}
}
-(NSArray *)feedArticles{
NSLog(#"Array of Articles: %#", self.arrayOfArticles);
return self.arrayOfArticles;
}
-(id)initWithRSSArray:(NSArray *)inputURLArray{
self = [super init];
if (self) {
self.arrayOfArticles = [[[NSMutableArray alloc] init] autorelease];
self.rssURLArray = [[[NSArray alloc] initWithArray:inputURLArray] autorelease];
numberOfFeeds = [self.rssURLArray count];
[self connect];
}
return self;
}
-(void)dealloc{
[rssURLArray release];
[xmlData release];
[articles release];
[arrayOfArticles release];
[super dealloc];
}
- (id)init
{
self = [super init];
return self;
}
#end
I've done everything I can think of to resolve the leaks. I've read the Apple Memory Management guides, as well as the excellent guide on iPhoneDevSDK and that has helped me cut down on 90% of the leaks I originally had (the class doesn't leak so long as you call it once). Maybe i've been staring at this for too long, or maybe i'm missing something obvious.
I appreciate it!
First, should delegate be retained? I'm not sure why you need it as an instance variable at all. But since it's retained (and you don't seem to release it), your RSS object will retain a circular reference to itself and won't ever be released.
Second, do you need to keep the date formatters in an instance variable? It looks like you're allocating them and releasing them in the same method. Note that they're retained in the RSS instance and never released.