Menu

Tag Archives: objective-c

While writing a hybrid native/web application for iOS, I encountered a problem I never even bothered to consider before starting:

How do I clear the browser cache for the application?

Clearing cached HTML resources, scripts, and stylesheets is the type of browser-provided functionality that is easy to take for granted. However, when writing an application with a hosted UIWebView, you’re suddenly solely responsible for handling little issues like this.

Thankfully, programmatically clearing the browser cache for an iOS application is simple:

[[NSURLCache sharedURLCache] removeAllCachedResponses];

If your also interested in clearing out cookies, then also include this:

When and where to invoke this functionality is really up to the particular circumstances of your application.

While clearing the browser cache is the right approach for certain problems, it’s not the answer to every cache-related problem. If you’re interested in bypassing the caching of web resources all together, be sure to look into NSURLRequest‘s NSURLRequestCachePolicy rather than aggressively clearing the application’s cache.

Sadly, it is a common scenario in web development to have code that handles specific browsers, or classes of browsers, differently. The practice largely has it’s roots in the “bad-old-days” of having to handle the many various quirks and idiosyncrasies in the different CSS and layout engines of the major browser vendors. Thankfully, this problem is getting, largely, better (at least in my experience), and the need to write browser-specific code of this type is becoming less of an issue.

But handling layout differences across browsers isn’t the only reason to treat different clients uniquely. Dropbox’s matching of the sort order paradigms of either Windows or Mac depending on which OS the site is being viewed on is a practical example of when functional differentiation is desired for different clients.

Dropbox on Mac

Dropbox on Windows

These techniques are nearly always implemented by inspecting the user agent string provided in the headers of each request the server receives. Each browser provides a user agent header that describes the type, version, OS, and other relevant details about the client sending the request. For a web application, the client application is the web browser, so you don’t need to worry about specifying one, the browser provides it. You only need to worry about consuming it if necessary.

But what about hybrid native applications where mobile content is running within a web browser control (such as UIWebView in iOS) within a native application. By default, the web browser control typically sends a subset of the user agent string the full-browser version would send. But what if you want your server code to recognize when your native application is submitting requests? How can you specify details about the native application in the user agent string if you need the backend application to behave differently for these hybrid clients?

In the iOS SDK, the answer is to modify the UIWebView’suser agent string for your application. This is easily done by:

The essence of the above code is that the existing UIWebView user agent is retrieved from a UIWebView instance (it doesn’t need to be the instance that will actually display the content), the custom user agent information is appended to the original user agent, and then the new user agent is registered to the user defaults of the application under the key UserAgent. This will set the user agent sent for all network requests sent from your application’s code, including raw NSConnection requests.*

In my experience, you should always augment the existing user agent rather than completely replace it as the server will likely make some assumptions about whether you are a supported client and what flavor of display code to deliver based on the web view control’s existing agent string. This is especially true in large applications where you don’t own every aspect of the server code.

* NOTE: Things can get a little weird for requests issued from a linked library (like making an API call such as stringWithContentsOfFile). Therefore, it is dangerous to assume that EVERY server connection issued by your application will carry the custom user agent. Your milage may vary, so verify that the custom agent is being applied uniformly for all calls and adjust how the agent is registered if needed.

If you are developing iOS applications and are not using NSUndoManager, you should be. The class provides a very solid implementation for enabling undo/redo functionality within an iOS application.

However, when I first wired the class into a project, I quickly noticed that changing a value after navigating backward into the undo stack did not clear the now-downstream, previously-applied undo stack operations. Which is to say, if I undid an edit and then changed the value of the “undone” field, I could redo to the value prior to the undo. Which, much like that last sentence, is very confusing and hard to follow.

Most undo/redo implementations assume that applying a change after navigating backwards in the undo stack should invalidate the forward operations within the stack. So while I was surprised that wasn’t happening right out of the box with NSUndoManager, I assumed it was capable of the behavior (i.e. I must be doing something wrong).

And I was correct. Straight out of the box, NSUndoManager assumes that all undo operations are non-discardable, which is to say, it will preserve them even when the state of the class hints that the operation may no longer be valid. To enable the functionality of having the traversed undo operations removed from the redo stack once a modification is made to the “undone” data value, I simply had to indicate that the applied undo operations should be treated as “discardable“.

A very straightforward, and possibly overly-aggressive, way of doing this would be to subclass the NSUndoManager and override prepareWithInvocationTarget as so:

– (id) prepareWithInvocationTarget:(id)target
{
// This ensures that the redo stack is reset if the user
// edits a field after moving back in the undo stack.
[self setActionIsDiscardable:true];
return [super prepareWithInvocationTarget:target];
}

As is often the case with software development, your project likely has some characteristics that are very specific to your application, so the above code likely is not be a perfect fit to your problem. However, if you’re looking for a way to reset the redo stack of NSUndoManager, the above code should provide some guidance on how to develop a solution that fits your needs.