Cocoa WebView: Load-time when computer idles - objective-c

I wrote a simple Mac OS application. The view only contains a hidden WebView and a NSTextView. At a certain time 100 URLs will be parsed, one after another. A URL is loaded in the WebView, some DOM tree parsing happens, rinse and repeat.
Everything works fine except there are some major differences in loading times when the computer is in idle (no user activity). When i am using the computer a URL takes about 2 seconds to load. When i am not using the computer for some time, a URL takes about 1 minute to load.
When i come back to the computer after some time and see in the TextView that the last URLs took about a minute, the next ones immediately will be loaded in seconds. So it has to be something with the user activity on the system. Right? Anybody knows why?
I am on OS X 10.9.4.
Here is the code how i load the URLs:
#pragma mark -
- (void)processNextUrl
{
// No url, stop
if (_processQueue.count == 0)
return;
// Process url
NSString *urlString = [_processQueue objectAtIndex:0];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[_webView mainFrame] loadRequest:request];
}
#pragma mark - WebFrameLoadDelegate
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
// Page ready for parsing?
if ([[sender mainFrame] isEqual:frame] == NO)
return;
// Append date, time and url to TextView
NSString *url = [[[[frame dataSource] request] URL] absoluteString];
[self logText:url];
// Parser does some DOM parsing and outputs to TextView
Parser *parser = [[Parser alloc] initWithWebFrame:frame delegate:self];
[parser parse];
}
#pragma mark - ParserDelegate Methods
- (void)parser:(Parser *)parser didFinishParsingUrl:(NSString *)url
{
[_processQueue removeObjectAtIndex:0];
[self processNextUrl];
}

I'd bet AppNap is kicking in and starving your process of resources.
You may want to either disable AppNap in the Finder's Get Info window of your app - a temporary test to see if I'm right of course - or just use NSProcessInfo's beginActivityWithOptions:reason: to tell the system that the activity was user initiated and the app should not be throttled.

Related

how to stop the infinite loop and allow to accept the touches during this loop

