Solipsism Gradient

Rainer Brockerhoff’s blog

Browsing Posts published by Rainer Brockerhoff

Klicko 1.1 (201) is up, with the aforementioned full keyboard access/VoiceOver support…

An hours-long outage somewhere inside my ISP made me re-evaluate the idea about a reachability transition callback. It seems to be reliable enough when the local network is involved, but when the clogged pipe is several routers away, it can go down – and never go up again.

So what I’m doing now is, while the network is down, retry with a direct reachability test every half hour or so; of course if the callback says it’s back up, I tear that timer down again. Seems to work OK.

Several interim builds of Klicko went up in the meantime, implementing this and other small changes. The latest one is now in testing; build 1.1 (201) will implement full keyboard accessibility and VoiceOver support. At least in the preferences panel part; the background process is still mouse-oriented, and I don’t foresee disabled people needing any of the Klicko functions. Still, it’s good practice since nearly all of the Klicko panel code will be re-used in the upcoming rewrite of Quay, and there it will certainly be useful.

Re: Klicko 1.1

No comments

And, the reachability check explained below is now also present in Klicko 1.1 (189).

Re: Cocoa musings pt.4

No comments

M. Uli Kusterer emailed me to say I’d forgotten that some people still have non-broadband connections. I had supposed that NSURLConnection would simply return an immediate error if the internet link is down – it turns out that it may autoconnect if the user has a dialup connection, for instance.

The way around that is to use the network reachability APIs. Here’s one way to insert that into the example below:

#include <SystemConfiguration/SCNetworkReachability.h>
...
- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate {
   if ((self = [super init])) {
      SCNetworkConnectionFlags flags = 0;
      if (SCNetworkCheckReachabilityByName([[theURL host] UTF8String], &flags)) {
         const SCNetworkConnectionFlags mask =
            kSCNetworkFlagsReachable|
            kSCNetworkFlagsConnectionRequired|
            kSCNetworkFlagsConnectionAutomatic|
            kSCNetworkFlagsInterventionRequired;
         if ((flags&mask)==kSCNetworkFlagsReachable) {
            delegate = theDelegate;
            data = nil;
            conn = [[NSURLConnection alloc]
               initWithRequest:[NSURLRequest requestWithURL:theURL]
               cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
               timeoutInterval:60.0]
               delegate:self];
            return self;
         }
      }
      [self autorelease];
      self = nil;
   }
   return self;
} 

This changes the alloc/init pair to return nil whenever the URL host is unreachable… you’ll have to test for that of course.

Update: the downside to the above approach is that the SCNetworkCheckReachabilityByName() function will block until it gets the information – which may take several seconds, perhaps more on some systems. The solution I’m actually using is more complex, installing a reachability transition callback, which sets a global flag that is tested before activating the NSURLConnection.

Cocoa musings pt.4

No comments

One thing I had to do very recently was to download a relatively short block of data from my server. There are some nice Cocoa shortcuts for such a thing, like:

NSError* error = nil;
NSData* data = [NSData dataWithContentsOfURL:theURL options: NSUncachedRead error:&error];

which accesses that URL from the server, presents it to you as NSData and/or gives you an error object. The problem, however, is that it’s synchronous; if your server is down, it may take some time to time-out, and all that time your UI is inactive.
The initial impulse to get around this problem would be to simply spin those two lines off into a secondary thread. It’s easy to underestimate the subtleties of that, however, and there are good alternatives available. Here’s a simplified version of what I eventually elected to use. First the .h file:

@interface AsynchDownload : NSObject {
   NSURLConnection* conn;
   NSMutableData* data;
   id delegate;
}

- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate;

- (void)cancel;

@end

and here the .m file:

@implementation AsynchDownload

- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate {
   if ((self = [super init])) {
      delegate = theDelegate;
      data = nil;
      conn = [[NSURLConnection alloc]
         initWithRequest:[NSURLRequest requestWithURL:theURL]
         cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]                            
         delegate:self];
   }
   return self;
}

- (void)cancel {
   [conn cancel];
}

- (void)dealloc {
   [conn release];
   [data release];
   [super dealloc];
}

- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error {
   [delegate performSelectorOnMainThread:@selector(failure:)
      withObject:[error localizedDescription] waitUntilDone:NO];
}

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
   [data release];
   data = nil;
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)theData {
   if (!data) {
      data = [[NSMutableData alloc] init];
   }
   [data appendData:theData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
   [delegate performSelectorOnMainThread:@selector(success:)
      withObject:data waitUntilDone:NO];
}

@end

and, finally, here’s how to call it:

   AsynchDownload* download = [[AsynchDownload alloc] initWithURL:theURL delegate:self];

… and in the same class, two methods:

- (void)failure:(NSString*)failure {
   [download release];
   //.. complain and show the failure string
}

- (void)success:(NSData*)data {
   [download release];
   // do something with data
}

… and finally, you might call

   [download cancel];

somewhere inside, say, a cancel button’s action.

This takes care of nearly all common cases. The only non-obvious part is the connection:didReceiveResponse: method; according to the docs, you need to discard already-received data there, as it may be sent multiple times before you get the final data. In this method, you could also ask the NSURLResponse for the data size, to set a download progress indicator.

Finally, note the performSelectorOnMainThread: messages which pass the results back to your own code; they neatly insulate you from threading effects.

Re: Klicko 1.1

No comments

Rainer Brockerhoff wrote:

Maybe I should, after all, implement regular automatic version checking? I dislike such automatic processes myself, but I’ll look into doing it less obtrusively.

So, it’s done; Klicko 1.1 (187) has automatic version checking.

Details were trickier than I had anticipated, but I managed to do it while increasing download size by only 44K (23%). Most of that is accounted for by a new helper application. The background process checks for updates periodically. If an update is out, the process runs the helper app to display an alert; the user can then ask the app to run System Preferences to download and install the update. (If the Klicko panel is already open, the helper app isn’t called.)

Re: Klicko 1.1

No comments

And the first bug is fixed: you had to click twice in Exposé in some cases. So I pushed out 1.1 (181). This also has, again, the French localization, thanks to Ronald Leroux for rewriting it on short notice.
I think I’ll continue doing small, immediate bug fixes just by incrementing the build number, instead of doing lots of dot-dot versions like 1.1.2c15. The downside is that these small updates won’t get press releases or special mention on the version tracking sites; for now, I’ll just post a “developer note” on those when possible.
Currently the user has to check for updates by opening System Preferences, going to the “Installation” tab, and clicking on “Check for Updates”. I do get the impression that relatively few users do so regularly, or when they run into a bug. Maybe I should, after all, implement regular automatic version checking? I dislike such automatic processes myself, but I’ll look into doing it less obtrusively.

Klicko 1.1

No comments

Details on the product page. More about it later here… must run.

Photos licensed by Creative Commons license. Unless otherwise noted, content © 2002-2024 by Rainer Brockerhoff. Iravan child theme by Rainer Brockerhoff, based on Arjuna-X, a WordPress Theme by SRS Solutions. jQuery UI based on Aristo.