This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 11 years ago.
I have been building a program for the past few months. I finally got to the point where everything was working great. The program has been built for iOS 5 from the beginning. And i just checked on things that i thought i had cemented in and that were working fine when i checked a few days ago. But now everything is acting goofy. My recorder stopped working. Not sure in what area. My seeker's values arnt changing as they should. Playback of the recording is not working. The recorder used to take a second to load up where it would freeze the screen then start, now it is instantaneous which i find odd. Im sorry if im being vague but im totally stumped. The record button sets off a timer after the record command. In the timer there is an if statement, which has many clauses, one being myRecorder.isRecording == NO, and yet the if statement acts as if the conditions are met and executes the code inside, despite the recorder being told to record right before the timer even started. I AM BAFFLED. Please help. Thanks
Code for record button:
- (IBAction)record:(id)sender {
//[myPlayer play];
if ([sender tag] == 2){
[self stop];
[sender setTag:1];
[sender setTitle:#"Record"];
[sender setEnabled:NO];
NSLog(#"Tag is 2");
}else{
if ([myPlayer isPlaying]){
[myPlayer pause];
[myPlayer setCurrentTime:0];
}
anotherControl = 0;
theToolbar.userInteractionEnabled = NO;
[theToolbar setTintColor:myColor];
[theToolbar setAlpha:.5];
[controlSong setTintColor:[UIColor blackColor]];
beatPackTable.userInteractionEnabled = NO;
beatPackTable.hidden = YES;
onScreenTut.text = #"(Hold phone to your face and begin. When finished recording, hit the stop button)";
recordCover.titleLabel.text = #"Recording";
recordCover.titleLabel.textAlignment = UITextAlignmentCenter;
recordCover.hidden = NO;
NSString *docsDir;
myDate = [[NSDate alloc] init];
myDate = [NSDate date];
seekerTwo.userInteractionEnabled = NO;
homeButton.enabled = NO;
tracksButton.enabled = NO;
tutButton.enabled = NO;
previewBeatTwo.enabled = NO;
previewPauseTwo.enabled = NO;
/*
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
NSString *myDate2 = [NSString stringWithFormat:#"%#",[dateFormatter stringFromDate:myDate]];
*/
// dirPaths = NSSearchPathForDirectoriesInDomains(
// NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = NSTemporaryDirectory();
NSString *soundFilePath = [docsDir
stringByAppendingPathComponent:[NSString stringWithFormat:#"%#%i.caf",myDate,control]];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
NSDictionary *recordSettings = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:AVAudioQualityLow],
AVEncoderAudioQualityKey,
[NSNumber numberWithInt:16],
AVEncoderBitRateKey,
[NSNumber numberWithInt: 2],
AVNumberOfChannelsKey,
[NSNumber numberWithFloat:44100.0],
AVSampleRateKey,
nil];
NSError *error = nil;
myNewRecorder = [[AVAudioRecorder alloc]
initWithURL:soundFileURL
settings:recordSettings
error:&error];
if (error)
{
NSLog(#"error: %#", [error localizedDescription]);
} else {
[myNewRecorder prepareToRecord];}
seeker.userInteractionEnabled = NO;
voiceVolumeControl.enabled =YES;
recordButton.enabled = NO;
stopButton.enabled = YES;
myPlayer.currentTime = 0;
[myNewRecorder record];
[myPlayer play];
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
if ([sender tag] == 1){
[sender setTitle:#"End"];
[sender setTag:2];
}}
NSLog(#"%#",myDate);
}
Code for timer:
-(void)updateCounter:(NSTimer *)theTimer{
if ([myNewRecorder isRecording]) {
[seeker setValue:myPlayer.currentTime/myPlayer.duration animated:YES];}
if ([myPlayer2 isPlaying])
{
[seeker setValue:myPlayer2.currentTime/myPlayer2.duration animated:YES];
}
if ([myPlayer isPlaying] && myPlayer2.isPlaying == NO && myNewRecorder.isRecording == NO && isNotepadOpen == NO && anotherControl == 0) {
[myPlayer stop];
myPlayer.currentTime = 0;
[seeker setValue:0];
playButton.enabled = YES;
stopButton.enabled = NO;
NSLog(#"Its the timer for some reason");
}
if ([myNewRecorder isRecording] || [myPlayer isPlaying] || [myPlayer2 isPlaying]){
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
}else{
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
}
if ([myPlayer isPlaying]){
[seekerTwo setValue:myPlayer.currentTime/myPlayer.duration animated:YES];
}
}
See my answer (and others) to that question on Programmers SE: “It was working yesterday, I swear!” What can you do?
Basically, find what has changed since the sudden bugs, maybe a new delivery of iOS 5, Xcode or an external library that your software uses.
Related
I have an app that shows twitter account feed. So I have ImageView, textLabel and detailLabel for the content of the feed. The problem is that when all the data is loaded, the uiimage doesn't appear. When I click on the cell or scroll up-down, images are set. here is some of my code.
-(void)getImageFromUrl:(NSString*)imageUrl asynchronouslyForImageView:(UIImageView*)imageView andKey:(NSString*)key{
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageData =[NSData dataWithContentsOfURL:url];
if(imageData){
[self.imagesDictionary setObject:[UIImage imageWithData:imageData] forKey:key];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = self.imagesDictionary[key];
});
}
});
});
}
- (void)refreshTwitterHomeFeedWithCompletion {
// Request access to the Twitter accounts
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error){
if (granted) {
NSArray *accounts = [accountStore accountsWithAccountType:accountType];
// Check if the users has setup at least one Twitter account
if (accounts.count > 0)
{
ACAccount *twitterAccount = [accounts objectAtIndex:0];
NSLog(#"request.account ...%#",twitterAccount.username);
NSURL* url = [NSURL URLWithString:#"https://api.twitter.com/1.1/statuses/home_timeline.json"];
NSDictionary* params = #{#"count" : #"50", #"screen_name" : twitterAccount.username};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url parameters:params];
request.account = twitterAccount;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error)
{
NSString* errorMessage = [NSString stringWithFormat:#"There was an error reading your Twitter feed. %#",
[error localizedDescription]];
NSLog(#"%#",errorMessage);
}
else
{
NSError *jsonError;
NSArray *responseJSON = [NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
if (jsonError)
{
NSString* errorMessage = [NSString stringWithFormat:#"There was an error reading your Twitter feed. %#",
[jsonError localizedDescription]];
NSLog(#"%#",errorMessage);
}
else
{
NSLog(#"Home responseJSON..%#",(NSDictionary*)responseJSON.description);
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData:responseJSON];
});
}
}
}];
}
}
}];
}
-(void)reloadData:(NSArray*)jsonResponse
{
self.tweets = jsonResponse;
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
SNTwitterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[SNTwitterCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSDictionary *tweetDictionary = self.tweets[indexPath.row];
NSDictionary *user = tweetDictionary[#"user"];
NSString *userName = user[#"name"];
NSString *tweetContaint = tweetDictionary[#"text"];
NSString* imageUrl = [user objectForKey:#"profile_image_url"];
[self getImageFromUrl:imageUrl asynchronouslyForImageView:cell.imageView andKey:userName];
cell.profileImage.image = [UIImage imageNamed:#"images.png"];
NSArray *days = [NSArray arrayWithObjects:#"Mon ", #"Tue ", #"Wed ", #"Thu ", #"Fri ", #"Sat ", #"Sun ", nil];
NSArray *calendarMonths = [NSArray arrayWithObjects:#"Jan", #"Feb", #"Mar",#"Apr", #"May", #"Jun", #"Jul", #"Aug", #"Sep", #"Oct", #"Nov", #"Dec", nil];
NSString *dateStr = [tweetDictionary objectForKey:#"created_at"];
for (NSString *day in days) {
if ([dateStr rangeOfString:day].location == 0) {
dateStr = [dateStr stringByReplacingOccurrencesOfString:day withString:#""];
break;
}
}
NSArray *dateArray = [dateStr componentsSeparatedByString:#" "];
NSArray *hourArray = [[dateArray objectAtIndex:2] componentsSeparatedByString:#":"];
NSDateComponents *components = [[NSDateComponents alloc] init];
NSString *aux = [dateArray objectAtIndex:0];
int month = 0;
for (NSString *m in calendarMonths) {
month++;
if ([m isEqualToString:aux]) {
break;
}
}
components.month = month;
components.day = [[dateArray objectAtIndex:1] intValue];
components.hour = [[hourArray objectAtIndex:0] intValue];
components.minute = [[hourArray objectAtIndex:1] intValue];
components.second = [[hourArray objectAtIndex:2] intValue];
components.year = [[dateArray objectAtIndex:4] intValue];
NSTimeZone *gmt = [NSTimeZone timeZoneForSecondsFromGMT:2];
[components setTimeZone:gmt];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDate *date = [calendar dateFromComponents:components];
NSString *tweetDate = [self getTimeAsString:date];
NSString *tweetValues = [NSString stringWithFormat:#"%# :%#",userName,tweetDate];
cell.textLabel.text = [NSString stringWithFormat:#"%#",tweetValues];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#",tweetContaint];
[cell.detailTextLabel setFont:[UIFont fontWithName:#"Helvetica" size:20]];
return cell;
}
- (NSString*)getTimeAsString:(NSDate *)lastDate {
NSTimeInterval dateDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int nrSeconds = dateDiff;//components.second;
int nrMinutes = nrSeconds / 60;
int nrHours = nrSeconds / 3600;
int nrDays = dateDiff / 86400; //components.day;
NSString *time;
if (nrDays > 5){
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterShortStyle];
[dateFormat setTimeStyle:NSDateFormatterNoStyle];
time = [NSString stringWithFormat:#"%#", [dateFormat stringFromDate:lastDate]];
} else {
// days=1-5
if (nrDays > 0) {
if (nrDays == 1) {
time = #"1 day ago";
} else {
time = [NSString stringWithFormat:#"%d days ago", nrDays];
}
} else {
if (nrHours == 0) {
if (nrMinutes < 2) {
time = #"just now";
} else {
time = [NSString stringWithFormat:#"%d minutes ago", nrMinutes];
}
} else { // days=0 hours!=0
if (nrHours == 1) {
time = #"1 hour ago";
} else {
time = [NSString stringWithFormat:#"%d hours ago", nrHours];
}
}
}
}
return [NSString stringWithFormat:NSLocalizedString(#"%#", #"label"), time];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
The fundamental problem is that the standard imageView property of the standard table view cell will automatically resize itself based upon the image that is present when cellForRowAtIndexPath finishes. But since there is no image yet when you first present the table, the cell is laid out as if there's no image. And when you asynchronously update the image view's image, it won't resize the image view.
There are a couple of ways of solving this:
Don't use the default imageView provided by UITableViewCell, but rather define your own custom cell subclass with an IBOutlet to its own UIImageView property. Make sure that this UIImageView has a fixed layout (i.e., it doesn't use the intrinsic size derived from the underlying image).
If you do that, you can asynchronously update the image property for your custom UIImageView outlet, and because the layout was not contingent upon the presence of the image, any asynchronous updates of that image should appear correctly.
When you receive the image, don't just set the image view's image property, but rather reload the whole row associated with that NSIndexPath using reloadRowsAtIndexPaths.
If you do this, the cell will be laid out correctly assuming that you retrieve the image from the cache correctly, and do so before cellForRowAtIndexPath finishes.
Note, if you do this, you will need to fix your getImageFromUrl to actually try to retrieve the image from the cache first (and do this from the main queue, before to dispatch to the background queue), or else you'll end up in an endless loop.
Having said that, there are deeper problems here.
As I mentioned above, you're caching your images, but never using the cache when retrieving the images.
You are asynchronously updating the image view.
You should initialize the image property of the UIImageView before you initiate the new asynchronous fetch, otherwise when a cell is reused, you'll see the old image there until the new image is retrieved.
What if the cell was reused in the intervening period between calling getImageFromUrl and when the asynchronous request finishes? You'll be updating the image view for the wrong cell. (This problem will be more apparent when doing this over a slow connection. Run your code using the network link conditioner to simulate slow connections and you'll see the problem I'm describing.)
What if the user rapidly scrolls down to the 100th row in the table? The network requests for the visible cells will be backlogged behind the other 99 image requests. You could even get timeout errors on slow connections.
There are a bunch of tactical little issues in getImageFromUrl.
Why dispatching synchronously from global queue to another global queue? That's unnecessary. Why dispatching UI update synchronously to main thread? That's inefficient.
Why define imageData as __block outside of the block; just define it within the block and you don't need __block qualifier.
What if you didn't receive a valid UIImage from the network request (e.g. you got a 404 error message); the existing code would crash. There are all sorts of responses the server might provide which are not a valid image, and you really must identify that situation (i.e. make sure that not only was NSData you received not nil, but also that the UIImage that you created from it was not nil, too).
I'd probably use NSCache rather than NSMutableDictionary for the cache. Also, regardless of whether you use NSCache or NSMutableDictionary, you want to make sure that you respond to memory pressure events and empty that cache if needed.
We can go through all of these individual problems, but it's a non-trivial amount of work to fix all of this. I might therefore suggest you consider the UIImageView categories of SDWebImage or AFNetworking. They take care of most of these issues, plus others. It will make your life much, much easier.
I spent my whole 2weeks for just trying to resolve this problem. So frustrate!
Following 2 functions are what I'm using for fetching a image from device library.
If I use "setImage" function multiple times I keep losing my free memory on my iOS Device.
I think "[imageFromAsset initWithCGImage:[[myasset defaultRepresentation] fullScreenImage]];" in assetImage function causes the problem.
Can any guys help me? Any clues or thinking would be SUPER appreciate! Please!
- (void)setImage:(NSURL *)imageURL{
UIImage *imageFromDeviceLibrary = [[UIImage alloc] init];
[[DevicePhotoControl sharedInstance] assetImage:[imageURL absoluteString] imageToStore:imageFromDeviceLibrary];
UIImageView *fullImageView = [[UIImageView alloc] initWithImage:imageFromDeviceLibrary];
[imageFromDeviceLibrary release];
[self.view addSubview:fullImageView];
[fullImageView release];
}
- (void)assetImage:(NSString *)assetURL imageToStore:(UIImage *)imageFromAsset{
// Handling exception case for when it doesn't have assets-library
if ([assetURL hasPrefix:#"assets-library:"] == NO) {
assetURL = [NSString stringWithFormat:#"%#%#",#"assets-library:",assetURL];
}
__block BOOL busy = YES;
ALAssetsLibrary* assetslibrary = [[[ALAssetsLibrary alloc] init] autorelease];
//get image data by URL
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
[imageFromAsset initWithCGImage:[[myasset defaultRepresentation] fullScreenImage]];
busy = NO;
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Library Image Fetching Failed : %#",[myerror localizedDescription]);
busy = NO;
};
[assetslibrary assetForURL:[NSURL URLWithString:assetURL]
resultBlock:resultblock
failureBlock:failureblock];
while (busy == YES) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
The moment the AssetLibrary is released, all the asset object will be gone with it.
I suggest that you create your assetLibrary in the app delegate to keep it alive and only reset it when you receive change notification ALAssetsLibraryChangedNotification from the ALAssetLibrary
here
It may help you.
Long time listener, first time caller here so forgive me if I am not being explicit enough. I am currently scratching my head over an issue with NSError objects. Here is how things are setup:
-(void)mainMethod {
NSError *myError = nil;
NSString *myString = #"A really great string. 1234.";
if ([self parseAndStoreString:myString error:&myError] == NO) {
[self popupWithErrorMessage:[[myError localizedDescription] copy]];
}
-(BOOL)parseAndStoreString:(NSString *)incomingString error:(NSError *__autoreleasing *)err {
if ([self setupSomething error:err] == NO) {
return NO;
}
if ([self doSomethingWithString:incomingString error:err] == NO) {
return NO;
}
if ([self storeString:incomingString error:err] == NO) {
return NO;
}
}
-(BOOL)doSomethingWithString:(NSString *)incomingString error:(NSError *__autoreleasing *)err {
if (incomingString == nil) {
DLog(#"String was null! See? : %#", incomingString);
NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:EDEmptyStringErrorDescription forKey:NSLocalizedDescriptionKey];
if (err != nil) *err = [NSError errorWithDomain:EDStringDomain code:EDEmptyStringError userInfo:errorInfo];
return NO;
}
if (somethingElseIsWrongWithString) {
DLog(#"Another Problem Geesh");
NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:EDAnotherStringErrorDescription forKey:NSLocalizedDescriptionKey];
if (err != nil) *err = [NSError errorWithDomain:EDStringDomain code:EDAnotherStringError userInfo:errorInfo];
return NO;
}
}
Now whichever error hits is being properly established and displayed in the popup as expected the first time through. The problem comes in when the user clicks on something that activates either the 'mainMethod' again or another method that calls 'parseAndStoreString:error:' (there are three or four that call the method). If there is another error, the popup is showing the description for the first error and the second error glued together into one string. This is ARC code, so my understanding is that the compiler should be releasing the myError object (or whatever NSError object the other methods are creating) after the error is presented. I don't think the problem is in the 'popupWithErrorMessage' method since I only pass in a copy of the localized description which gets displayed and then destroyed by [popupView orderOut:self]. Any ideas on how the heck these error messages keep piling up in the NSError object?
#torrey.lyons : Sure, here is the code for togglePopupWithMessage:onView-
- (void)popupWithErrorMessage:(NSString *)errorToDisplay {
if (!messagePopup) {
if (errorToDisplay == nil) {
DLog(#"Warning, incoming error message was nil");
return;
}
NSDictionary *messageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSColor whiteColor], NSForegroundColorAttributeName,
[NSFont systemFontOfSize:12], NSFontAttributeName,
nil];
NSAttributedString *messageWithAttributes = [[NSAttributedString alloc]
initWithString:errorToDisplay
attributes:messageAttributes];
NSPoint messagePoint = NSMakePoint(NSMidX([[self.mainSplitView.subviews objectAtIndex:1] frame]),
NSMinY([anchorView frame])+4.0f);
messagePopup = [[MAAttachedWindow alloc] initWithView:popupView
attachedToPoint:messagePoint
inWindow:[self window]
onSide:MAPositionBottom
atDistance:0];
[messagePopup setAlphaValue:0.0f];
NSTextStorage *messageStorage = popupMessage.textStorage;
[messageStorage beginEditing];
[messageStorage insertAttributedString:messageWithAttributes atIndex:0];
[messageStorage endEditing];
[messagePopup setFrame:NSMakeRect(messagePopup.frame.origin.x, messagePopup.frame.origin.y, 250.0f, 100.0f)
display:YES];
messageWithAttributes = nil;
messageAttributes = nil;
errorToDisplay = #"";
[[self window] addChildWindow:messagePopup ordered:NSWindowAbove];
[[messagePopup animator] setAlphaValue:1.0f];
[popupMessage setNeedsDisplay:YES];
[NSTimer scheduledTimerWithTimeInterval:3.5
target:self
selector:#selector(turnOffPopupFromTimer:)
userInfo:nil
repeats:NO];
} else {
[[self window] removeChildWindow:messagePopup];
[messagePopup fadeOutAndOrderOut:YES];
messagePopup = nil;
}
- (void)turnOffPopupFromTimer:(NSTimer *)timer {
if (messagePopup) {
//Added to correct problem \/\/\/\/
NSTextStorage *messageStorage = popupMessage.textStorage;
[messageStorage beginEditing];
[messageStorage deleteCharactersInRange:NSMakeRange(0, messageStorage.characters.count)];
[messageStorage endEditing];
//Added to correct problem /\/\/\/\
[[self window] removeChildWindow:messagePopup];
[messagePopup fadeOutAndOrderOut:YES];
messagePopup = nil;
}
}
I suspect the problem is in popupWithErrorMessage, which is piling up the localized descriptions. Even if there is a bug in the lifecycle of your NSError objects, each one created for a separate error is a separate object and won't have the localized description of other NSError objects stuffed into it. On the other hand, your description of popupWithErrorMessage sounds suspicious: [popupView orderOut:self] just removes the window from the screen but does not release it or cause it to be destroyed. Can you post the code for popupWithErrorMessage?
Hello everyone, I want to create if loop that running asynchoursly with this code:
NSString *care = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://localhost/untitled.txt"]];
if (care == #"ONRED") { //untitled.txt = ONRED
[red_on setHidden:NO];
[red_off setHidden:YES];
}
How I can run the if statement like a loop?
If I get you right, you can put those statements in a method and call performSelectorInBackground:
(void)asyncMethod {
NSString *care = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://localhost/untitled.txt"]];
if (care == #"ONRED") { //untitled.txt = ONRED
[red_on setHidden:NO];
[red_off setHidden:YES];
}
}
// in some other method
[self performSelectorInBackground:#selector(asyncMethod) withObject:nil];
Another option is to use the grand central dispatch (as described in this answer):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSString *care = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://localhost/untitled.txt"]];
if (care == #"ONRED") { //untitled.txt = ONRED
[red_on setHidden:NO];
[red_off setHidden:YES];
}
});
OK, I have a memory management problem that is driving me up a wall. At one point I swear this worked with no problems, but now it's leaking memory everywhere and I can't figure out why.
To begin with I'm starting an NSTask and then running a loop while the task is running.
NSTask *encodingTask = [[NSTask alloc] init];
NSFileHandle *taskStdout = [NSFileHandle fileHandleForWritingAtPath:encodingOutput];
[encodingTask setStandardOutput:taskStdout];
[encodingTask setStandardError:taskStdout];
NSString argString = [NSString stingWithString: #"some arguments"];
[encodingTask setArguments:taskArgs];
[encodingTask setLaunchPath:somePath];
[encodingTask launch];
while ([encodingTask isRunning]){
sleep(1);
[self encodeProgressTimer];
}
The encodeProgessTimer method is grabbing the last line from the stdOut and placing that in the menu bar:
- (void)encodeProgressTimer
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"menuProgress"]) {
// Read the last line
NSString *fileData = [NSString stringWithContentsOfFile:encodingOutput encoding:NSASCIIStringEncoding error:nil];
NSArray *lines = [fileData componentsSeparatedByString:#"\r"];
NSString *lastLine = [lines objectAtIndex:[lines count] - 1];
NSString *percent;
NSString *eta;
BOOL dataFound = NO;
if ([lastLine length] == 71) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {61,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
else if ([lastLine length] == 72) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {62,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
else if ([lastLine length] == 70) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {60,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
if (dataFound) {
NSMutableString *bottomStr = [[NSMutableString alloc]
initWithFormat:#"Encoding: %#%% - ETA %#", percent, eta];
[appDelegate setMenuTop:topString andBottom:bottomStr];
[bottomStr release];
}
}
}
It's my understanding that anything I'm not specifically allocating and initializing should be auto released when the method has completed, but that isn't the case. Memory usage goes up exponentially every second when this is called. If I look at my memory allocations the number of living CFstings goes through the roof. If I turn of encodeProgressTimer my problems go away. I tried adding an autorelease pool to encodeProgressTimer which made memory usage very stable, however after 20 minutes or so of running I get a EXC_BAD_ACCESS. Turning on Zombies turns that into:
*** -[NSConcreteAttributedString _drawCenteredVerticallyInRect:scrollable:]: message sent to deallocated instance 0x2bc756e0
I actually went through and changed each variable declaration into it's alloc/init counterpart and manually released them, but that didn't solve the problem either. At this point I'm pretty stumped.
Also for the sake of completeness the [appDelegate setMenuTop: andBottom:] method looks like this:
-(void) setMenuTop: (NSString *) top andBottom: (NSString *) bottom
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"menuProgress"]) {
[statusItem setImage:nil];
NSMutableParagraphStyle *lineHeight = [[NSMutableParagraphStyle alloc] init];
[lineHeight setMaximumLineHeight:10.5];
[lineHeight setLineBreakMode:NSLineBreakByTruncatingMiddle];
OperationQueue *opQueue = [OperationQueue sharedQueue];
NSString *sBuffer = [[NSMutableString alloc] initWithFormat: #"%# (%i More)\n%#", top, [opQueue queueCount] - 1, bottom];
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont menuFontOfSize:9], NSFontAttributeName, lineHeight, NSParagraphStyleAttributeName, nil];
if (statusTitle)
[statusTitle release];
statusTitle = [[NSAttributedString alloc] initWithString: sBuffer attributes: attributes];
[statusItem setAttributedTitle: statusTitle];
[lineHeight release];
[sBuffer release];
[attributes release];
}
}
There will be loads of stuff in the autorelease pool, but you need to explicitly drain it for the memory to go away. Change your while loop as follows:
while ([encodingTask isRunning]){
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
sleep(1);
[self encodeProgressTimer];
[pool drain];
}
Other stuff: if you are running this on a thread, you can't update user interface items directly. You need to use something like performSelectorOnMainThread: to actualy update the UI. If you are not running this on a thread, you need to rethink your design. The whole UI of your application will freeze while the loop is running.
You may want to use properties here or nil out the reference.
if (statusTitle) {
[statusTitle release];
statusTitle = nil;
}