while (true)
{
endtime = CFAbsoluteTimeGetCurrent();
double difftime = endtime - starttime;
NSLog(#"The tine difference = %f",difftime);
if (difftime >= 10)
{
NSURL *url= [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/a0.wav" , [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops=0;
[audioPlayer play];
starttime = endtime;
}
}
when i call this method my application enters in an infinite loop and doesnot accept any touches again how can solve this please?
It looks like you want it to play a sound every 10 seconds. You would be better using a timer like this...
NSTimer *yourTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(repeatedAction)
userInfo:nil
repeats:YES];
Then have your repeating function...
- (void)repeatedAction
{
// do your stuff in here
}
The UI will be responsive all the time and you can have a cancel button with an action like this...
- (void)cancelRepeatedAction
{
[self.yourTimer invalidateTimer];
}
This will stop the time rand the repeated action.
HOWEVER
You are also trying to download a file EVERY time you run your action.
A better approach would be to download the file once and store it.
Even better would be to download the file asynchronously.
This is not how you structure an event-driven application. You are blocking the event processing queue (NSRunLoop) by not returning out of this function. So no touch events will get a chance to be processed until you return from this loop.
You need to play the sounds asynchronously - which AVAudioPlayer can do for you. Read up on the ADC documentation about queueing and playing music in AVFoundation.
For a start, you don't need the while loop at all. Just start the playback, and register for notifications of playback progress. Make the _audioPlayer an ivar of your controller class, as its lifetime persists beyond the method used to load the file and initiate playback:
NSURL *url= [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/a0.wav" , [[NSBundle mainBundle] resourcePath]]];
NSError *error;
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
_audioPlayer.numberOfLoops=0;
[_audioPlayer play];
Then define a delegate method which implements the AVAudioPlayerDelegate protocol, and provide at least this method:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
}
You can also provide a touch event handler in your view, which calls a pauseAudio: method in the controller class, which then calls:
[audioPlayer pause];
Have a good read through the docs, and study the sample code:
iOS Developer Library - ADC AVAudioPlayer Class Reference
Have a look at how this app is structured, with a controller registering for notifications and using a delegate for callbacks. It also uses a timer and features GUI updates.
iOS Developer Library - avTouch - media player sample code

Remove part of the url

I am working with Sencha Touch and PhoneGap. The code is for iOS and it's waiting for url with suffix #phonegap-external ..
- (BOOL) webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *url = [request URL];
if ( ([url fragment] != NULL) && ([[url fragment] rangeOfString:#"phonegap=external"].location != NSNotFound))
{
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
return NO;
}
}
return [super webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
}
So because I haven't written any line of code in Obj-C, I need your help. Can someone edit code, so that it would open url without suffix.
EDIT:
If user opens url in app, it would open it inside webview but on occasion I would prefer that url is opened in Safari. So this code checks if url has suffix like this - http://google.com#phonegap-external and than it opens it in Safari. Only thing what bugs me, is url is not changed into http://google.com and it opens given url http://google.com/#phonegap-external. Could someone please fix this.
If you're sure that the part of the URL that indicates whether it's to be opened inline or externally (i. e. the #phonegap-external string) is always the last one in the URL, then you can try removing this suffix by writing something like as follows:
NSString *orig = [url absoluteString];
size_t frLen = [#"phonegap-external" length];
NSString *stripped = [orig substringToIndex:orig.length - frLen];
NSURL *newURL = [NSURL URLWithString:stripped];
[[UIApplication sharedApplication] openURL:newURL];

Have buttons override other processes

In my app, the root view controller acquires information from the internet, parses it, and displays it in viewDidAppear. I am using this method because my app in embedded in a UINavigationController and this way the root view controller will reload its data when the user presses the back button and pops to the root view.
When this occurs, it takes some time for the information from the internet to be acquired and displayed. During this time, if the user clicks a button to move to a different view, the button action will not occur until the view controller has completed its process of acquiring the web data.
How can I make it so that the buttons will override the other processes and immediately switch the view? Is this safe? Thanks in advance.
Edit
Here's an example of a portion where I take the information off of the site (My app parses the HTML).
NSURL *siteURL = [NSURL URLWithString:#"http://www.ridgefield.org/ajax/dist/emergency-announcements"];
NSError *error;
NSString *source = [NSString stringWithContentsOfURL:siteURL
encoding:NSUTF8StringEncoding
error:&error];
This is where Apple folks would hound, "don't block the main thread!".
The primary suggestion for this kind of workflow is to use a separate thread (read: queue) for loading data from the web. Then the worker who completes the load can set some property on your view controller, and inside that setter is where the UI should be updated. Remember to call the setter back on the main thread.
There are several ways to skin the concurrency cat, but the answer to this particular question leaves them out of scope. The short answer is to not do the load in the main thread, and that should lead you to the right place.
The NSString method stringWithContentsOfURL is synchronous and will block your main thread.
Instead of using background threads to solve the problem, you can use asynchronous URL requests. This does not block the user interface because a delegate protocol is used to let you know when the request is complete. For example:
NSURL *url = [NSURL URLWithString:#""http://www.ridgefield.org/ajax/dist/emergency-announcements""];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLConnection* theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
And then some of your delegate methods are:
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)theResponse
{
// create received data array
_receivedData = [[NSMutableData alloc] init];
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)theData
{
// append to received data.
[_receivedData appendData:theData];
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
// now the connection is complete
NSString* strResult = [[NSString alloc] initWithData: _receivedData encoding:NSUTF8StringEncoding];
// now parse strResult
}

Calling -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] never returns

I have a straightforward NSDocument-based Mac OS X app in which I am trying to implement iCloud Document storage. I'm building with the 10.7 SDK.
I have provisioned my app for iCloud document storage and have included the necessary entitlements (AFAICT). The app builds, runs, and creates the local ubiquity container Documents directory correctly (this took a while, but that all seems to be working). I am using the NSFileCoordinator API as Apple recommended. I'm fairly certain I am using the correct UbiquityIdentifier as recommended by Apple (it's redacted below tho).
I have followed Apple's iCloud Document storage demo instructions in this WWDC 2011 video closely:
Session 107 AutoSave and Versions in Lion
My code looks almost identical to the code from that demo.
However, when I call my action to move the current document to the cloud, I experience liveness problems when calling the -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] method. It never returns.
Here is the relevant code from my NSDocument subclass. It is almost identical to Apple's WWDC demo code. Since this is an action, this is called on the main thread (as Apple's demo code showed). The deadlock occurs toward the end when the -setUbiquitous:itemAtURL:destinationURL:error: method is called. I have tried moving to a background thread, but it still never returns.
It appears that a semaphore is blocking while waiting for a signal that never arrives.
When running this code in the debugger, my source and destination URLs look correct, so I'm fairly certain they are correctly calculated and I have confirmed the directories exist on disk.
Am I doing anything obviously wrong which would lead to -setUbiquitous never returning?
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSString *bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSString *appID = [NSString stringWithFormat:#"XXXXXXX.%#.macosx", bundleID];
BOOL makeUbiquitous = 1 == [sender tag];
NSURL *destURL = nil;
NSFileManager *mgr = [NSFileManager defaultManager];
if (makeUbiquitous) {
// get path to local ubiquity container Documents dir
NSURL *dirURL = [[mgr URLForUbiquityContainerIdentifier:appID] URLByAppendingPathComponent:#"Documents"];
if (!dirURL) {
NSLog(#"cannot find URLForUbiquityContainerIdentifier %#", appID);
return;
}
// create it if necessary
[mgr createDirectoryAtURL:dirURL withIntermediateDirectories:NO attributes:nil error:nil];
// ensure it exists
BOOL exists, isDir;
exists = [mgr fileExistsAtPath:[dirURL relativePath] isDirectory:&isDir];
if (!(exists && isDir)) {
NSLog(#"can't create local icloud dir");
return;
}
// append this doc's filename
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// get path to local Documents folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
// append this doc's filename
destURL = [[dirs objectAtIndex:0] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSFileCoordinator *fc = [[[NSFileCoordinator alloc] initWithFilePresenter:self] autorelease];
[fc coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForMoving writingItemAtURL:destURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *fileURL, NSURL *destURL) {
NSError *err = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
[fc itemAtURL:fileURL didMoveToURL:destURL];
} else {
NSWindow *win = ... // get my window
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
}
}];
}
I don't know if these are the source of your problems, but here are some things I'm seeing:
-[NSFileManager URLForUbiquityContainerIdentifier:] may take a while, so you shouldn't invoke it on the main thread. see the "Locating the Ubiquity Container" section of this blog post
Doing this on the global queue means you should probably use an allocated NSFileManager and not the +defaultManager.
The block passed to the byAccessor portion of the coordinated write is not guaranteed to be called on any particular thread, so you shouldn't be manipulating NSWindows or presenting modal dialogs or anything from within that block (unless you've dispatched it back to the main queue).
I think pretty much all of the iCloud methods on NSFileManager will block until things complete. It's possible that what you're seeing is the method blocking and never returning because things aren't configured properly. I'd double and triple check your settings, maybe try to simplify the reproduction case. If it still isn't working, try filing a bug or contacting DTS.
Just shared this on Twitter with you, but I believe when using NSDocument you don't need to do any of the NSFileCoordinator stuff - just make the document ubiquitous and save.
Hmm,
did you try not using a ubiquity container identifier in code (sorry - ripped out of a project so I've pseudo-coded some of this):
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *iCloudDocumentsURL = [[fm URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:#"Documents"];
NSURL *iCloudFileURL = [iCloudDocumentsURL URLByAppendingPathComponent:[doc.fileURL lastPathComponent]];
ok = [fm setUbiquitous:YES itemAtURL:doc.fileURL destinationURL:iCloudRecipeURL error:&err];
NSLog(#"doc moved to iCloud, result: %d (%#)",ok,doc.fileURL.fileURL);
And then in your entitlements file:
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>[devID].com.yourcompany.appname</string>
</array>
Other than that, your code looks almost identical to mine (which works - except I'm not using NSDocument but rolling it all myself).
If this is the first place in your code that you are accessing iCloud look in Console.app for a message like this:
taskgated: killed yourAppID [pid 13532] because its use of the com.apple.developer.ubiquity-container-identifiers entitlement is not allowed
Anytime you see this message delete your apps container ~/Library/Containers/<yourAppID>
There may also be other useful messages in Console.app that will help you solve this issue.
I have found that deleting the app container is the new Clean Project when working with iCloud.
Ok, So I was finally able to solve the problem using Dunk's advice. I'm pretty sure the issue I was having is as follows:
Sometime after the WWDC video I was using as a guide was made, Apple completed the ubiquity APIs and removed the need to use an NSFileCoordinator object while saving from within an NSDocument subclass.
So the key was to remove both the creation of the NSFileCoordinator and the call to -[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]
I also moved this work onto a background thread, although I'm fairly certain that was not absolutely required to fix the issue (although it was certainly a good idea).
I shall now submit my completed code to Google's web crawlers in hopes of assisting future intrepid Xcoders.
Here's my complete solution which works:
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) {
NSBeep();
return;
}
BOOL makeUbiquitous = 1 == [sender tag];
if (makeUbiquitous) {
[self displayMoveToCloudDialog];
} else {
[self displayMoveFromCloudDialog];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doMoveToOrFromCloud:makeUbiquitous];
});
}
- (void)doMoveToOrFromCloud:(BOOL)makeUbiquitous {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSURL *destURL = nil;
NSFileManager *mgr = [[[NSFileManager alloc] init] autorelease];
if (makeUbiquitous) {
NSURL *dirURL = [[MyDocumentController instance] ubiquitousDocumentsDirURL];
if (!dirURL) return;
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// move to local Documentss folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
destURL = [[dirs firstObject] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSError *err = nil;
void (^completion)(void) = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
completion = ^{
[self hideMoveToFromCloudDialog];
};
} else {
completion = ^{
[self hideMoveToFromCloudDialog];
NSWindow *win = [[self canvasWindowController] window];
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
};
}
dispatch_async(dispatch_get_main_queue(), completion);
}

QTKit A file or directory could not be found

I'm trying to record and play movies with my app using qtkit. I record the video in one view and display it into another view. Here's how I do it
- (void)startRecording
{
NSString *applicationSupportDirectory = [[NSFileManager defaultManager] applicationSupportDirectory];
NSString *path = [applicationSupportDirectory stringByAppendingPathComponent:kVideoOutputName];
NSURL *url = [NSURL fileURLWithPath:path];
// Delete the previous file
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
mCaptureMovieFileOutput.delegate = self;
[mCaptureMovieFileOutput recordToOutputFileURL:url];
}
- (void)stopRecording
{
[mCaptureMovieFileOutput recordToOutputFileURL:nil];
}
- (void)captureOutput:(QTCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL forConnections:(NSArray *)connections dueToError:(NSError *)error
{
// [[NSWorkspace sharedWorkspace] openURL:outputFileURL];
// removes the current view
[self cleanView];
MyViewController *controller = [[SharingViewController alloc] init];
controllerpath.path = outputFileURL;
[self.view addSubview:[controller view]];
[self stopCamera];
}
Now in my view controller I assign the movie to my movie player.
- (void)awakeFromNib
{
NSError *error;
moviePlayer.movie = [QTMovie movieWithURL:path error:&error];
NSLog(#"%#", [error localizedDescription]);
}
Now, this code works the first time around, but I need to register and show multiple times.
There's already one problem here. If I want to record videos multiple times, I have to delete the first one, otherwise after the first time it wont record anything (it complains that a file already exists).
The problem is that after the first time, it also doesn't show the video at all. When I execute [QTMovie movieWithURL:path error:&error]; it complains that the file or directory does not exists, when in reality it does (I also checked with [QTMovie canInitWithUrl:]).
I'm not sure what's going on here. The apple's sample code is able to record multiple times, but for some reasons I can't without first deleting the existing file (it works the first time, though).
I'd be happy to provide further details if needed.
EDIT: If I use a different name every time for the video, everything works. So this is really an issue about recording with the same name every time.
I ended up using a different file name for every file.
I ran into the same issue, and I found that setting the movie view to nil, prior to reusing the same file name resolved the issue.
I get the same weird problem creating a QTMovie. I just loaded the file to an NSData object like this and it worked:
[self setMovieData:[NSData dataWithContentsOfURL:[self movieURL]]];
[self setMovie:[QTMovie movieWithData:[self movieData] error:&error]];