Airsource Bloghttps://airsource.co.uk/blog/2023-03-22T14:32:00+00:00TeamCity, Containers and SSH keys2023-03-22T14:32:00+00:002023-03-22T14:32:00+00:00Nick Clareytag:airsource.co.uk,2023-03-22:/blog/2023/03/22/teamcity-containers-sshkeys/<p><a href="https://jetbrains.com/teamcity">TeamCity</a> is our Continuous Integration (CI) environment of choice at Airsource, and we love it. Yes, there are open source options for CI, but when you spend your days developing commercial software it makes sense to break out the checkbook for tools that make everyone's job easier.</p>
<p><a href="https://jetbrains.com/teamcity">TeamCity</a> is our Continuous Integration (CI) environment of choice at Airsource, and we love it. Yes, there are open source options for CI, but when you spend your days developing commercial software it makes sense to break out the checkbook for tools that make everyone's job easier.</p>
<p>Even with the best tools for the job, you still sometimes run into interesting issues. We have recently been doing more web development for our clients, and have settled on using containers both for deployment and for CI. This means that your build agents are pulling exactly the right (consistent) environment during the build process, and further reduces the scope for build agent configurations to diverge.</p>
<p>This was the scenario I ran into the other week:</p>
<ul>
<li>A TeamCity step being used for an "npm build" command</li>
<li>The npm environment needing to pull an in-house package from our GitLab deployment</li>
<li>The build slave was unable to pull the package due to SSH key problems</li>
</ul>
<h1>The symptoms</h1>
<p>Well lots of this:</p>
<p><img alt="Node.js: code 128, An unknown git error occurred" src="https://airsource.co.uk/blog/images/2023/unknown_git_error.png"></p>
<p>and looking more deeply into the logs:</p>
<div class="highlight"><pre><span></span><code><span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="n">An</span><span class="w"> </span><span class="k">unknown</span><span class="w"> </span><span class="n">git</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">occurred</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="nl">Warning</span><span class="p">:</span><span class="w"> </span><span class="n">Permanently</span><span class="w"> </span><span class="n">added</span><span class="w"> </span><span class="s1">'git.airsource.co.uk,10.0.1.178'</span><span class="w"> </span><span class="p">(</span><span class="n">ECDSA</span><span class="p">)</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">list</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">known</span><span class="w"> </span><span class="n">hosts</span><span class="p">.</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="n">git</span><span class="nv">@git</span><span class="p">.</span><span class="n">airsource</span><span class="p">.</span><span class="n">co</span><span class="p">.</span><span class="nl">uk</span><span class="p">:</span><span class="w"> </span><span class="n">Permission</span><span class="w"> </span><span class="n">denied</span><span class="w"> </span><span class="p">(</span><span class="n">publickey</span><span class="p">).</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="nl">fatal</span><span class="p">:</span><span class="w"> </span><span class="n">Could</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="k">read</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">remote</span><span class="w"> </span><span class="n">repository</span><span class="p">.</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="n">Please</span><span class="w"> </span><span class="n">make</span><span class="w"> </span><span class="n">sure</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">have</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">correct</span><span class="w"> </span><span class="n">access</span><span class="w"> </span><span class="n">rights</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">repository</span><span class="w"> </span><span class="ow">exists</span><span class="p">.</span>
</code></pre></div>
<p>Which points to git being unable to talk to the repository. This all, actually, makes perfect sense - the Node.js container is isolated from the parent system, and of course can't access the agent's SSH keys. So we need to, somehow, supply the SSH credentials to the container.</p>
<h1>Attempt 1: ssh-agent</h1>
<p>This is the pristine solution - use the SSH agent to expose the credentials to the container. And it <em>should</em> work. But unfortunately it seems that the Node.js build process doesn't pick up the SSH agent. Phooey. Same result.</p>
<h1>Attempt 2: expose the keyfile</h1>
<p>This solution actually improved things. In the additional docker run arguments, mount the agent's ssh keys to somewhere that the container can find them:</p>
<p><img alt="TeamCity build step configuration: attempt 2" src="https://airsource.co.uk/blog/images/2023/attempt_2.png"></p>
<p>A little finessing is required - we need to ensure that the GIT_SSH_COMMAND is set to use the identity you have carefully exposed to the container. Assuming your build agent is in <code>/Users/agent</code>, and you've created the necessary ssh key for it with <code>ssh-keygen</code>, you should be able to do the following:</p>
<div class="highlight"><pre><span></span><code>-v "/Users/agent/.ssh/id_rsa:/tmp/id_rsa"
-e "GIT_SSH_COMMAND=/usr/bin/ssh -i /tmp/id_rsa"
</code></pre></div>
<p>and it will all start to work. Well, nearly...</p>
<div class="highlight"><pre><span></span><code><span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="n">An</span><span class="w"> </span><span class="k">unknown</span><span class="w"> </span><span class="n">git</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">occurred</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="n">command</span><span class="w"> </span><span class="n">git</span><span class="w"> </span><span class="o">--</span><span class="k">no</span><span class="o">-</span><span class="nf">replace</span><span class="o">-</span><span class="n">objects</span><span class="w"> </span><span class="n">ls</span><span class="o">-</span><span class="n">remote</span><span class="w"> </span><span class="n">git</span><span class="nv">@git</span><span class="p">.</span><span class="n">airsource</span><span class="p">.</span><span class="n">co</span><span class="p">.</span><span class="nl">uk</span><span class="p">:</span><span class="n">airsource</span><span class="o">/</span><span class="n">fine</span><span class="o">-</span><span class="n">video</span><span class="o">-</span><span class="n">scrubber</span><span class="p">.</span><span class="n">git</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="k">Host</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="n">verification</span><span class="w"> </span><span class="n">failed</span><span class="p">.</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="nl">fatal</span><span class="p">:</span><span class="w"> </span><span class="n">Could</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="k">read</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">remote</span><span class="w"> </span><span class="n">repository</span><span class="p">.</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="n">Please</span><span class="w"> </span><span class="n">make</span><span class="w"> </span><span class="n">sure</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">have</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">correct</span><span class="w"> </span><span class="n">access</span><span class="w"> </span><span class="n">rights</span>
<span class="n">npm</span><span class="w"> </span><span class="n">ERR</span><span class="err">!</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">repository</span><span class="w"> </span><span class="ow">exists</span><span class="p">.</span>
</code></pre></div>
<p>The npm resolution process is trying to login to our Gitlab to retrieve the npm package, but unfortunately it doesn't know the host! So it's failing to connect due to an unknown host.</p>
<h1>Security misstep: don't do this</h1>
<p>The tempting solution, which does work, is to just ignore hostkeys by supplying <code>-o StrictHostKeyChecking=no</code> to <code>GIT_SSH_COMMAND</code>. And you know what? That will actually work. But! It does open you up to so-called man-in-the-middle attacks if someone breaks into your network and inserts a malicious fake git host that hoovers up credentials. But a lot of people suggested it online, and I can see why it's tempting to just shove in the change and call it a day.</p>
<p>But we can do better...</p>
<h1>Attempt 3: expose both the keyfile AND the known_hosts file</h1>
<p>Much better, is of course, to ensure that the container knows about the same servers as your container host. And we can do that very easily by exposing the known_hosts file to the container as follows:</p>
<p><img alt="TeamCity build step configuration: attempt 3" src="https://airsource.co.uk/blog/images/2023/attempt_3.png"></p>
<div class="highlight"><pre><span></span><code>-v "/Users/agent/.ssh/id_rsa:/tmp/id_rsa"
-v "/Users/agent/.ssh/known_hosts:/tmp/known_hosts"
-e "GIT_SSH_COMMAND=/usr/bin/ssh -o UserKnownHostsFile=/tmp/known_hosts -i /tmp/id_rsa"
</code></pre></div>
<p>With this minor tweak, the Node.js container fires up and is able to pull npm packages from our Gitlab just fine, no drama.</p>Announcing - Cellar 2!2018-12-18T18:00:00+00:002018-12-18T18:00:00+00:00Ben Blaukopftag:airsource.co.uk,2018-12-18:/blog/2018/12/18/announcing-cellar2/<p><a href="http://www.airsource.co.uk/">Airsource</a> are delighted to announce the release of <a href="http://www.airsource.co.uk/cellar2">Cellar 2</a>, <a href="https://itunes.apple.com/app/id1442088735">on the Apple App Store</a>!</p>
<p>Cellar 2 takes the original wine-management app that everyone loved and remasters it.</p>
<p><a href="http://www.airsource.co.uk/">Airsource</a> are delighted to announce the release of <a href="http://www.airsource.co.uk/cellar2">Cellar 2</a>, <a href="https://itunes.apple.com/app/id1442088735">on the Apple App Store</a>!</p>
<p>Cellar 2 takes the original wine-management app that everyone loved and remasters it.</p>
<p><img alt="Cellar" src="https://airsource.co.uk/blog/images/2018/12/18/cellar2_screenshot.png"></p>
<p>Edit and view your wine in landscape or portrait, either in a beautiful Cellar mode that shows actual bottles, or in List mode if you want to see more information about your wine. Full support for large screen devices like the iPhone XS Max. We've added the Bocksbeutel bottle shape by popular demand. And we didn't want to add Bocksbeutel without the round labels that often accompany that kind of bottle - so we did that too.</p>
<p>More new features in the app - try it with our 14-Day Trial for free! If you are an existing Cellar user you can get a discount on the full version of the app.</p>
<p>Using it is "almost as fun as drinking the wine you collect" - Michael Rose, tuaw.com.</p>
<p>Cellar is designed to keep track of your wine, and remember what to buy next time you visit the store, thanks to the app's unique 'Garage' feature.</p>
<p><a href="https://itunes.apple.com/app/id1442088735"><img alt="Cellar" src="https://airsource.co.uk/blog/images/2018/12/18/App_store_icon.png"></a>.</p>
<p>Organizing your wine collection has never been so much fun!</p>Quickstart With Firebase And Google App Engine Part 22017-10-27T09:00:00+01:002017-10-27T09:00:00+01:00Abhi Singhtag:airsource.co.uk,2017-10-27:/blog/2017/10/27/quickstart-with-firebase-and-google-app-engine-part-2/<p>This article shows you how to set up the tools and SDKs to implement the a firebase authentication with google app engine for your application.</p><p>There are a lot of steps in implementing a backend for your iOS app with Google App Engine and adding Firebase authentication to it, with some tricky bits during the setting up of the tools. Hopefully, this quickstart guide will help you to smoothly implement your backend, without missing any of the tricky steps (and spending hours, or maybe days on debugging).</p>
<p>In this tutorial, we will set up a backend for a simple note-taking application that stores users' notes in their own personal notebooks. This tutorial continues from <a href="https://airsource.co.uk/blog/2017/10/16/quickstart-with-firebase-and-google-app-engine/">part 1</a>, where I explain the configuration of the components that comprise this backend. If you are unfamiliar with GAE or Firebase, I would strongly recommend reading through the <a href="https://airsource.co.uk/blog/2017/10/16/quickstart-with-firebase-and-google-app-engine/">part 1</a> first. This app is based on the official <a href="https://cloud.google.com/appengine/docs/python/authenticating-users-firebase-appengine">Google tutorial</a>. Although, the official tutorial is based on a Javascript frontend, we will ignore the frontend and use the backend for powering our own iOS frontend.</p>
<h1>Step 1 - Prerequisites</h1>
<ol>
<li>
<p>You need to have <a href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git/">Git</a> and <a href="https://www.python.org">Python 2.7</a> installed on your system. Additionally, if you want to integrate the backend in an iOS app, you'll need Xcode 7.0 (or later) and CocoaPods 1.0.0 (or later).</p>
</li>
<li>
<p>Create a Google Cloud Platform project :</p>
<ol>
<li>Go to <a href="https://console.developers.google.com">Google Developers console</a> and click 'Create Project'.</li>
<li>Make a note of the project ID, which might be different from the project name. The project ID is used in commands and in configurations.</li>
</ol>
</li>
<li>
<p>Install <a href="https://cloud.google.com/sdk/docs/">gcloud tool</a> for your platform. This tutorial was initially written to use Google Cloud SDK 138.0.0 running on a MacOS OS X El Capitan machine but has been tested to work on Google Cloud SDK 175.0.0 and MacOS Sierra.</p>
</li>
<li>
<p>Import the Google Cloud Platform project from step 2 in the <a href="https://console.firebase.google.com">firebase console</a> by clicking on 'IMPORT GOOGLE PROJECT' and selecting the above project. Make a note of the firebase project id.</p>
</li>
<li>
<p>Enable Email/Password authentication for the project in firebase console by clicking Auth > Sign-in method. Under Sign-in providers, hover the cursor over a provider and click the pencil icon to edit the sign-in method.</p>
</li>
</ol>
<h1>Step 2 - Get the backed code.</h1>
<ol>
<li>
<ol>
<li>Download and unzip the <a href="https://airsource.co.uk/blog/images/2017/webApp2App.zip">example backend project</a>. This project is based on the <a href="https://github.com/GoogleCloudPlatform/python-docs-samples.git">Google Cloud Platform python samples</a></li>
</ol>
</li>
<li>
<p>Navigate to the directory that contains the sample backend code by running</p>
<div class="highlight"><pre><span></span><code>cd webApp2App
</code></pre></div>
</li>
<li>
<p>The webApp2App directory should have the following files :
<img alt=""Directory structure of firenotes backend"" src="https://airsource.co.uk/blog/images/2017/gae_snapshots/dir_structure.png"></p>
<ol>
<li>
<p>An app.yaml file - This configures the App Engine application's settings e.g. how URL paths correspond to request handlers and static files.</p>
</li>
<li>
<p>An index.yaml file - This tunes the indexes for the properties of entities used in your application. Don't worry if you don't yet understand what this means.</p>
</li>
<li>
<p>A main.py file - This defines the route handlers for the api.</p>
</li>
<li>
<p>A notes.py file - This defines the handlers for the api corresponding to the notes endpoint.</p>
</li>
<li>
<p>An appengine_config.py - This file has a method which registers the libraries directory for the app. Please note that if you need to include your app backed in unit tests, you will need to additionally import this file in your test script file.</p>
</li>
</ol>
</li>
</ol>
<h1>Step 3 - Install the requirements for the backend.</h1>
<ol>
<li>
<p>Set up and run a virtual environment by running the following commands from the webApp2App directory</p>
<div class="highlight"><pre><span></span><code>pip install virtualenv
virtualenv venv
source venv/bin/activate
</code></pre></div>
</li>
<li>
<p>Install the third-party requirements that are not included in the App Engine SDK</p>
<div class="highlight"><pre><span></span><code>pip install -r requirements.txt -t lib
</code></pre></div>
</li>
</ol>
<h1>Step 3 - Understand and configure the backend code.</h1>
<p>An app.yaml file specifies the services that run on the server and the scripts where the corresponding request handlers are defined.</p>
<ol>
<li>
<p>Edit the app.yaml file and replace the PROJECT_ID with your firebase project id from step 1.4.</p>
</li>
<li>
<p>Note, the 'service' property in the app.yaml is currently specified as 'main'. Since, our app only has one service, this is also the default service.</p>
</li>
</ol>
<h1>Step 4 - Run the backend locally</h1>
<ol>
<li>
<p>You can now run your backend locally by running the following command from the webApp2App directory.</p>
<div class="highlight"><pre><span></span><code><span class="n">dev_appserver</span><span class="p">.</span><span class="n">py</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="n">yaml</span>
</code></pre></div>
</li>
<li>
<p>If you will be running your iOS app on a iOS device instead of a local simulator, you will need to start the development server with the following command, with <YOUR IP address> replaced by your computer's IP address.</p>
<div class="highlight"><pre><span></span><code><span class="n">dev_appserver</span><span class="p">.</span><span class="n">py</span><span class="w"> </span><span class="o">--</span><span class="n">host</span><span class="w"> </span><span class="o"><</span><span class="n">YOUR</span><span class="w"> </span><span class="n">IP</span><span class="w"> </span><span class="n">address</span><span class="o">></span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="n">yaml</span>
</code></pre></div>
</li>
<li>
<p>You can check your local Google App Engine instances by visiting <a href="http://localhost:8000/instances">http://localhost:8000/instances</a> on your web browser.</p>
</li>
<li>
<p>When a user creates notes on your server, you will be able to view them locally by visiting <a href="http://localhost:8000/datastore">http://localhost:8000/datastore</a> on your web browser. When you have some notes, this will look as below:</p>
</li>
</ol>
<p><img alt=""Directory structure of firenotes backend"" src="https://airsource.co.uk/blog/images/2017/gae_snapshots/local_datastore.png"></p>
<h1>Step 5 - Deploy the backend</h1>
<ol>
<li>You can deploy the backend (the backend will run on Google servers) by running the following from the backend directory.<div class="highlight"><pre><span></span><code>gcloud app deploy index.yaml app.yaml
</code></pre></div>
</li>
</ol>
<h1>Step 6 - Add Firebase to the iOS app.</h1>
<p>To add firebase to an example iOS app, follow the below steps:</p>
<ol>
<li>
<p>Download the <a href="https://airsource.co.uk/blog/images/2017/FrontEndTest.zip">example project</a>. If you open the sample app's FrontEndTest.xcodeproj file in Xcode, the directory structure of you app will look as below:
<img alt=""Directory structure of iOS App project"" src="https://airsource.co.uk/blog/images/2017/gae_snapshots/iOSAppDirStr.png"></p>
</li>
<li>
<p>You'll need to make a note of the iOS app's bundle ID before continuing. This can be found by clicking the project name in Xcode and reading the Bundle Identifier property from the General Tab.
<img alt=""General Tab of Xcode iOS App project"" src="https://airsource.co.uk/blog/images/2017/gae_snapshots/xcodeGeneralTab.png"></p>
</li>
<li>
<p>Open your Firebase project in the <a href="https://console.firebase.google.com">firebase console</a> and add firebase to your iOS app by clicking 'Add app', selecting 'iOS' and then following the setup steps.</p>
</li>
<li>
<p>When prompted, enter your app's bundle ID. It's important to correctly enter the bundle ID that your app is using.</p>
</li>
<li>
<p>Download the GoogleService-Info.plist file and copy this into your Xcode project root. If you are using the example project, replace the existing GoogleService-Info.plist file with the one you have just downloaded.</p>
</li>
</ol>
<h1>Step 7 - Add Firebase to the iOS app.</h1>
<ol>
<li>
<p>Create a Podfile for your project if you don't have one by running <code>pod init</code> from your project directory. The Podfile is already present in the example project and you can skip this step if using the example project.</p>
</li>
<li>
<p>Add the following pods 'Firebase/Core' and 'Firebase/Auth' to your Podfile. These are already present in the example project and you can skip this step if using the example project.</p>
</li>
<li>
<p>Install the above pods by running <code>pod install</code>. From now close the FrontEndTest.xcodeproj (or your-project.xcodeproj if you are using your own Xcode project) and you should use FrontEndTest.xcworkspace (or your-project.xcworkspace).</p>
</li>
<li>
<p>You will notice that the example project imports the Firebase module in the UIApplicationDelegate subclass AppDelegate: <code>@import Firebase</code> and configures a FIRApp shared instance <code>[FIRApp configure]</code> in the app's <code>application:didFinishLaunchingWithOptions:</code> method.</p>
<div class="highlight"><pre><span></span><code><span class="c1">// AppDelegate.m</span>
<span class="c1">// FrontEndTest</span>
<span class="cp">#import "AppDelegate.h"</span>
<span class="k">@import</span><span class="w"> </span><span class="n">UIKit</span><span class="p">;</span>
<span class="k">@import</span><span class="w"> </span><span class="n">Firebase</span><span class="p">;</span>
<span class="k">@interface</span> <span class="nc">AppDelegate</span><span class="w"> </span><span class="p">()</span>
<span class="k">@end</span>
<span class="k">@implementation</span> <span class="nc">AppDelegate</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">application:</span><span class="p">(</span><span class="bp">UIApplication</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">application</span><span class="w"> </span><span class="nf">didFinishLaunchingWithOptions:</span><span class="p">(</span><span class="bp">NSDictionary</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">launchOptions</span><span class="w"> </span><span class="p">{</span>
<span class="c1">// Override point for customization after application launch.</span>
<span class="p">[</span><span class="n">FIRApp</span><span class="w"> </span><span class="n">configure</span><span class="p">];</span>
<span class="k">return</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span>
<span class="p">...</span>
</code></pre></div>
</li>
</ol>
<h1>Step 8 - Get Firebase authentication token and add it to URL requests</h1>
<ol>
<li>
<p>The sample app FrontEndTest is a very simple app with just two ViewControllers - SignInViewController and PostNoteViewController. The SignInViewController allows the users to register/sign in using their email-id and password while the PostNoteViewController allows the user to post and get their saved notes.
When a user registers/signs in to the iOS app, they send their username and password to the firebase server. The server checks the credentials and returns a token if they are valid. The example project retrieves the token in our iOS app in the following manner.</p>
<div class="highlight"><pre><span></span><code><span class="n">FIRUser</span><span class="w"> </span><span class="o">*</span><span class="n">currentUser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">FIRAuth</span><span class="w"> </span><span class="n">auth</span><span class="p">].</span><span class="n">currentUser</span><span class="p">;</span>
<span class="p">[</span><span class="n">currentUser</span><span class="w"> </span><span class="n">getTokenForcingRefresh</span><span class="o">:</span><span class="nb">YES</span>
<span class="w"> </span><span class="nl">completion</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">_Nullable</span><span class="w"> </span><span class="n">idToken</span><span class="p">,</span>
<span class="w"> </span><span class="bp">NSError</span><span class="w"> </span><span class="o">*</span><span class="n">_Nullable</span><span class="w"> </span><span class="n">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Handle error</span>
<span class="w"> </span><span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Store the idToken in your app to be sent to your backend via HTTPS</span>
<span class="c1">// ...</span>
<span class="p">}];</span>
</code></pre></div>
</li>
<li>
<p>We can now add this token as the Bearer property in the Authorisation Header of any HTTP request we make to our Google App Engine server. For e.g. if we have a NSMutableURLRequest object called request,</p>
<div class="highlight"><pre><span></span><code><span class="p">[</span><span class="n">request</span><span class="w"> </span><span class="n">setValue</span><span class="o">:</span><span class="p">[</span><span class="bp">NSString</span><span class="w"> </span><span class="n">stringWithFormat</span><span class="o">:</span><span class="s">@"Bearer %@"</span><span class="p">,</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">id_token</span><span class="p">]</span><span class="w"> </span><span class="n">forHTTPHeaderField</span><span class="o">:</span><span class="s">@"Authorization"</span><span class="p">];</span>
</code></pre></div>
</li>
</ol>
<h1>Step 9 - Configure iOS app requests to the Google App Engine Server.</h1>
<ol>
<li>
<p>Configure your requests to use the Google App Server's base url or your localhost url (<code>http://localhost:8080</code> if running your app on the simulator or <code>http://YOUR-IP-ADDRESS:8080</code> if running your app on a device). If you are using the example project, you'll need to change the hostname property inside the <code>viewDidAppear</code> method of the PostNoteViewController.
If you create your own requests, remember to change the URL to your own base url.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidAppear:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">animated</span><span class="w"> </span><span class="p">{</span>
<span class="c1">//Option 1 - use your ip address if you have deployed your app on your local machine</span>
<span class="c1">// _hostname =@"http://YOUR-IP_ADDRESS:8080";</span>
<span class="c1">//Option 2 - use the Google App Server's base if you have deployed your app on GAE</span>
<span class="c1">// _hostname =@"my-project-id.appspot.com";</span>
<span class="p">...</span>
</code></pre></div>
</li>
<li>
<p>Create methods to create HTTP requests to your API. If you are referring to the sample app, these have already been created for you.
For. e.g. the method <code>formAfGetRequestWithToken</code> create an HTTP GET request to the <code>http://_hostname/notes</code> endpoint with the firebase token added to the Authorization header of the request.</p>
<div class="highlight"><pre><span></span><code><span class="p">-(</span><span class="kt">void</span><span class="p">)</span> <span class="nf">formAfGetRequestWithToken:</span><span class="p">(</span><span class="bp">NSString</span><span class="o">*</span><span class="p">)</span> <span class="nv">token</span>
<span class="p">{</span>
<span class="w"> </span><span class="n">AFURLSessionManager</span><span class="w"> </span><span class="o">*</span><span class="n">manager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">AFURLSessionManager</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithSessionConfiguration</span><span class="o">:</span><span class="p">[</span><span class="bp">NSURLSessionConfiguration</span><span class="w"> </span><span class="n">defaultSessionConfiguration</span><span class="p">]];</span>
<span class="w"> </span><span class="bp">NSMutableURLRequest</span><span class="w"> </span><span class="o">*</span><span class="n">req</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">AFJSONRequestSerializer</span><span class="w"> </span><span class="n">serializer</span><span class="p">]</span><span class="w"> </span><span class="n">requestWithMethod</span><span class="o">:</span><span class="s">@"GET"</span><span class="w"> </span><span class="n">URLString</span><span class="o">:</span><span class="p">[</span><span class="bp">NSString</span><span class="w"> </span><span class="n">stringWithFormat</span><span class="o">:</span><span class="s">@"%@/notes"</span><span class="p">,</span><span class="n">_hostname</span><span class="p">]</span><span class="w"> </span><span class="n">parameters</span><span class="o">:</span><span class="nb">nil</span><span class="w"> </span><span class="n">error</span><span class="o">:</span><span class="nb">nil</span><span class="p">];</span>
<span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="n">timeoutInterval</span><span class="o">=</span><span class="w"> </span><span class="p">[[[</span><span class="bp">NSUserDefaults</span><span class="w"> </span><span class="n">standardUserDefaults</span><span class="p">]</span><span class="w"> </span><span class="n">valueForKey</span><span class="o">:</span><span class="s">@"timeoutInterval"</span><span class="p">]</span><span class="w"> </span><span class="n">longValue</span><span class="p">];</span>
<span class="w"> </span><span class="p">[</span><span class="n">req</span><span class="w"> </span><span class="n">setValue</span><span class="o">:</span><span class="s">@"application/json"</span><span class="w"> </span><span class="n">forHTTPHeaderField</span><span class="o">:</span><span class="s">@"Content-Type"</span><span class="p">];</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">([</span><span class="n">req</span><span class="p">.</span><span class="n">allHTTPHeaderFields</span><span class="w"> </span><span class="n">objectForKey</span><span class="o">:</span><span class="s">@"Authorization"</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">nil</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">//NSLog(@"token is %@",token);</span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">authValue</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">NSString</span><span class="w"> </span><span class="n">stringWithFormat</span><span class="o">:</span><span class="s">@"Bearer %@"</span><span class="p">,</span><span class="w"> </span><span class="n">token</span><span class="p">];</span>
<span class="w"> </span><span class="p">[</span><span class="n">req</span><span class="w"> </span><span class="n">setValue</span><span class="o">:</span><span class="n">authValue</span><span class="w"> </span><span class="n">forHTTPHeaderField</span><span class="o">:</span><span class="s">@"Authorization"</span><span class="p">];</span>
<span class="w"> </span><span class="p">}</span>
</code></pre></div>
</li>
<li>
<p>Look through the sample app's code for examples of how to handle the HTTP response from your backend. Please note that there are a variety of libraries available that'll allow you to make requests and parse received json objects much more efficiently.</p>
</li>
<li>
<p>You can now build your project and run your iOS app on any iOS device or simulator and should have a working app with a connected backend.</p>
</li>
</ol>
<p>Congratulations! You now deployed a functioning backend for the example iOS project. You should now be able to use the iOS app to create and retrieve notes for users.</p>Quickstart With Firebase And Google App Engine2017-10-16T15:40:00+01:002017-10-16T15:40:00+01:00Abhi Singhtag:airsource.co.uk,2017-10-16:/blog/2017/10/16/quickstart-with-firebase-and-google-app-engine/<p>So, you have thoughtfully crafted an API specification for your app to talk to a backend and now just require a robust and quick way to make the API come alive.</p>
<!-- More -->
<p>Recently, I happened to be in the exact same position.</p>
<p>An iOS app required a backend that could register …</p><p>So, you have thoughtfully crafted an API specification for your app to talk to a backend and now just require a robust and quick way to make the API come alive.</p>
<!-- More -->
<p>Recently, I happened to be in the exact same position.</p>
<p>An iOS app required a backend that could register and authenticate users as well as allow users to create, store, read, update and delete some data. After thoughtfully writing down the REST API specification (using RAML as the modeling language) and pondering about the backend design decisions, I ended up with the following configuration of components as the configuration of my choice.</p>
<p><img alt=""Configuration of components"" src="https://airsource.co.uk/blog/images/2017/firebase_gae.png"></p>
<p>Before the above diagram makes sense, we need to know what Google App Engine, Firebase and Cloud Datastore are.</p>
<p>Google App Engine (GAE) is a cloud computing platform for developing and hosting apps. You can use GAE to host and run a backend for your app. The GAE standard environment executes your backend code and allows it to receive web requests from your app.</p>
<p>Firebase is a platform with tools and infrastructure to build apps e.g. Firebase Authentication “provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app”.</p>
<p>Google Cloud Datastore is a NoSQL database holding data objects. Data objects are stored as entities where an entity can have one or more properties. The Google Datastore NDB Client Library allows Python apps running on the Google App Engine (or locally if using the local development server) to connect to Cloud Datastore.</p>
<p>Now, looking at the above diagram, we can see that the above application has the following components :</p>
<ul>
<li>
<p>The iOS app provides a user interface which allows a user to register a user account using their email/password credentials.</p>
</li>
<li>
<p>This creates a new user account with the above credentials on the Firebase backend.</p>
</li>
<li>
<p>Next, when the user with the above credentials logs into the iOS app, it sends the above credentials to the Firebase backend. The firebase backend returns an authentication ID token if the credentials are authorised.</p>
</li>
<li>
<p>The iOS app sends this ID token in an authorisation header along with any REST HTTP Request (think GET, POST, PUT, PATCH, DELETE) to the GAE backend.</p>
</li>
<li>
<p>The GAE backend verifies the ID token present in the authorisation header and performs the HTTP request and returns a response to the iOS app.</p>
</li>
<li>
<p>Once the GAE backend verifies the above ID token, it can create, read, update or delete (perform CRUD operations) on the requested data object stored on the cloud datastore.</p>
</li>
</ul>
<p>Thus, using Firebase Authorisation allows you to completely delegate the authorisation functionality (think sending confirmation emails, using multiple authorisation providers for the same user e..g Facebook, Twitter ) away from your backend. Using Google App Engine allows you to completely delegate your backend availability and scalability (think maintaining servers) to Google. Of course, scalability with simplicity comes with a price as you will most likely need a paid account for your app as it starts to become more popular.</p>
<!-- [part 2](|filename|quickstart-with-firebase-and-google-app-engine-part-2.markdown) -->
<p>In part 2 of this tutorial, I will explain how to set up the tools and SDKs to implement the above configuration for your application.</p>Preparing for a User Test2017-07-21T00:00:00+01:002017-07-21T00:00:00+01:00Liz Clareytag:airsource.co.uk,2017-07-21:/blog/2017/07/21/preparing-for-a-user-test/<p>Unfortunately I've found as a tester that the very time I am packing up and organising a user test is also amongst the busiest time at work. I am testing the code at a frantic pace trying to make sure that there are as few bugs as possible. How can …</p><p>Unfortunately I've found as a tester that the very time I am packing up and organising a user test is also amongst the busiest time at work. I am testing the code at a frantic pace trying to make sure that there are as few bugs as possible. How can you stay sane at this time and make sure that everything is organised for your test. Here are some of the ways that we prepare for a user test.</p>
<ol>
<li>
<p>Confirm everything before hand - confirm who from your customer is meeting you at what time. Confirm that a room is being provided for the testing debrief, who will meet the users, if advertising/banners will be up and where you can park. Confirm what refreshments are being provided and who is providing clipboards, paper, colouring pens. If you are testing at multiple sites, ideally have one person from your customer to take on this responsibility.</p>
</li>
<li>
<p>Check your travel plans - check that hire cars and travel tickets are for the correct day, and make sure everyone has the necessary documentation (passports, licenses etc). It sounds obvious but someone is bound to forget.</p>
</li>
<li>
<p>Get there early - I shouldn't need to say it, but never plan on getting there at the last minute. Leave time for traffic and parking, to find where you're going and to organise everything before your users arrive. </p>
</li>
<li>
<p>Have a test day manager - it is hard work in a user test. You are trying to put users at ease, getting them to fill in questionnaires, installing software, signing waivers, getting them cups of tea, all while trying to make sure all documentation is kept and organised and that all your observations are written down. It is exhausting and if you can have someone there to help greet, organise getting waivers signed, organise refreshments, keep paperwork organised and keep all your bags and testing paraphernalia safe it will make your life easier. I have run all my user tests so far without this - but it's definitely on my wishlist.</p>
</li>
<li>
<p>Engineers on standby - if you are running a multi-day test, make sure that someone is available to make any urgent changes to the code, and if you do this make sure you run a quick smoke test of the app to make sure nothing has been broken.</p>
</li>
<li>
<p>Take spare devices - I find it better if users can use their own devices, however sometimes users don't have enough room on their device, it isn't charged enough, or the iOS version is too old to install the app. Having spare devices means that you have a backup plan. I also pre-install the app on my backup devices and make sure they are fully charged so we can quickly change devices.</p>
</li>
<li>
<p>Take backup batteries and cables - There is nothing worse than an iPhone running out of charge during a test. I always take several Anker batteries, small enough to put in a pocket but powerful enough to charge several iPhones. It also means that if users are using their own devices you can send them off with a battery that isn't drained. Our apps aren't normally heavy on battery life, but often during a user test we'll add extra analytics, which can be heavier on battery life. </p>
</li>
<li>
<p>Confirm attendance with your users - send a reminder out the week before and confirm (ideally by phone) the day before. User test are useless without users, and this gives you the best chance of having an audience to test your app.</p>
</li>
<li>
<p>Have a short list of backup attendees - especially important if you are testing with children or families. Children get ill, and try to have other users you can call on if your users can't attend (this is partly why you confirm attendance).</p>
</li>
<li>
<p>Check mobile coverage - when we install an iOS test app on a users mobile phone, apple make the user "trust" our company. This requires a network connection, so it needs to be checked in advance. Also if your app requires a network connection you will need to check this in advance. </p>
</li>
<li>
<p>Make plans for lunch - I'm not talking about a 3 course meal but you will need to eat. Leave time in your schedule for lunch, and check in advance if there is a sandwich shop or somewhere you can get food. If not make sure you take food and drink with you.</p>
</li>
<li>
<p>Pack your rainjacket/waterproofs/sunhat/sunglasses - if you are outside you can never depend on the weather, so plan for for the worst.</p>
</li>
<li>
<p>Sort out Waivers - If you want to take photos and use them, then you will need to get waivers signed. You may also need to get these approved by your customer (or you may need to use their waiver). Some organisations may also want a waiver signed before users participate, especially if there are minors involved.</p>
</li>
<li>
<p>Pre and Post Test Questionnaires - it's useful to know a bit about your users before they start - their age, whether they are local and have visited the site before (for site based apps), what device they normally use, model numbers and iOS/Android versions. Post test we have a questionnaire to discuss what they thought of the app, what was easy to use, what they learnt/enjoyed and what improvements they would like to see. Our questionnaire isn't exhaustive, and is often used as a starting point to get them to talk. Print these out well in advance, and staple pre and post questionnaires together so they stay linked. Make sure they can be linked to noted made while observing users during the test. </p>
</li>
<li>
<p>Organise Refreshments - If you want users to stick around afterwards for a post test debrief, then having coffee, tea, juice, biscuits or other forms of refreshments will make this more enjoyable. </p>
</li>
<li>
<p>Activities for children - When you are working with families you may want to talk to parents longer than the children. In this case having an activity for kids to do, will make everyones life easier. In our last test run we made colouring sheets that tied in with the app.</p>
</li>
</ol>Agile reality; or the agile manifesto revisited2017-07-14T00:00:00+01:002017-07-14T00:00:00+01:00Nick Clareytag:airsource.co.uk,2017-07-14:/blog/2017/07/14/agile-revisited/<p>"Do you do agile?" is a frequently-asked question from people who approach us to develop software for them. The answer is - "Yes, but."</p>
<p>"Do you do agile?" is a frequently-asked question from people who approach us to develop software for them. The answer is - "Yes, but."</p>
<p class="caption"><img alt="Scaled Agile Framework image" src="https://airsource.co.uk/blog/images/2017/safe.png">
If you squint, you can just make out a waterfall in this picture...</p>
<p>If there's one certainty in software development, it's that nothing has really changed in the 50-odd years of the discipline.</p>
<p>The industry shuffles slowly from one extreme to another. Top-down rigour emphasises planning, spreadsheets, and gantt charts. Critical path analysis favours large teams plodding towards a goal. And then there’s laissez-faire, fly-by-the-seat-of-your-pants style development which favours individual heroics and legendary hackery.</p>
<p>But as those seasoned pros among us know, these extremes are straw men. Every project that involves software development - and frankly, just about every phase of a larger project - lies somewhere on the continuum between the highly controlled waterfall and the loosely structured free-for-all.</p>
<p>To both employees and to customers I emphasise the "software engineering funnel."</p>
<p>Every project starts life loosely structured and more freewheeling. This is by necessity. As we explore the problem - without bogging ourselves down with process and detailed planning - patterns emerge more readily. Similarly, as you near hard delivery deadlines, every project needs to ramp up discipline and procedures as other parts of the business prepare for products to launch, and for marketing to move into high gear.</p>
<p>The vast majority of customers have limited budgets and strict timescales, which will necessarily mean that you'll need to make commitments to the questions, “When will it be ready?” and, “How much will it cost?”. It's rare indeed to find a customer with such patience and deep pockets that they are willing to accept the answer, "We don't know" to either of those questions, let alone both.</p>
<p>What's the primary driver behind discipline needing to ramp up as projects progress?</p>
<p>The answer lies in the complexity of software and how it is produced. At inception there is nothing, and therefore both no chance of regression and no chance of bugs. But as the code produced increases, the chances of each successive line of code causing problems in others goes up. In poorly structured code, it could go up exponentially.</p>
<p>It's also worth pointing out that different types of components afford different development approaches.</p>
<p>The funnel approach works where you have little idea of what you're producing, perhaps on a new platform or in an unfamiliar environment. But for common development patterns, a test-driven approach might be most appropriate. For very clearly, well-defined components that need to meet an external specification or standard, a waterfall approach might be best.</p>
<p>Be flexible, pragmatic, and willing to experiment with different methodologies rather than being particularly attached to one, and you'll come to realise that each approach has it's strengths and weaknesses.</p>
<p>So it's very interesting, 16 years on, to go back and re-read the "Agile Manifesto" with this in mind. There's nothing in that manifesto that suggests there's anything wrong with this more flexible approach, and it's particularly instructive to compare it to how people currently think of agile.</p>
<p>Agile was formulated primarily as a corrective to the overly-prescriptive software manufacturing methodologies that tried to treat engineers as replaceable widgets in a process. Instead Agile puts people at the centre, valuing participation by both customers and developers, while acknowledging that in software it's rare to know up front what you actually want built.</p>
<p>It's pretty scary how such a simple and unarguably worthy manifesto has been translated into armies of highly paid consultants, complex methodologies and scary process diagrams.</p>
<p>Wherever you sit on the continuum in your projects, I'd encourage you to cut through the fluff and read what Agile is really about - which is much more of a mentality than a list of specifics. Maybe even give it to your local agile evangelist and see what they make of it!</p>
<p>You can re-read the <a href="http://agilemanifesto.org/">agile manifesto here.</a></p>Shadowing vs. Interviews in User Testing2017-07-07T00:00:00+01:002017-07-07T00:00:00+01:00Liz Clareytag:airsource.co.uk,2017-07-07:/blog/2017/07/07/shadowing-vs-interviews-user-testing/<p>In which we conclude that given a choice between pre and post-test surveys, user shadowing is vastly preferable.</p><p>As any parent of school aged children knows, the words "it's fine" are a common response to the question "how was your day?" These words could cover a range of possible outcomes between having got a detention to having aced a test. However after living in Britain for 17 years it seems that British adults aren't so dissimilar, and if someone describes their day as "fine", their day could have been made up of anything from winning the lottery to the death of their cat.</p>
<p>But seriously; how do you get meaningful data from a user when they are using an app for the first time? How do you know what they actually thought it? Are they just being polite in the responses that they give? Or do they genuinely think that it was, actually, fine? When you field test an app that requires moving around an area, is it possible to work out what a user thinks by relying on pre and post-test interviews?</p>
<p>Our last user test involved testing an app aimed at 7-10 year olds and their families at two sites in Northern Ireland. Instead of gathering results using analytics and pre and post-test interviews, we decided to also shadow each user (child and parents) as they walked around the sites. Our conclusion is that shadowing users is far more preferable to surveys - and here's why.</p>
<h1>It's easier to workaround problems</h1>
<p>The code and the UI in a user test often isn't perfect, so if a user gets stuck in the app, you can observe them for a while to see where they are stuck. You can watch the steps they take to resolve the problem and see where the user might need a tooltip in the app or what should be made clearer. But even more importantly when they get stuck, you can offer a temporary solution so that they can move on. Without this there may be whole areas of the app that they haven't found or been able to get into because of something they haven't understood or an error in your code.</p>
<p>And yes; we do test our code extensively before user tests but bugs will still slip through and users don't always react in the way you expect. The whole point of a user test is to find these areas! This is especially significant if you are trying something completely new where you might not be able to anticipate all the problems.</p>
<h1>Visceral, instantaneous reactions become obvious</h1>
<p>Following users you see their instantaneous reaction. Testing a history app for kids, this gave us an opportunity to watch which facts made kids excited. Do kids want to know about ghosts and hangings? How gory is too gory? When you watch them as they read (or their parents read) it's very obvious what they enjoy. Not surprisingly many eyes lit up at the mention of cannonballs going through houses and ghosts rumoured to be lurking behind corners, but we could also see at which point their eyes glazed over because the text was too long. Contrast this with the experience of interviewing them and their parents at the end of the test - they simply can't remember all of these moments after the event.</p>
<h1>Issues and problems with interactions become much clearer</h1>
<p>If users don’t always remember all the good moments in a post-test interview, they also don’t remember all of the difficulties or questions that they’ve had. If you aren't observing them, then how do you expect to find out this information? In talking about some of the difficulties they had in a post-test interview they also may not be able to tell you all the steps they took to overcome the problem. So you will see part of the information, but not all.</p>
<h1>Group interactions are brought to the fore</h1>
<p>Observing family groups lets you observe family interaction. It helps you to understand how you can keep different aged children engaged at the same time, and observe little interactions like who gets to hold the phone and who is able to read maps.</p>
<h1>Users can fixate on solutions in interviews</h1>
<p>Running user tests you swiftly realise that what users think to tell you isn't always what you need to know or what you think is most important. For example in a post-test interview users may get stuck talking about how a map "needs to work just like Google Maps", but in observation you may realise that there are other possible solutions that serve their needs better as well as meeting the application goals.</p>
<h1>Large sites can lose users</h1>
<p>If you are testing on a big site - especially one with multiple entrances and exits - you can't always rely on users to return to complete a post-test interview. Users who fill out a pre-test questionnaire don't always come back to talk to you afterwards. When you are observing a user they can still decide to cut out early, but you will still have your observations recorded even if they can't do a post-test interview.</p>
<h1>Fewer people at greater depth gives more useful feedback and saves you time</h1>
<p>Bigger isn't always better, and this is often what is behind the desire for a large number of users in a user test. A smaller number of users carefully observed can give you much more information than a larger number of users where you only get feedback from questionnaires/interviews. Of course this does mean that it is important to have test users who are of the right demographic.</p>
<p>Running a user test with a large number of people using interviews also requires a massive amount of organisation. Don’t underestimate how much work it is to greet people, install the app, do pre-interviews and give them instructions before they go off to try the app. Then after they come back you need to do a post-test interview and make sure you match up the documentation from before. In this case, these post-test interviews are where you get your most useful information. When you have large numbers of people you won’t always have time to spend the time on these that you would like.</p>Auto Layout Constraint Priorities2017-06-23T00:00:00+01:002017-06-23T00:00:00+01:00Matthew Houldentag:airsource.co.uk,2017-06-23:/blog/2017/06/23/autolayout-constraint-priorities/<p>Interface builder and autolayout are arguably among the most useful features of Xcode. But with such a range of device sizes available, basic constraints don’t always cut it.</p>
<!-- More -->
<p>In this post, we’ll walk through a thought process of utilising auto layout constraint priorities to create an app that …</p><p>Interface builder and autolayout are arguably among the most useful features of Xcode. But with such a range of device sizes available, basic constraints don’t always cut it.</p>
<!-- More -->
<p>In this post, we’ll walk through a thought process of utilising auto layout constraint priorities to create an app that looks great whatever the screen size. Check out the <a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/PrioritiesExample.zip">example project</a> here and run on a large screen device or simulator to see the effect of priorities on auto layout constraints in action.</p>
<h1>Scenario</h1>
<p>You are to develop a splash screen for a new app consisting of the main app logo in the centre with the two smaller logos in the bottom left and bottom right corners respectively. The design team have requested a 32 point margin between:</p>
<ul>
<li>The left logo and the left of the screen</li>
<li>The right logo and the right of the screen</li>
<li>The left and right logos with the bottom of the screen</li>
</ul>
<h1>Attempt 1</h1>
<p>You set up:</p>
<ul>
<li>Centre x and centre y constraints on the centre logo</li>
<li>Leading and bottom constraints on the left logo</li>
<li>Trailing and bottom constraints on the right logo</li>
<li>Width and height constraints on each logo</li>
</ul>
<p>On a large screen your design looks great:</p>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt1-wide.png"><img alt="Attempt 1 - Wide" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt1-wide.png"></a></p>
<p>But then you decide to see how it looks on a narrower screen:</p>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt1-narrow.png"><img alt="Attempt 1 - Narrow" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt1-narrow.png"></a></p>
<p>Uh oh! The bottom left and bottom right logos overlap! Back to the drawing board…</p>
<h1>Attempt 2</h1>
<p>To ensure that the left and right logos don’t overlap, you decide to introduce spacer views between them. You create three spacer views (coloured here for visibility) and apply the corresponding constraints:</p>
<ul>
<li>The first spacer view between the left of the screen and the left logo</li>
<li>The second spacer view between the left and right logos</li>
<li>The third spacer view between the right logo and the right of the screen</li>
<li>Equal width constraints between the spacer views</li>
</ul>
<p>Now let’s see how this looks on various screen sizes:</p>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt2-wide.png"><img alt="Attempt 2 - Wide" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt2-wide.png"></a>
<a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt2-narrow.png"><img alt="Attempt 2 - Narrow" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt2-narrow.png"></a></p>
<p>Hmm… not too bad... The design team are less impressed however and it looks like we’ve come as far as we can with basic constraints so now let’s take a look at priorities.</p>
<h1>Attempt 3</h1>
<p>You now decide that you want to try to maintain the 32 point logo-to-screen margins unless the two logos become too close (less than 32 point) together in which case you want the logo-to-screen margins to shrink. To start with you delete the centre spacer view, and then apply constraints for:</p>
<ul>
<li>The left spacer view has width equal to 32</li>
<li>The left spacer view has equal width to the right spacer view</li>
<li>Horizontal spacing of greater than or equal to 32 between the logos</li>
</ul>
<p>Depending on the width of the screen and of the logos, interface builder may be happy with this or may complain with auto layout errors. To enable this layout to work on a range of screen widths, reduce the priority of the left spacer view width from required (1000) to high (750).</p>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/change-priority.png"><img alt="Change Priority" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/change-priority.png"></a></p>
<p>So now:</p>
<ul>
<li>On a wide enough screen, the two logos will be 32 points away from their respective screen edges</li>
<li>As the screen becomes narrower, the two logos will continue to be 32 points away from their respective screen edges and the distance between the logos will shrink</li>
<li>When the distance between the two logos would otherwise fall below 32 points, the left spacer view width (equal to the right spacer view width) constraint breaks as it has the lower priority</li>
<li>The logos now become closer to their respective screen edges whilst maintaining the distance between them</li>
</ul>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt3-wide.png"><img alt="Attempt 3 - Wide" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt3-wide.png"></a>
<a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt3-narrow.png"><img alt="Attempt 3 - Narrow" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt3-narrow.png"></a></p>
<p>So now the spacings are correct on a sufficiently wide screen but it still doesn’t look right on a narrower screen…</p>
<h1>Attempt 4</h1>
<p>You still would like to maintain the 32 point logo-to-screen margins and the logo-logo spacing to be at least 32 point but this time when the screen becomes too narrow you want all margins to shrink. Remove all width constraints from the spacer views, put back the centre spacer view, and add constraints for:</p>
<ul>
<li>The left spacer view has width less than or equal to 32</li>
<li>The left and right spacer views have equal widths</li>
<li>The left and centre spacer views have equal widths but with reduced priority (750)</li>
</ul>
<p>With these new constraints:</p>
<ul>
<li>On sufficiently wide screens, the left and centre spacer views equal widths constraint is broken with the left and right spacer views both having width 32 and the centre spacer view filling the remaining space</li>
<li>As the screen becomes narrower, the centre spacer view shrinks until it matches the width of the left (and therefore right) spacer view at which point the constraint is reactivated</li>
<li>After this point all three spacer views shrink at the same rate</li>
</ul>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt4-wide.png"><img alt="Attempt 4 - Wide" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt4-wide.png"></a>
<a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt4-narrow.png"><img alt="Attempt 4 - Narrow" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt4-narrow.png"></a></p>
<p>Again, the spacings are correct on a sufficiently wide screen and look a little better on a narrower screen, but maybe we could do better by adjusting the size of the logos too…</p>
<h1>Attempt 5</h1>
<p>You now want to force the logo-to-screen margins to always be 32 point and maintain a minimum logo-logo spacing of 32 point but to compensate for a narrow screen width, instead the logos are to shrink. To start with:</p>
<ul>
<li>Delete the left and right spacer views and replace with 32 fixed constraints between the logos and their respective screen edges</li>
<li>Delete the centre spacer view and replace with a horizontal spacing constraint of greater than or equal to 32 between the logos</li>
</ul>
<p>Then:</p>
<ul>
<li>Remove the height constraint of each logo (keeping the width constraint) and replace with an aspect ratio constraint</li>
<li>Reduce the priority of the width constraint of each logo (750)</li>
</ul>
<p>This may seem to be enough to do what we are after, but on a narrow screen you’ll be presented with auto layout errors. But why? Although auto layout understands that it should shrink the width of the logos, it doesn’t know how to shrink them because they have equal priorities. If you were to change the priority of one width constraint to 751 then autolayout would know to shrink the other one. But how to make them both shrink?</p>
<ul>
<li>Add a less than or equal to width constraint to the left logo</li>
<li>Remove the width constraint from the right logo</li>
<li>Add an equal widths constraint between the two logos</li>
</ul>
<p><a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt5-wide.png"><img alt="Attempt 5 - Wide" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt5-wide.png"></a>
<a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt5-narrow.png"><img alt="Attempt 5 - Narrow" src="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/attempt5-narrow.png"></a></p>
<h1>Conclusion</h1>
<p>From here, the next step could be to extend attempt 5 to restrict how much the logos are allowed to shrink and then the margins would change width to compensate but that can be left as an exercise to the reader. Priorities are a useful feature of auto layout constraints and it takes careful thought (or trial and error!) to get them right. Don’t forget to check out the <a href="https://airsource.co.uk/blog/images/autolayout-constraint-priorities/PrioritiesExample.zip">example project</a> and run on a large screen device or simulator which demonstrates these examples.</p>From Architecture to Apps Design2017-06-15T09:43:00+01:002017-06-15T09:43:00+01:00Mercedes Cepedatag:airsource.co.uk,2017-06-15:/blog/2017/06/15/from-architecture-to-apps-design/<p>I come from the world of architecture. Not the one where architects design software but the one where they design buildings - real ones with foundations, a structure, walls and a roof. And although that is now part of my past, it is still present in my daily life, because - believe …</p><p>I come from the world of architecture. Not the one where architects design software but the one where they design buildings - real ones with foundations, a structure, walls and a roof. And although that is now part of my past, it is still present in my daily life, because - believe it or not - the design of apps and buildings have so much in common.</p>
<!-- More -->
<p>Let’s start with the basics of UI and UX to see how they are analogous to architecture.</p>
<p>We can define the User Interface (UI) as a hierarchy of screens, pages, and visual elements that are used to interact with an app. They are the space and primary method that a user navigates around. In architecture this equates to a floor plan: the distribution of spaces, rooms, corridors, windows, and stairs that people navigate inside a building.</p>
<p>Further, we can compare them by individual elements.</p>
<p>“Sign In” and “Sign Out” actions are like the front door of an application, with passwords as the keys. Action buttons are comparable to the doors and corridors that move us between screens or rooms. Navigation bars connect users with different levels of the app, like stairs or lifts in a building, that allow you to change floors.</p>
<p>Even the interior design of a building, the use of space, signage and colours, have good analogies to the aesthetic elements of an app such as the iconography, typography, and colour palette.</p>
<p>The international standard on ergonomics of human system interaction, ISO 9241-210 defines user experience (UX) as "a person's perceptions and responses that result from the use or anticipated use of a product, system or service". This includes all of the users' emotions, beliefs, preferences, perceptions, physical and psychological responses, behaviors and accomplishments that occur before, during and after use.</p>
<p>In order to ensure that an application has an appropriate UX, we start with good research.</p>
<p>What is the context within which we have to work? What will the application be used for? Who is going to use it, and how? Which are the necessary features and how do we propose to implement them? What problem are we trying to solve for the user?</p>
<p>The creation of effective <a href="https://www.usability.gov/how-to-and-tools/methods/personas.html">personas</a>, based on real user data, provides us with the information that we need to help us make appropriate decisions as we design an application.</p>
<p><img alt="Persona image" src="https://airsource.co.uk/blog/images/Persona.png"></p>
<p>This qualitative and quantitative research is also used in the architectural design of spaces.</p>
<p>Is the house for a single person, or a family with three kids and a dog? Does this building need to allow large numbers of people to flow easily through it? Is security an important aspect? What working styles will be employed in this commercial building?</p>
<p>Each of these mean that you will make different design decisions, considering individual and collective needs.</p>
<p>The user of the space is the key that both architect and app designer have to have in mind during each step of the design process.</p>
<p>Let’s go a bit further and consider dimensions. Each platform (and a lot of designers) have their own rules and patterns concerning such things as a button’s minimum tappable area, ideal font size, or element spacing, in order to help the user interact with the app more effectively. In the same way an architect employs established rules and patterns concerning, for example, the right size of a parking place. Get this wrong and the result will be a scratched and beat up car, and an unhappy user.</p>
<p>The solution obviously isn’t to make everything bigger. A toilet the size of a living room doesn’t work, and neither does a secondary button twice the size of a screen’s primary action button. In architecture, by law, plots often have a maximum space they can build in, or there are planning restrictions that govern how the space has to be used, depending on the type of land. The same principle here can apply to certain types of app; for example to meet Apple’s “Made for Kids” restriction, apps have to ensure that they do not allow browser access without some form of parental control.</p>
<p>All that being said, designing an app does have certain advantages over designing a building.</p>
<p>For example, designing an icon. In essence, all you have to do is check that all of the dimensions are whole numbers while you are drawing. Most of the programs we use now as part of the design process are aware of this necessity and provide tools that help you to do this work. Amazing. You export the icon in the sizes the engineer needs and the work is done.</p>
<p>It’s not quite so simple in architecture! The problems are not usually found at design time (except when you have a particularly painful client - usually your family), they appear when you send the plans to the builder.</p>
<p>You arrive on site to see what is being built and you find that the builder, in order to reduce their costs, has made changes to the materials so the walls are thinner than you have designed. Or the opposite - the walls are thicker because he has changed the position of the service ducting and they take more space than you were expecting. Or perhaps the brand of windows that the architect has specified are too difficult to source and the builder has given up and swapped them for windows with different dimensions… sigh.</p>
<p><img alt="Phone and house comparation" src="https://airsource.co.uk/blog/images/architecture-to-apps.png"></p>
<p>These are only two examples - both from on my own experience! - that show how the final dimensions of the space can be affected. The architect’s job is to always be alert that changes being made don’t adversely affect the user experience. I think I have had more confrontations with builders and labourers than with software engineers - maybe this is significant!</p>
<p>What about usability? In architecture it makes no sense for a user to have to go up five flights of stairs when they need to go to the toilet, and in app design it makes no sense for a user to have to travel through too many screens to get to where they want to go - especially if it’s a function that has to be used frequently!</p>
<p>Users need to understand the structure of the app instinctively, just like they need to understand the structure of a building. They should know how to enter and exit (unless we’re talking about a prison…), and how to move between spaces.</p>
<p>In the same way this ensures you don’t feel lost in a building, we need to ensure that the same feeling of safety and natural movement is applied in the design of an app. If they find it hard to move around and get to where they want to go, they won’t use it.</p>
<p>Of course, let’s not forget all of the regulations around aspects such as fire safety, electrical safety, security and so on. There are many thousands of them in architecture - even forgetting country-specific regulations - but in apps you only have to worry about the rules as they apply to the operating system you develop for, usually iOS, Android and Windows. And they have far less regulations!</p>
<p>It’s also worth considering frequency of use in architecture and app design.</p>
<p>Building a house - something that you use continually, for many years - could be similar to creating a social media or journal app. A school could equate to fitness or diet apps, something that you use for a defined period of time, and perhaps only at certain times of the week. A hotel translates as a city guide app - something that you use once and delete when you’re finished. A utility or administration app is like a hospital, where functionality can and should triumph over aesthetics. A fantasy game-app could be a type of organic or whimsical architecture with natural forms where the limitations aren’t obvious from just looking at the outside.</p>
<p>Regarding style, surely both designers and architects have much to learn from minimalist architecture, such as <a href="https://en.wikipedia.org/wiki/Ludwig_Mies_van_der_Rohe">Mies Van der Rohe</a> and his “less is more” approach, where it predominates the essence, purity and unity of the elements. Wouldn’t it be great to find more apps like this? No repetition of actions, no overloaded screens of information, a flow that’s easily understood by users… these design choices are now in your (and my) hands.</p>
<p class="caption"><img alt="Mies Van Der Rohe" src="https://airsource.co.uk/blog/images/barcelona-pavillion.png">
The Barcelona Pavillion by Ludwig Mies van der Rohe. Original photo by <a href="https://commons.wikimedia.org/wiki/File:The_Barcelona_Pavilion,_Barcelona,_2010.jpg">Ashley Pomeroy</a>.</p>An Intro to CALayer Animations2017-01-31T17:12:00+00:002017-01-31T17:12:00+00:00Matthew Houldentag:airsource.co.uk,2017-01-31:/blog/2017/01/31/intro-calayer-animations/<p>The correct use of animations can mean the difference between your app being just another one of many on the store, or standing out from the crowd.</p>
<!-- More -->
<p>In this post, we will look at the use of CALayers to quickly create fun animations to give your app that extra little …</p><p>The correct use of animations can mean the difference between your app being just another one of many on the store, or standing out from the crowd.</p>
<!-- More -->
<p>In this post, we will look at the use of CALayers to quickly create fun animations to give your app that extra little bit of polish. We’ll work through the process of creating a simple animation, looking at some of the issues you could encounter and what to do to avoid them.</p>
<h1>Scenario</h1>
<p>You are to develop an app in which you will show progress to the user. The design team feel that simply displaying a number is too boring, a dynamically created static graphic is too plain, and that a UIProgressView isn’t quite what they are looking for. A custom animating view is required to best suit the feel of the app and you know that CALayers are the tool for the job.</p>
<h1>Part 1</h1>
<p>In order to easily utilise your new CALayer (such as in interface builder), it is easiest to wrap the layer within a UIView. Start by creating two new classes: LinearProgressView which which subclasses UIView and LinearProgressLayer which subclasses CALayer. We would like to display progress from 0 to 100 so in the header file of each class, declare a property of type NSUInteger called progress:</p>
<p><em>LinearProgressView.h</em></p>
<div class="highlight"><pre><span></span><code><span class="k">@interface</span> <span class="nc">LinearProgressView</span> : <span class="bp">UIView</span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">NSUInteger</span><span class="w"> </span><span class="n">progress</span><span class="p">;</span>
<span class="k">@end</span>
</code></pre></div>
<p><em>LinearProgressLayer.h</em></p>
<div class="highlight"><pre><span></span><code><span class="k">@interface</span> <span class="nc">LinearProgressLayer</span> : <span class="bp">CALayer</span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">NSUInteger</span><span class="w"> </span><span class="n">progress</span><span class="p">;</span>
<span class="k">@end</span>
</code></pre></div>
<p>In LinearProgressLayer.m, all that is required is to declare that its layer is a LinearProgressLayer, and to pass the on the progress to its layer (with some validation):</p>
<p><em>LinearProgressView.m</em></p>
<div class="highlight"><pre><span></span><code><span class="k">@implementation</span> <span class="nc">LinearProgressView</span>
<span class="p">+</span> <span class="p">(</span><span class="kt">Class</span><span class="p">)</span><span class="nf">layerClass</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="n">LinearProgressLayer</span><span class="w"> </span><span class="k">class</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setProgress:</span><span class="p">(</span><span class="n">NSUInteger</span><span class="p">)</span><span class="nv">progress</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">progress</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="mi">100</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">progress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">100</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">((</span><span class="n">LinearProgressLayer</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="p">).</span><span class="n">progress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">progress</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="n">NSUInteger</span><span class="p">)</span><span class="nf">progress</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">((</span><span class="n">LinearProgressLayer</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="p">).</span><span class="n">progress</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@end</span>
</code></pre></div>
<p>Now for the fun stuff! In LinearProgressLayer.m, there are three main methods which need to be overridden for the animation to happen:</p>
<ul>
<li>needsDisplayForKey: so that the layer knows that there should be an animation</li>
<li>actionForKey: so that the layer knows which animation to use</li>
<li>drawInContext: so that the layer knows what it should draw throughout the animation</li>
</ul>
<p><em>LinearProgressLayer.m</em></p>
<div class="highlight"><pre><span></span><code><span class="k">@implementation</span> <span class="nc">LinearProgressLayer</span>
<span class="p">+</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">needsDisplayForKey:</span><span class="p">(</span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">key</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">([</span><span class="n">key</span><span class="w"> </span><span class="n">isEqualToString</span><span class="o">:</span><span class="s">@"progress"</span><span class="p">])</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">needsDisplayForKey</span><span class="o">:</span><span class="n">key</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="o"><</span><span class="bp">CAAction</span><span class="o">></span><span class="p">)</span><span class="nf">actionForKey:</span><span class="p">(</span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">event</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">([</span><span class="n">event</span><span class="w"> </span><span class="n">isEqualToString</span><span class="o">:</span><span class="s">@"progress"</span><span class="p">])</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="bp">CABasicAnimation</span><span class="w"> </span><span class="o">*</span><span class="n">animation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">CABasicAnimation</span><span class="w"> </span><span class="n">animationWithKeyPath</span><span class="o">:</span><span class="n">event</span><span class="p">];</span>
<span class="w"> </span><span class="n">animation</span><span class="p">.</span><span class="n">fromValue</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="nb">self</span><span class="w"> </span><span class="n">presentationLayer</span><span class="p">]</span><span class="w"> </span><span class="n">valueForKey</span><span class="o">:</span><span class="n">event</span><span class="p">];</span>
<span class="w"> </span><span class="n">animation</span><span class="p">.</span><span class="n">timingFunction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">CAMediaTimingFunction</span><span class="w"> </span><span class="n">functionWithName</span><span class="o">:</span><span class="n">kCAMediaTimingFunctionEaseInEaseOut</span><span class="p">];</span>
<span class="w"> </span><span class="n">animation</span><span class="p">.</span><span class="n">duration</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.f</span><span class="p">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">animation</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">actionForKey</span><span class="o">:</span><span class="n">event</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawInContext:</span><span class="p">(</span><span class="n">CGContextRef</span><span class="p">)</span><span class="nv">context</span>
<span class="p">{</span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">drawInContext</span><span class="o">:</span><span class="n">context</span><span class="p">];</span>
<span class="w"> </span><span class="c1">// Draw the track bar</span>
<span class="w"> </span><span class="n">CGPathRef</span><span class="w"> </span><span class="n">path1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">pathForProgress</span><span class="o">:</span><span class="mi">100</span><span class="p">];</span>
<span class="w"> </span><span class="n">CGContextAddPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">path1</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathRelease</span><span class="p">(</span><span class="n">path1</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">lightGrayColor</span><span class="p">].</span><span class="n">CGColor</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextDrawPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">kCGPathFill</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Draw the progress bar</span>
<span class="w"> </span><span class="n">CGPathRef</span><span class="w"> </span><span class="n">path2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">pathForProgress</span><span class="o">:</span><span class="nb">self</span><span class="p">.</span><span class="n">progress</span><span class="p">];</span>
<span class="w"> </span><span class="n">CGContextAddPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">path2</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathRelease</span><span class="p">(</span><span class="n">path2</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">blueColor</span><span class="p">].</span><span class="n">CGColor</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextDrawPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">kCGPathFill</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="n">CGMutablePathRef</span><span class="p">)</span><span class="nf">pathForProgress:</span><span class="p">(</span><span class="n">NSUInteger</span><span class="p">)</span><span class="nv">progress</span>
<span class="p">{</span>
<span class="w"> </span><span class="n">CGFloat</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">;</span>
<span class="w"> </span><span class="n">CGFloat</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2.f</span><span class="p">;</span>
<span class="w"> </span><span class="n">CGFloat</span><span class="w"> </span><span class="n">xMin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">radius</span><span class="p">;</span>
<span class="w"> </span><span class="n">CGFloat</span><span class="w"> </span><span class="n">xMax</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">radius</span><span class="p">;</span>
<span class="w"> </span><span class="n">CGFloat</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">xMin</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">progress</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">100.f</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">(</span><span class="n">xMax</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">xMin</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGMutablePathRef</span><span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGPathCreateMutable</span><span class="p">();</span>
<span class="w"> </span><span class="n">CGPathMoveToPoint</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="n">xMin</span><span class="p">,</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mf">2.f</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathAddArc</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="n">xMin</span><span class="p">,</span><span class="w"> </span><span class="n">radius</span><span class="p">,</span><span class="w"> </span><span class="n">radius</span><span class="p">,</span><span class="w"> </span><span class="n">M_PI</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2.f</span><span class="p">,</span><span class="w"> </span><span class="mf">3.f</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">M_PI</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2.f</span><span class="p">,</span><span class="w"> </span><span class="nb">NO</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathAddLineToPoint</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="mf">0.f</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathAddArc</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">radius</span><span class="p">,</span><span class="w"> </span><span class="n">radius</span><span class="p">,</span><span class="w"> </span><span class="mf">3.f</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">M_PI</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2.f</span><span class="p">,</span><span class="w"> </span><span class="n">M_PI</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2.f</span><span class="p">,</span><span class="w"> </span><span class="nb">NO</span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">path</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@end</span>
</code></pre></div>
<p>For now we have hard-coded some values for the animation timing function and duration as well as the colours used. Now add a LinearProgressView to the storyboard, hit run and see...nothing…?</p>
<p>Let’s get debugging! If you add a breakpoint within the drawInContext: method then you will see that it is never called. This can be fixed by adding the following in LinearProgressLayer.m to force the layer to draw:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">init</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">init</span><span class="p">];</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="p">];</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>Run the app again and you will now see that drawInContext: is called and the view is displayed. It is also possible to see your hard work in the storyboard without having to run the app at all by marking LinearProgressView as IB_DESIGNABLE:</p>
<p><em>LinearProgressView.h</em></p>
<div class="highlight"><pre><span></span><code><span class="n">IB_DESIGNABLE</span>
<span class="k">@interface</span> <span class="nc">LinearProgressView</span> : <span class="bp">UIView</span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">NSUInteger</span><span class="w"> </span><span class="n">progress</span><span class="p">;</span>
<span class="k">@end</span>
</code></pre></div>
<p>Link up your LinearProgressView in the storyboard with an IBOutlet and set the progress somewhere in your code. Run the app and see...the progress doesn’t change…?</p>
<p>Add a breakpoint in actionForKey: and you’ll see that the CABasicAnimation
object driving the animation is not created. This is resolved by marking the progress property as @dynamic in LinearProgressLayer.m:</p>
<div class="highlight"><pre><span></span><code><span class="k">@implementation</span> <span class="nc">LinearProgressLayer1</span>
<span class="k">@dynamic</span><span class="w"> </span><span class="n">progress</span><span class="p">;</span>
<span class="p">...</span>
<span class="k">@end</span>
</code></pre></div>
<p>Run the app again and it works! Beaming with pride, you rush over to show the design team your hard work but they immediately spot two glaring issues:</p>
<ul>
<li>The animation is jerky</li>
<li>The outlines are fuzzy</li>
</ul>
<p><a href="https://airsource.co.uk/blog/images/calayer-animations/part1.gif"><img alt="Part 1" src="https://airsource.co.uk/blog/images/calayer-animations/part1.gif"></a></p>
<p>Looks like more work is required…</p>
<h1>Part 2</h1>
<p>Your CABasicAnimation object handles determining how the progress changes over time but why doesn’t it do so smoothly? The answer lies in the type of our progress property: by defining progress as an NSUInteger, the progress can only take discrete values rather than vary over a smooth continuum. Go through the LinearProgressView and LinearProgressLayer classes and replace NSUInteger with CGFloat. In addition, it might be a good idea for the LinearProgressView to now also validate the the progress is positive.</p>
<p>After a bit of research, you find that setting the layer’s contentsScale property to the screen scale should solve your problem. You try updating the LinearProgressLayer init method:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">init</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">init</span><span class="p">];</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">contentsScale</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIScreen</span><span class="w"> </span><span class="n">mainScreen</span><span class="p">].</span><span class="n">scale</span><span class="p">;</span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="p">];</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>But find that it hasn’t worked! You attempt to debug the issue by logging the value of contentsScale within the drawInContext: and find that it seems to have been reset to the default value of 1.</p>
<p>We'll now take a short detour in debugging this issue properly but feel free to skip and continue with Part 2.</p>
<h1>A Detour In Debugging</h1>
<p>As suggested by this <a href="http://stackoverflow.com/a/39068721">stack overflow answer</a>, it appears that by overriding the view’s layerClass method, the layer’s contentsScale may be reset. Rather than simply take their word for it, we can investigate further and so to start with, we add some breakpoints to try to establish the lifecycle of the view and layer:</p>
<p><em>LinearProgressView.m</em></p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithCoder:</span><span class="p">(</span><span class="bp">NSCoder</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">aDecoder</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">initWithCoder</span><span class="o">:</span><span class="n">aDecoder</span><span class="p">];</span><span class="w"> </span><span class="c1">// 1st</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span><span class="w"> </span><span class="c1">// 5th</span>
<span class="p">}</span>
<span class="p">+</span> <span class="p">(</span><span class="kt">Class</span><span class="p">)</span><span class="nf">layerClass</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="n">LinearProgressLayer2</span><span class="w"> </span><span class="k">class</span><span class="p">];</span><span class="w"> </span><span class="c1">// 2nd</span>
<span class="p">}</span>
</code></pre></div>
<p><em>LinearProgressLayer.m</em></p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">init</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">init</span><span class="p">];</span><span class="w"> </span><span class="c1">// 3rd</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">contentsScale</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIScreen</span><span class="w"> </span><span class="n">mainScreen</span><span class="p">].</span><span class="n">scale</span><span class="p">;</span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="p">];</span><span class="w"> </span><span class="c1">// 4th</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>Which appear to occur in a logical order. Next we print the layer to the console at breakpoints 4 and 5 respectively and see:</p>
<div class="highlight"><pre><span></span><code><span class="o"><</span><span class="n">LinearProgressLayer</span><span class="o">:</span><span class="mh">0x60000002c920</span><span class="p">;</span><span class="w"> </span><span class="n">position</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGPoint</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"> </span><span class="n">bounds</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGRect</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"> </span><span class="n">allowsGroupOpacity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span><span class="w"> </span><span class="n">contentsScale</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="o">></span>
<span class="o"><</span><span class="n">LinearProgressLayer</span><span class="o">:</span><span class="mh">0x60000002c920</span><span class="p">;</span><span class="w"> </span><span class="n">position</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGPoint</span><span class="w"> </span><span class="p">(</span><span class="mf">187.5</span><span class="w"> </span><span class="mi">132</span><span class="p">);</span><span class="w"> </span><span class="n">bounds</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGRect</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="mi">343</span><span class="w"> </span><span class="mi">32</span><span class="p">);</span><span class="w"> </span><span class="n">delegate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o"><</span><span class="n">LinearProgressView</span><span class="o">:</span><span class="w"> </span><span class="mh">0x7f845c70d8e0</span><span class="p">;</span><span class="w"> </span><span class="n">frame</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="mi">16</span><span class="w"> </span><span class="mi">116</span><span class="p">;</span><span class="w"> </span><span class="mi">343</span><span class="w"> </span><span class="mi">32</span><span class="p">);</span><span class="w"> </span><span class="n">autoresize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">RM</span><span class="o">+</span><span class="n">BM</span><span class="p">;</span><span class="w"> </span><span class="n">layer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o"><</span><span class="n">LinearProgressLayer</span><span class="o">:</span><span class="w"> </span><span class="mh">0x60000002c920</span><span class="o">>></span><span class="p">;</span><span class="w"> </span><span class="n">allowsGroupOpacity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span><span class="w"> </span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o"><</span><span class="n">CGColor</span><span class="w"> </span><span class="mh">0x608000286270</span><span class="o">></span><span class="w"> </span><span class="p">[</span><span class="o"><</span><span class="n">CGColorSpace</span><span class="w"> </span><span class="mh">0x60000002e240</span><span class="o">></span><span class="w"> </span><span class="p">(</span><span class="n">kCGColorSpaceICCBased</span><span class="p">;</span><span class="w"> </span><span class="n">kCGColorSpaceModelMonochrome</span><span class="p">;</span><span class="w"> </span><span class="n">Generic</span><span class="w"> </span><span class="n">Gray</span><span class="w"> </span><span class="n">Gamma</span><span class="w"> </span><span class="mf">2.2</span><span class="w"> </span><span class="n">Profile</span><span class="p">;</span><span class="w"> </span><span class="n">extended</span><span class="w"> </span><span class="n">range</span><span class="p">)]</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">);</span><span class="w"> </span><span class="n">contentsScale</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="o">></span>
</code></pre></div>
<p>Clearly a lot has happened to the LinearProgressLayer between the 4th and 5th breakpoint!</p>
<p>Seeing that the LinearProgressView is the delegate of the LinearProgressLayer, we explore CALayerDelegate where the actionForLayer:forKey: method appears promising. Implementing this optional delegate method and logging the event key (returning nil as specified in the documentation if the delegate doesn't specify a behaviour for the event), we see a number of event keys including 'contentsScale'. Furthermore, the documentation leads us to look into the CALayer class method defaultActionForKey: and subsequently back to the CALayer instance method actionForKey: as used earlier.</p>
<p>According to the documentation, the default (super) implementation of actionForKey: calls a number of methods in order including the CALayerDelegate method actionForLayer:forKey: and CALayer class method defaultActionForKey: searching for a non-nil object implementing the CAAction protocol. Logging the default action, event key and the layer's contentsScale in this method, we see that the action object is always nil and, most importantly, the contentsScale is [UIScreen mainScreen].scale until after actionForLayer:forKey: has been called with event key equal to 'contentsScale', after which time it is reset to the default value of 1.</p>
<p>Having now determined why and how the contentsScale has been reset, the 'proper' way to resolve the issue would be to override one of actionForLayer:forKey:, defaultActionForKey: or actionForKey: and return an object implementing the CAAction protocol which would set the layer contentsScale in its protocol method. However, for this action object to be utilised, it appears that the layer's contentsScale must be set at least once (as we are currently doing in the layer init method). However, the much easier approach is simply to set the layer's contentsScale after all this initial default behaviour is finished...</p>
<h1>Part 2 Continued</h1>
<p>You try a new approach, first reverting the LinearProgressLayer init method change and instead setting the layer’s contentsScale from LinearProgressView:</p>
<p><em>LinearProgressView.m</em></p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithCoder:</span><span class="p">(</span><span class="bp">NSCoder</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">aDecoder</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">initWithCoder</span><span class="o">:</span><span class="n">aDecoder</span><span class="p">];</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">contentsScale</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIScreen</span><span class="w"> </span><span class="n">mainScreen</span><span class="p">].</span><span class="n">scale</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>It worked! The design team are impressed and so is your boss who asks you to make this into a common component which can be easily customised for use in future projects. It looks like your work isn’t over just yet…</p>
<p><a href="https://airsource.co.uk/blog/images/calayer-animations/part2.gif"><img alt="Part 2" src="https://airsource.co.uk/blog/images/calayer-animations/part2.gif"></a></p>
<h1>Part 3</h1>
<p>You decide that your first priority to make your new UI component reusable is to make the colours easily customisable. Taking inspiration from UIProgressView, you decide to adopt their naming convention with properties called progressTintColor and trackTintColor. In addition, you mark these properties in LinearProgressView.h as IBInspectable so that they can be set directly from the storyboard:</p>
<div class="highlight"><pre><span></span><code><span class="n">IB_DESIGNABLE</span>
<span class="k">@interface</span> <span class="nc">LinearProgressView</span> : <span class="bp">UIView</span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">CGFloat</span><span class="w"> </span><span class="n">progress</span><span class="p">;</span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">IBInspectable</span><span class="w"> </span><span class="bp">UIColor</span><span class="w"> </span><span class="o">*</span><span class="n">progressTintColor</span><span class="p">;</span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">IBInspectable</span><span class="w"> </span><span class="bp">UIColor</span><span class="w"> </span><span class="o">*</span><span class="n">trackTintColor</span><span class="p">;</span>
<span class="k">@end</span>
</code></pre></div>
<p>Duplicate these properties in LinearProgressLayer and, as we did for progress, have LinearProgressView forward these values onto its layer.</p>
<p>Now replace the hard-coded colours in the LinearProgressLayer drawInContext: method with these newly declared properties. We also need to declare some default values so add these to the LinearProgressLayer init method:</p>
<div class="highlight"><pre><span></span><code><span class="k">@implementation</span> <span class="nc">LinearProgressLayer</span>
<span class="p">...</span>
<span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="n">init</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">init</span><span class="p">];</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">trackTintColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">lightGrayColor</span><span class="p">];</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">progressTintColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">blueColor</span><span class="p">];</span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="p">];</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">drawInContext</span><span class="o">:</span><span class="p">(</span><span class="n">CGContextRef</span><span class="p">)</span><span class="n">context</span>
<span class="p">{</span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">drawInContext</span><span class="o">:</span><span class="n">context</span><span class="p">];</span>
<span class="w"> </span><span class="c1">// Draw the track bar</span>
<span class="w"> </span><span class="n">CGPathRef</span><span class="w"> </span><span class="n">path1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">pathForProgress</span><span class="o">:</span><span class="mf">100.f</span><span class="p">];</span>
<span class="w"> </span><span class="n">CGContextAddPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">path1</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathRelease</span><span class="p">(</span><span class="n">path1</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">trackTintColor</span><span class="p">.</span><span class="n">CGColor</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextDrawPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">kCGPathFill</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Draw the progress bar</span>
<span class="w"> </span><span class="n">CGPathRef</span><span class="w"> </span><span class="n">path2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">pathForProgress</span><span class="o">:</span><span class="nb">self</span><span class="p">.</span><span class="n">progress</span><span class="p">];</span>
<span class="w"> </span><span class="n">CGContextAddPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">path2</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGPathRelease</span><span class="p">(</span><span class="n">path2</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">progressTintColor</span><span class="p">.</span><span class="n">CGColor</span><span class="p">);</span>
<span class="w"> </span><span class="n">CGContextDrawPath</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">kCGPathFill</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="k">@end</span>
</code></pre></div>
<p>Set some fancy new colours from the storyboard, run the project and see that the new colours have taken effect:</p>
<p><a href="https://airsource.co.uk/blog/images/calayer-animations/edit-colours.png"><img alt="Edit colours" src="https://airsource.co.uk/blog/images/calayer-animations/edit-colours.png"></a></p>
<p>Then perform the animation and see...the colours turn to black…? Over the course of animation, it appears that the reference to the colours is lost as trackTintColor and progressTintColor are nil during drawInContext:. Checking the documentation of CALayer, you notice that there is another initializer which you have not overridden and you realise that you need to add the following to LinearProgressLayer.m:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithLayer:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">layer</span>
<span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">initWithLayer</span><span class="o">:</span><span class="n">layer</span><span class="p">];</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">([</span><span class="n">layer</span><span class="w"> </span><span class="n">isKindOfClass</span><span class="o">:</span><span class="p">[</span><span class="n">LinearProgressLayer3</span><span class="w"> </span><span class="k">class</span><span class="p">]])</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">LinearProgressLayer3</span><span class="w"> </span><span class="o">*</span><span class="n">other</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">LinearProgressLayer3</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">layer</span><span class="p">;</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">trackTintColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">trackTintColor</span><span class="p">;</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">progressTintColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">other</span><span class="p">.</span><span class="n">progressTintColor</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>Run the project again and it looks pretty awesome even if you do say so yourself!</p>
<p><a href="https://airsource.co.uk/blog/images/calayer-animations/part3.gif"><img alt="Part 3" src="https://airsource.co.uk/blog/images/calayer-animations/part3.gif"></a></p>
<p>From here, there are further customisations, modifications and improvements which could be implemented including:</p>
<ul>
<li>Customisable CAMediaTimingFunction</li>
<li>Customisable animation duration</li>
<li>Animation duration dependant on the difference between the ‘from’ and ‘to’ values</li>
<li>Progress to run from 0 to 1 for a further parallel to UIProgressView</li>
<li>DOCUMENTATION!</li>
</ul>
<p>But these are left as an exercise to the reader.</p>
<h1>Conclusion</h1>
<p>Animations utilising CALayers are a great addition to almost any app but as seen here, there are many places you can get stuck and start tearing your hair out trying to figure out why it refuses to behave. See the <a href="https://airsource.co.uk/blog/images/calayer-animations/CALayerAnimationsExample.zip">example project</a> here for the completed code at the end of each round and get started on your next great animation.</p>Debugging and how to do it2016-04-18T13:00:00+01:002016-04-18T13:00:00+01:00Ben Blaukopftag:airsource.co.uk,2016-04-18:/blog/2016/04/18/debugging-ble-nr52/<p>Some rules about debugging, and a case study on how to waste time when you get it wrong.</p><p>One question that Junior Developers frequently ask me is "How do you debug?".</p>
<h1>Debugging Rules</h1>
<p>I have a reasonably good track record on debugging problems, quite often on systems where I have zero prior knowledge. The general rules I follow for debugging other people's programs are:</p>
<ol>
<li>
<p>Ask what is <em>supposed</em> to be happening, and what is <em>actually</em> happening.</p>
</li>
<li>
<p>Require proof that every step is actually happening.</p>
<p>In the course of this, almost all of the time we'll find one of the following results:</p>
<ul>
<li>They weren't running the code they thought they were (usually failure to do a clean build)</li>
<li>Twenty seconds into the explanation, they will realise the problem, quite possibly without me having a clue how anything hangs together (<a href="http://hwrnmnbsol.livejournal.com/148664.html">Rubber Duck Debugging</a>).</li>
<li>Their supposition about what should happen is at odds with what the code actually says, and therefore with what it does. I call this "The compiler is broken! No it isn't!" This is really a variant on Rubber Duck Debugging, except that it needs me to point out, for example, that they have a symbol mismatch, or a stray semi-colon.</li>
<li>There is a compiler warning announcing precisely what the problem is, which is being ignored.</li>
<li>There is a compiler warning that has been turned off and some point in the project's history, which would have reported this issue.</li>
<li>This is a known problem on iOS X.xx that we have run into on other projects. As a company, we're getting better at both disseminating information about this kind of thing via sprint kick-offs, and also by teaching our engineers to spot when something smells.</li>
</ul>
<p>Once we get past that stage, I then follow this approach:</p>
</li>
<li>
<p>Is there any example of this working, anywhere? If so, what's different about my code?</p>
<p>and then:</p>
</li>
<li>
<p>If it all makes no sense at all, step away from the problem for a bit. Make a cup of tea, or even better, go do some exercise.</p>
</li>
</ol>
<h1>Case Study</h1>
<p>I recently had a thorny issue on some BLE Firmware where I failed to follow my own rules, and demonstrated why they work! We were working on <a href="https://www.nordicsemi.com">Nordic's</a> new <a href="https://www.nordicsemi.com/eng/Products/Bluetooth-Smart-Bluetooth-low-energy/nRF52832">nRF52832 chipset</a>, using both Nordic <a href="https://www.nordicsemi.com/eng/Products/Bluetooth-Smart-Bluetooth-low-energy/nRF52-DK">nRF52 dev board</a>, and <a href="https://www.rigado.com">Rigado's</a> <a href="https://www.rigado.com/products/bmd-300-eval-kit/">BMD-300 Evaluation Board</a>.</p>
<p><img alt="Nordic and Rigado dev boards" src="https://airsource.co.uk/blog/images/2016/04/boards.jpg">
<em>Nordic and Rigado dev boards</em></p>
<p>For some reason the firmware I had written was drawing far far too much current in <a href="https://devzone.nordicsemi.com/question/61646/can-someone-explain-to-me-how-nordic-nrf51822-sleep-mode-works/">System-On Sleep mode</a>, when it should have been essentially unobservable on the scope I was using.</p>
<p><img alt="Excess current draw" src="https://airsource.co.uk/blog/images/2016/04/current_leakage.jpg">
<em>Excess current draw - CurB is the sleep current, which should be essentially zero.</em></p>
<h2>Starting to debug</h2>
<p>Applying my third rule, I rapidly established that a simple cut-down version of my program which broadcast adverts but nothing else did not draw excess current.</p>
<p>There were two clear possibilities. Either it was actually leaking huge amounts of current when asleep, or it wasn't actually going to sleep. Either case was triggered by something in my program.</p>
<p>I immediately had a few suspicions:</p>
<ul>
<li>The chipset, dev board, and SDK were relatively new. Was there any Product Anomaly that applied to my board? I identified the precise variant of the chipset I was using, and found nothing suspicious in the documentation for the chipset or for the Nordic dev board. I also checked the SoftDevice release notes.</li>
<li>I was using the <a href="https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk52.v0.9.0%2Fhardware_driver_saadc.html">SAADC</a> from a <a href="https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.s132.sds%2Fdita%2Fsoftdevices%2Fs130%2Fradio_notif%2Fradio_notification.html">radio notification callback</a>. Was I perhaps running into some edge case that the manufacturer hadn't tested? That's a bit like claiming the compiler is broken. Possible, but not probable.</li>
<li>I had missed something in the documentation about how to put the device to sleep. This seemed to be the most likely case, but I did lots of searching in the soft device documentation, and the forums, and didn't find anything.</li>
</ul>
<p>I had a look in the SDK examples, and found a program that used the SAADC. It worked fine. I extended it to use the radio notification callback. It worked fine. I cut-down my program and found the function that was causing the problem, narrowing down the issue to a particular line of code (more on that later) which seemed fairly innocuous - it just wrote to a block of memory outside the stack.</p>
<h2>Faulty diagnosis</h2>
<p>I had, by this stage, unwittingly broken my first and second rules of debugging. And once the second rule goes wrong, everything else is suspect. I then wasted some time trying to establish why writing to memory should cause a current leak.</p>
<p><img alt="Bad diagnosis" src="https://airsource.co.uk/blog/images/2016/04/bad_diagnosis.jpg">
<em>The section of code that confused me</em></p>
<p>When I started debugging the problem, I forgot to turn off optimisations in the compiler. That meant the code I was looking at was not necessarily the code that was being run, which broke my First Rule. In particular, some floating point operations I had executed a few lines higher were only actually compiled in if the result was used.</p>
<p>I then broke my Rules because I decided not to ask anyone for help, on the grounds that this problem related to a development environment that no one else in the office knew anything about. While this was true, that only meant that most likely no one could help me <em>fix</em> the problem. It did not mean that no one could help me <em>diagnose</em> the problem by spending five minutes sanity checking my own diagnosis process. If I had even then proved to myself that the code I was looking at was being run, I would have been okay. But I didn't. However, I was starting to not trust the diagnosis any more.</p>
<h2>Rule 4</h2>
<p>However, I did get one thing right. Instead of going back to the office after dinner to work on the problem (largely, I admit, because I couldn't find my office keys, and needed to use the oscilliscope) I decided to clean up my test cases a bit, and then got an early night (Rule 4). The next morning, working with a fresh mind (this is a partially valid application of rule 1!) I realised my error in not disabling optimisations (Rule 2), and rapidly found the problem.</p>
<p>The eventual problem turned out to be complex - but once I had the diagnosis, finding a fix was actually quite quick. I turned off optimisations, and discovered that the offending piece of code was actually the floating point instructions. A swift search for "nRF52 FPU sleep" in the Nordic dev forums brought up a related issue as the first link. It wasn't initially clear to me how this related to my problem, but the important thing was the fix provided worked.</p>
<h2>The actual problem - feel free to skip</h2>
<p>After another half hour of investigation, I realised that my case was actually the same as <a href="https://devzone.nordicsemi.com/question/70989/fpu-divide-by-0-and-high-current-consumption/">that discussed in a forum query</a>. There, the divide by zero triggered the <a href="http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABBFJEC.html">DZC bit in the FPU Status Control Register.</a>. In my case, a conversion from an integer to a float triggered the IXC bit which indicates an <a href="http://www.umiacs.umd.edu/~resnik/ling645_sp2002/cmu_manual/node19.html">Inexact Floating Point Exception</a>.</p>
<p>Both exceptions triggered an FPU IRQ that needed to be cleared before the CPU would sleep, therefore explaining my current leak. A spot more googling showed that the issue was actually documented by Nordic - only in <a href="https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v11.0.0%2Findex.html">the SDK documentation</a> rather than the <a href="https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.softdevices52%2Fdita%2Fsoftdevices%2Fs130%2Fs130.html&cp=1_3_0">soft device documentation</a> because it wasn't actually a bug, it was merely the documented way that things worked.</p>
<h2>The final results</h2>
<p><img alt="No leakage current" src="https://airsource.co.uk/blog/images/2016/04/no_leakage.jpg">
<em>The new oscilliscope trace - note that CurB (sleep current) is now zero, as closely as I can measure.</em></p>
<p>So what do we conclude? My rules would have worked - if only I had applied them properly!</p>
<p>Header image copyright <a href="https://commons.wikimedia.org/wiki/File:Chameleon-Stage_03.jpg">Sandriani Permani</a>. License: <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.en">CC BY-SA 4.0</a></p>10 Hot Tips for Unity Scripting2016-03-16T12:30:00+00:002016-03-16T12:30:00+00:00James Urentag:airsource.co.uk,2016-03-16:/blog/2016/03/16/10-hot-tips-for-unity-scripting/<p>In 2015 Airsource built its first game. After some research we chose to use Unity due to the multi-platform requirement and the significant amount of graphics and animations.</p>
<p>In 2015 Airsource built its first game. After some research we chose to use Unity due to the multi-platform requirement and the significant amount of graphics and animations.</p>
<p>The first thing to say is that Unity is great!</p>
<ul>
<li>There is a <a href="http://forum.unity3d.com">really supportive community</a></li>
<li>There are tons of free or very cheap <a href="https://www.assetstore.unity3d.com/en/">pre-built assets</a> you can drop in</li>
<li>There is a good amount of native mobile support (<a href="http://docs.unity3d.com/Manual/iphone-GettingStarted.html">iOS</a> and <a href="http://docs.unity3d.com/Manual/android.html">Android</a>)</li>
<li>The <a href="https://unity3d.com/get-unity/download">development environment</a> is easy to use and powerful, especially for rich graphics, particle animations etc</li>
</ul>
<p>So now that I've convinced you it's the right tool for your project, here are my ten hot dev tips:</p>
<ol>
<li>
<p><strong>C Sharp</strong> You have two choices for development language, C Sharp and UnityScript (Javascript but with a syntax for slightly stronger typing). Even if you love Javascript (<a href="https://airsource.co.uk/blog/2014/11/07/10-hot-tips-for-refactoring-your-javascript/">and I do!</a>), I would still recommend C-Sharp for these reasons:</p>
<ul>
<li>Most of the pre-built Unity Store assets are written in C Sharp (JS ones would still work, it's just easier to read all code in the same language)</li>
<li>You can use namespaces to easily segment your code</li>
<li>It is easy for weird bugs to creep in with UnityScript due to loose typing and implicit declaration.</li>
</ul>
<p>There's some good discussion on this in <a href="http://answers.unity3d.com/questions/7528/how-should-i-decide-if-i-should-use-c-javascript-u.html">this post</a>.</p>
</li>
<li>
<p><strong>git</strong> There are a couple of tricks to get Unity working with git version control. In <code>Editor > Project Settings > Editor</code>, make Unity's meta files visible in <code>Version Control Mode</code> and switch to <code>Force Text</code> in <code>> Asset Serialization Mode</code>. You'll also need a <code>.gitignore</code> file in the root of your repo to ignore changes to the files you don't want to check in. You can create one with <a href="https://www.gitignore.io">this handy tool</a>.</p>
</li>
<li>
<p><strong>Asset Store</strong> Before you write any code from scratch, have a good hunt round the <a href="https://www.assetstore.unity3d.com/en/">Asset Store</a>. It's not just graphic and animation assets, there are lots of full pre-built games and plugins, and most are free or surprisingly cheap considering their high quality.</p>
</li>
<li>
<p><strong>Architecture</strong> The big architectural difference I found when working with Unity, is that it is better to attach scripts to individual game objects so they can look after their own state. This is a quite different approach to iOS or Android programming. For example, in iOS you might have a ViewController that disables its 'Refresh' button when it receives a callback that says the network is unreachable. In Unity you would do this quite differently. You would write a script ButtonEnabledIfNetwork, that periodically checked the network and set it's own state accordingly.</p>
<div class="highlight"><pre><span></span><code><span class="k">public</span><span class="w"> </span><span class="k">class</span><span class="w"> </span><span class="nc">ButtonEnabledIfNetwork</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">MonoBehaviour</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">private</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">float</span><span class="w"> </span><span class="n">REACHABILITY_INTERVAL</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0.2f</span><span class="p">;</span>
<span class="w"> </span><span class="n">IEnumerator</span><span class="w"> </span><span class="nf">Start</span><span class="w"> </span><span class="p">()</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Button</span><span class="w"> </span><span class="n">button</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">gameObject</span><span class="p">.</span><span class="n">GetComponent</span><span class="o"><</span><span class="n">Button</span><span class="o">></span><span class="w"> </span><span class="p">();</span>
<span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">button</span><span class="p">.</span><span class="n">interactable</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Network</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">reachable</span><span class="p">;</span>
<span class="w"> </span><span class="k">yield</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="nf">WaitForSeconds</span><span class="p">(</span><span class="n">REACHABILITY_INTERVAL</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Now you can attach this same script to all buttons that you need to disable if there is no network. Plus it is efficient as it makes use of Unity's multi threading. Start() is a coroutine, Network is a singleton - both described below.</p>
</li>
<li>
<p><strong>Coroutines</strong> Multi-threading is handled with <a href="http://docs.unity3d.com/Manual/Coroutines.html">coroutines in Unity</a> and they are a vital part of programming for good performance in a game where multiple elements require updating (which is pretty much every game). In a coroutine you can hand back ('yield') control to the operating system at a sensible point in your execution, like at the end of a loop or while waiting for a network response. In the example above, we yield after we have updated the state of the button. You can yield for a single frame (by returning null), or for an approximate amount of time with the WaitForSeconds class. From then on Unity will handle all the threading for you, balancing requirements to prioritise the game's frame rate.</p>
</li>
<li>
<p><strong>Singletons</strong> Singletons are tricky because it's likely you want the game object with the Singleton attached to stick around even when you change scenes. <a href="http://wiki.unity3d.com/index.php/Singleton">This Unity Wiki example</a> uses DontDestroyOnLoad and it works well. I just removed the applicationIsQuitting stuff which seemed to cause more problems than it fixed in Unity 5.</p>
</li>
<li>
<p><strong>Pre-fabs</strong> Pre-fabs are an amazing tool in Unity, but must be used with care. The principle is that you pre-fabricate a game object so you can re-use it throughout the application. Then when you or the runtime create the object you tweak some of the parameters, such as starting position, for that particular instance and away you go. Just take care with the three new buttons at the top of your inspector: <a href="https://airsource.co.uk/blog/images/prefab.png"><img alt="Pre-Fab Buttons" src="https://airsource.co.uk/blog/images/prefab.png"></a> If you hit Apply it will take any changes that you have made to this instance and apply them to all other instances based on this pre-fab. My solution to this was to have a hidden master pre-fab instance that I made changes to and then applied globally.</p>
</li>
<li>
<p><strong>Animation 'Gotcha'</strong> This is a weird one: You can't drive game object parameters from code <em>and</em> animation, and I can't find this documented anywhere. If you even touch an animatable parameter from code all animations of this parameter will just stop working... If you need to do both you will need to work around this by nesting objects for example.</p>
</li>
<li>
<p><strong>Serializable</strong> To store game state you will need to make the class serializable. You can customise serialization with the 'ISerializationSurrogate' interface. <a href="http://forum.unity3d.com/threads/vector3-not-serializable.7766/#post-2098308">There's a good example here</a>.</p>
</li>
<li>
<p><strong>Script Unity</strong> I only started to get into this towards the end of the project, and it was the key thing I wish I'd started doing sooner. <a href="http://docs.unity3d.com/Manual/Plugins.html">Personalising Unity</a> to do the things you need for your project is one of the most powerful things about the IDE. If you find yourself doing some repetitive task when importing assets for example, write a quick plugin to do it. It's dead simple and will save you bags of time.</p>
</li>
</ol>
<p>I hope these tips help with your Unity project. Happy coding.</p>Become an iOS Developer in Three Weeks!2016-02-11T09:00:00+00:002016-02-11T09:00:00+00:00Ben Blaukopftag:airsource.co.uk,2016-02-11:/blog/2016/02/11/developer-course/<p>Anyone who has been out from under their rock in the past five years cannot have failed to notice the continual adverts for courses that promise to make you an iOS developer in three weeks.</p>
<p>Anyone who has been out from under their rock in the past five years cannot have failed to notice the continual adverts for courses that promise to make you an iOS developer in three weeks.</p>
<p>I dare say that at least some of these courses have merit. The problem I see with them is that anyone competent at programming knows full well that they do not need a course to learn how to do something. They need a set of instructions on how to install the build tools, which is almost always the painful bit, an example project (that actually builds), and a good internet connection to the documentation and <a href="http://stackoverflow.com/">StackOverflow</a>. Plus some time to play. Anyone employing (competent) engineers should know the same.</p>
<p>Naturally you can learn all of that from formal tuition as well. But what formal tuition will rarely give you - and where the value of the engineer with five years experience rather than three weeks lies - is not knowing how things work. It is knowing how to work efficiently, effectively, and how to cope when things don't work.</p>
<p>For example, five complexities of iOS that you are unlikely to pick up from a short course</p>
<ul>
<li>NSURLCache <a href="https://airsource.co.uk/blog/2014/10/11/nsurlcache-ios8-broken/">just does not work properly</a>.</li>
<li>If you <a href="http://stackoverflow.com/questions/12652502/app-killed-by-sigkill-when-changing-privacy-settings">change Photos permissions, your app will restart</a>.</li>
<li>An OTA install of an application on iOS 8 <a href="http://stackoverflow.com/questions/25923099/over-the-air-ota-installation-fails-for-ios8-app-using-itms-services-url">requires that the identifier presented in the plist be different to the original install</a>. But on iOS 9 <a href="http://stackoverflow.com/questions/32678253/ios9-unable-to-download-app">it has to be the same</a>.</li>
<li>You cannot set cornerRadius in Interface Builder - <a href="http://stackoverflow.com/questions/12301256/is-it-possible-to-set-uiview-border-properties-from-interface-builder">but you can if you address it as layer.cornerRadius in a User Defined Runtime Attribute</a>.</li>
<li>Background behaviours of Bluetooth on iOS. No link provided - this is going to be a whole blogpost series by itself.</li>
</ul>
<p>Even if your course happens to teach you all the gotchas on an operating system, it will only teach you the ones the instructor knows about - and there are new ones appearing all the time. The value of an experienced developer is that they know how to debug problems when things go wrong. And it cannot possibly teach you all of the gotchas as well as the critical foundation of how a computer actually works under the hood. </p>
<p>And that's just the raw programming. I took a look at the lecture list for a three week course, and compared it to some of the books hanging around the office here. There were several things absent from the syllabus that I would consider critical to being a competent engineer, or let us say developer, as opposed to a programmer.</p>
<ul>
<li>Understanding Requirements</li>
<li>Design</li>
<li>Estimating</li>
<li>Testing</li>
<li>Debugging</li>
<li>Performance</li>
<li>How to write robust, maintainable, portable code.</li>
<li>Source Code Control (subversion,git)</li>
<li>Release Management</li>
</ul>
<p>What do these courses promise?</p>
<ul>
<li>No pre-knowledge required - I'll teach you everything you need to know</li>
<li>Develop any iOS app you want</li>
</ul>
<p>No it won't; and no, you won't.</p>
<ul>
<li>Get app development jobs on freelancer sites</li>
</ul>
<p>You can get those anyway. Just submit the lowest bid. The trick is delivering it for a profit, which I would argue is almost impossible for those sites unless you compromise immensely on quality.</p>
<p>At best, a course will enable you to write fairly standard applications in fairly standard environments. If you are competent, you will spend the next few years gradually improving the quality of your code, your estimation skills, your processes, and your repertoire of tools to be applied when things fail to work the way they should. And if you are competent, you will realise you never needed to pay that money for the course in the first place, because while a three week course may have saved you a little bit of time overall (if the instructor was really good), it has certainly not made you an expert developer.</p>
<p>If you are incompetent, you will fail to do all of these things, and you will leave a trail of half-finished (more like 20% finished) projects behind you.</p>Quickstart guide to Google Cloud Messaging for iOS2016-01-29T10:41:00+00:002016-01-29T10:41:00+00:00James Urentag:airsource.co.uk,2016-01-29:/blog/2016/01/29/quickstart-google-cloud-messaging-ios/<p>There are a lot of moving parts to GCM so hopefully this quickstart guide will be useful to other developers. This post is intended to step through the parts of the process that are not described fully in the <a href="https://developers.google.com/cloud-messaging/ios/client">Google tutorial</a>.</p>
<p>There are a lot of moving parts to GCM so hopefully this quickstart guide will be useful to other developers. This post is intended to step through the parts of the process that are not described fully in the <a href="https://developers.google.com/cloud-messaging/ios/client">Google tutorial</a>.</p>
<h1>Benefits</h1>
<p>There are plenty of good reasons to choose GCM over raw Apple notifications:</p>
<ul>
<li>You can post notifications to Android and iOS devices with the same system.</li>
<li>You can (with a bit of fiddling) use a cross-platform tool like Unity to develop the app with minimal platform-specific code.</li>
<li>Google handle the communication with the Apple Push Notification Services servers for you, which is tricky to say the least...</li>
</ul>
<h1>Before we get started...</h1>
<p>You will need:</p>
<ul>
<li>An Apple Developer account and access to the certificates page</li>
<li>Your iOS app's bundle ID (found in your XCode project target General settings). It's normally something like <code>uk.co.mycompany.myapp</code>.</li>
<li>A server to keep track of the device registrations and hit GCM servers to post the notifications</li>
</ul>
<h1>Apple Push Notification Service</h1>
<p>Before I explain how GCM works, it's going to help to understand a bit about APNS. When you launch the app it requests the push service from the OS. If this is the first time, and the user has notifications globally enabled, then the OS will ask the user if they want to receive notifications. If they agree, the app will fetch a security token from Apple, and you'll get this in a callback.</p>
<p>This token is what you need to send the notification to the correct device, so in that callback you need to hit your own server and store the device's ID and token.</p>
<p>Now you have a table on your server full of devices and tokens, the final thing you need is certificates from Apple that are specific for your bundle ID. Then you can hit the APNS server to send devices notifications.</p>
<h1>GCM</h1>
<p>GCM is similar, but crucially it allows you to treat Android and iOS devices in the same way. Google will handle the APNS connection too. You'll need to:</p>
<ul>
<li>Register your app with Google and give them your certificates</li>
<li>In the callback from iOS, send the registration token to Google. They will give you a new token back.</li>
<li>Register the new token on your server alongside the device ID.</li>
</ul>
<p>Now your server can hit GCM to send notifications.</p>
<h1>Let's do it...</h1>
<p>Now I'll take you through these steps in more detail:</p>
<h2>Register your app</h2>
<p>The tricky bit here is getting the certificates right:</p>
<ul>
<li>Log into your Apple developer account and go to <a href="https://developer.apple.com/account/ios/certificate/certificateList.action">Certificates, Identifiers and Profiles</a></li>
<li>If you don't already have one, create an App ID with the correct (explicit) bundle ID and make sure the Push Notifications service is checked</li>
<li>Create two certificates for this app ID - Follow the instructions carefully to sign them with your KeyChain.<ul>
<li>A development certificate: <em>Apple Push Notification service SSL (Sandbox)</em></li>
<li>A production certificate: <em>Apple Push Notification service SSL (Sandbox & Production)</em></li>
</ul>
</li>
<li>Download both certificates, and open them in KeyChain. Right click on the certificate (<em>not</em> the key inside!) and export them as <code>development.p12</code> and <code>production.p12</code></li>
<li>Log into you Google developer account and <a href="https://developers.google.com/mobile/add?platform=ios&cntapi=gcm&cnturl=https:%2F%2Fdevelopers.google.com%2Fcloud-messaging%2Fios%2Fclient&cntlbl=Continue%20Adding%20GCM%20Support&%3Fconfigured%3Dtrue">register your app</a></li>
<li>When prompted upload your two .p12 certificates</li>
<li>If all went well, download your <code>GoogleService-Info.plist</code>. You will need this in the next step.</li>
<li>Make a note of the API key too. The server will use this to authenticate with GCM.</li>
</ul>
<h2>Configure Xcode</h2>
<p>Surprisingly, that's the hard bit done, now we just need to write some code. If you don't have CocoaPods you'll need it to import the libraries:</p>
<p>In your project directory:</p>
<p><code>sudo gem install cocoapods</code></p>
<p><code>pod init</code></p>
<p>Then add the line <code>pod 'Google/CloudMessaging'</code> to the Podfile and:</p>
<p><code>pod install</code></p>
<p>From now on open your <code>.xcworkspace</code> file, not your normal <code>.xcodeproject</code>.</p>
<ul>
<li>Drag the <code>GoogleService-Info.plist</code> into your project and add to all targets.</li>
<li>In your <code>AppDelegate.m</code>, add the callbacks as per <a href="https://developers.google.com/cloud-messaging/ios/client">Obtain a Registration Token</a>.</li>
<li>In your <code>_registrationHandler</code> block, call through to your server with your device ID and the GCM token.</li>
</ul>
<h2>Your server</h2>
<p>Whatever server architecture or framework you are using you will need:</p>
<ul>
<li>A database with a table of device IDs and tokens</li>
<li>An endpoint for registration that can be called by the Android or iOS client apps</li>
<li>Some code to hit the GCM server with your API key (you got this when you registered the app) and the device token(s).</li>
<li>An endpoint to test notifications - ideally a simple web form where you can enter a device ID and a message.</li>
</ul>
<h1>More information</h1>
<p><a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html">Apple Push Notification Service</a></p>
<p><a href="https://developers.google.com/cloud-messaging/">Google Cloud Messaging</a></p>
<p><a href="https://developers.google.com/cloud-messaging/ios/client">Setting up a GCM Client App on iOS</a></p>
<p><a href="https://developers.google.com/cloud-messaging/ios/start">Try Google Cloud Messaging for iOS</a></p>
<h1>I hope this helps! Good luck!</h1>
<p>Portions of this page are reproduced from work created and <a href="https://developers.google.com/site-policies">shared by Google</a> and used according to terms described in the <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons 3.0 Attribution License</a>.</p>A new project: Quilt2015-11-02T16:02:00+00:002015-11-02T16:02:00+00:00Sam Duketag:airsource.co.uk,2015-11-02:/blog/2015/11/02/quilt-intro/<p>Recently I departed from the safety of mobile apps to dive into OS X development and Swift. A bit of an adventure - and strangely, all in the name of Android development...</p><p>Recently I departed from the safety of mobile apps to dive into OS X development and Swift. A bit of an adventure!</p>
<h1>Motivation</h1>
<p>So why was I heading into unknown territory? On a recent Android project we had a recurring problem with a set of 9-patch assets. It was a large project with a tight deadline, and somewhere along the way these particular assets had gone a bit… awry. Our designer’s time was overloaded (it's okay - we have two designers now!) and, with the 9-patch graphics evolving and several resolutions to cater for, we ended up having a series of failed builds. (If you’re curious what a 9-patch is, or how it can make a build fail, have a look <a href="http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch">here</a>)</p>
<p>Our process for handling 9-patches wound up looking something like this:</p>
<ol>
<li>A designer exports a new 9-patch graphic</li>
<li>They commit it to the repo</li>
<li>An engineer pulls the changes to integrate them</li>
<li>Unfortunately the 9-patches are malformed so the project fails to build! Disaster!</li>
<li>The engineer has to revert the designer's commit and ask them to try again. Back to step 1.</li>
</ol>
<p>The biggest problem occured when our designer left the office at step 2 after a long day's work and we had to wait until the next morning for a fix! The root cause was found and we got back on course, but we realised that designers have no tools available to quickly and easily check the validity of 9-patches before committing them. Large graphic deliveries and tight deadlines are inevitable and mistakes will be made - this is not going away on its own.</p>
<p>So we decided to fix that.</p>
<p>(The 9-patch tooling, that is, not the designers committing to Git - we actually let them do that! Thanks SourceTree…)</p>
<h1>Developing a solution</h1>
<p>We did our research first of course. We initially thought that the solution to this could be found right at the source: project structuring and tools. But after a thorough investigation by Steve, we have concluded there really is no elegant solution to the problem. Adobe products lack the tools to help with 9-patches and Android’s draw9patch tool was not working as a solution. Tools such as Sketch have a number of 9-patch plugins but they would still need editing afterward. So if you can’t go over, round or under it… make a Quilt.</p>
<p>Our aim with Quilt is to try and help the designer as much as possible. With this in mind we have two strategies to employ - a two pronged attack if you will…</p>
<ul>
<li>
<p><strong>Active</strong> The graphic assets are exported and ready to go. But how do you know they are valid?</p>
<p>You could download the Android developer tools, open the project, and try to compile it; but that is an awful waste of time and effort. Even if something was found to be wrong, it might be hard to identify. With a dedicated desktop tool you could manually check the assets before committing and see any problems in an easy to read list.</p>
</li>
<li>
<p><strong>Passive</strong> When all is said and done a 9-patch can either be wrong or it can be right, so there is no way a bad one should end up in the repository. And what do you do when you want to enforce a rule on a repository?</p>
<p>Git Hooks to the rescue!</p>
<p>With a git hook installed in your local repository we can alert you that something is wrong and even what that something is. With the desktop tool you could get a more usable display of what is wrong (rather than a nasty command line output) and, hopefully, with a couple of tweaks and another check you can get your commit in easily.</p>
</li>
</ul>
<p>To that end, our new designer Merce has been cutting her teeth in Balsamiq Mockups by creating a way to bring these tools together into a nice, easy to use product, more details on that in part two...</p>10 Hot Tips for Refactoring Your Javascript2014-11-07T12:30:00+00:002014-11-07T12:30:00+00:00James Urentag:airsource.co.uk,2014-11-07:/blog/2014/11/07/10-hot-tips-for-refactoring-your-javascript/<p>This week I've had a departure from app development to build a web application for a new product we're developing at Airsource HQ. As with any web app this has involved a ton of Javascript, and along the way I picked up a few tips for refactoring that might be useful for your projects. 10 Hot one's as it happens...</p><p>This week I've had a departure from app development to build a web application for a new product we're developing at Airsource HQ. As with any web app this has involved a ton of Javascript, and along the way I picked up a few tips for refactoring that might be useful for your projects. 10 Hot one's as it happens...</p>
<ol>
<li>
<p><strong>Use a script loader.</strong> This keeps your HTML clean (you just need to include one script) and allows you to set dependencies on loading scripts. I used <a href="http://requirejs.org">requirejs</a> for this and it was great!</p>
</li>
<li>
<p><strong>Namespaces.</strong> With Javascript the default for every variable declaration is global, so it's very easy to get in a mess when you want variables with names like 'user'.</p>
<p>Declare a namespace like this:</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">MyNamespace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">MyNamespace</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="p">{};</span>
</code></pre></div>
<p>Then from then onwards you can use:</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">MyNamespace</span><span class="p">.</span><span class="nx">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Me"</span><span class="p">;</span>
</code></pre></div>
</li>
<li>
<p><strong>var i = 0.</strong> Without the keyword var, the variable is global. With it, the variable is valid only in the current scope (between the current set of curly braces). It's easy to forget to use it because it's not strictly required, but omitting it can lead to some weird and wonderful bugs. For example</p>
<div class="highlight"><pre><span></span><code><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="nx">i</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="mf">5</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// ... then later, even way later deep in a function...</span>
<span class="nx">test</span><span class="p">();</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">test</span><span class="p">()</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">while</span><span class="p">(</span><span class="nx">i</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="mf">5</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>You will get no error message for not declaring the second i, and the second loop won't run at all...</p>
<p><a href="http://jsfiddle.net/2vnq11eu/1/">See for yourself</a></p>
</li>
<li>
<p><strong>Modules are brilliant.</strong> Basically they are just a logical collection of functions and variables, analogous to static (or 'class') methods. You can encapsulate private data and functionality too. <a href="https://github.com/stevekwan/experiments/blob/master/javascript/module-pattern.html">stevekwan has a really helpful example</a> that I used.</p>
</li>
<li>
<p><strong>Classes - also brilliant.</strong> Unlike modules you are creating object instances - perfect for wrapping up data from the data model for example. I used another <a href="https://github.com/stevekwan/experiments/blob/master/javascript/class.html">example from stevekwan</a> that shows how to make methods and members public and private.</p>
</li>
<li>
<p><strong>Polymorphism - kind of.</strong> Because Javascript is very weakly typed, it's difficult to ensure that a class implements the right methods, but if you are careful you can use this weak typing to your advantage. For example, call a method on an object in a list regardless of what type the object is at runtime. Just remember to handle the error if the method does not exist.</p>
</li>
<li>
<p><strong>Use <a href="http://jquery.com">jQuery</a></strong> jQuery is a pretty big script to load, but for any largish web application it is brilliant. It provides a load of really useful wrappers for standard Javascript functions that you need to call all the time. For example each() provides a quick way of looping through elements:</p>
<div class="highlight"><pre><span></span><code><span class="nx">$</span><span class="p">(</span><span class="s2">".li"</span><span class="p">).</span><span class="nx">each</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span>
<span class="p">{</span>
<span class="w"> </span><span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span><span class="s2">"<p>It works!<p>"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>With jQuery you can also easily create...</p>
</li>
<li>
<p><strong>Custom controls.</strong> It is very likely you will need re-usable custom controls throughout the application. I used <a href="http://stefangabos.ro/jquery/jquery-plugin-boilerplate-revisited/">this boilerplate</a> to build a drop-zone plugin for dragging and dropping files for example.</p>
</li>
<li>
<p><strong>Wrap the console.</strong> The Javascript console is great for debugging, but it is limited to pretty basic logging, and you probably don't want any debug level logging when the app is deployed. There are plenty of open wrappers out there that add support for log levels etc, but it's pretty simple to wrap it yourself and you can even use the same module to display status in the UI for example.</p>
</li>
<li>
<p><strong>Embrace the script!</strong> If like me you are from a traditional OO background and don't like the word 'static', remember this is a scripting language, and that comes with benefits as well as drawbacks. Segmenting the codebase into logical chunks rather than pure objects can be very useful, readable and maintainable if done right.</p>
</li>
</ol>
<p>These 10 tips are the key details I learnt working with JS for this project. I hope they help you refactor your JS and get a bit further up the never-ending software development learning curve.</p>Did you notice this bizarre iOS 8 release process option?2014-10-21T17:00:00+01:002014-10-21T17:00:00+01:00Ben Blaukopftag:airsource.co.uk,2014-10-21:/blog/2014/10/21/ios8_api_diffs/<p>I think there may be some confusion over at <a href="http://www.theguardian.com/artanddesign/2013/nov/15/norman-foster-apple-hq-mothership-spaceship-architecture">Donut Central</a> about how a <a href="http://msdn.microsoft.com/en-gb/library/windows/desktop/cc307420.aspx">release</a> <a href="http://dilbert.com/strips/comic/2010-03-14/">process</a> is supposed to work. I was recently perusing the <a href="https://developer.apple.com/library/ios/releasenotes/General/iOS80APIDiffs/frameworks/Foundation.html">Foundation changes in iOS 8</a> and noticed an absolute gem:</p>
<p>I think there may be some confusion over at <a href="http://www.theguardian.com/artanddesign/2013/nov/15/norman-foster-apple-hq-mothership-spaceship-architecture">Donut Central</a> about how a <a href="http://msdn.microsoft.com/en-gb/library/windows/desktop/cc307420.aspx">release</a> <a href="http://dilbert.com/strips/comic/2010-03-14/">process</a> is supposed to work. I was recently perusing the <a href="https://developer.apple.com/library/ios/releasenotes/General/iOS80APIDiffs/frameworks/Foundation.html">Foundation changes in iOS 8</a> and noticed an absolute gem:</p>
<p><img alt="iOS 8 API Diffs" src="https://airsource.co.uk/blog/images/ios8_api_diffs.png">
Say what? I went and checked the <a href="https://developer.apple.com/library/IOs/releasenotes/General/iOS70APIDiffs/index.html">iOS 7 diffs</a> - it is definitely there!</p>
<p>Here's what the iOS 7 header says:</p>
<div class="highlight"><pre><span></span><code><span class="o">/*!</span>
<span class="w"> </span><span class="err">@</span><span class="k">const</span><span class="w"> </span><span class="n">NSURLErrorBackgroundTaskCancelledReasonKey</span>
<span class="w"> </span><span class="err">@</span><span class="n">abstract</span><span class="w"> </span><span class="n">The</span><span class="w"> </span><span class="n">NSError</span><span class="w"> </span><span class="n">userInfo</span><span class="w"> </span><span class="n">dictionary</span><span class="w"> </span><span class="n">key</span><span class="w"> </span><span class="n">used</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">store</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">retrieve</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">NSNumber</span><span class="w"> </span><span class="n">corresponding</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">reason</span><span class="w"> </span><span class="n">why</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">background</span>
<span class="w"> </span><span class="n">NSURLSessionTask</span><span class="w"> </span><span class="n">was</span><span class="w"> </span><span class="n">cancelled</span>
<span class="w"> </span><span class="o">*/</span>
<span class="n">FOUNDATION_EXPORT</span><span class="w"> </span><span class="n">NSString</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">NSURLErrorBackgroundTaskCancelledReasonKey</span><span class="w"> </span><span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="n">NA</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="n">_0</span><span class="p">);</span>
</code></pre></div>
<p>and here's the corresponding version from the iOS 8 headers:</p>
<div class="highlight"><pre><span></span><code><span class="o">/*!</span>
<span class="w"> </span><span class="err">@</span><span class="k">const</span><span class="w"> </span><span class="n">NSURLErrorBackgroundTaskCancelledReasonKey</span>
<span class="w"> </span><span class="err">@</span><span class="n">abstract</span><span class="w"> </span><span class="n">The</span><span class="w"> </span><span class="n">NSError</span><span class="w"> </span><span class="n">userInfo</span><span class="w"> </span><span class="n">dictionary</span><span class="w"> </span><span class="n">key</span><span class="w"> </span><span class="n">used</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">store</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">retrieve</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">NSNumber</span><span class="w"> </span><span class="n">corresponding</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">reason</span><span class="w"> </span><span class="n">why</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">background</span>
<span class="w"> </span><span class="n">NSURLSessionTask</span><span class="w"> </span><span class="n">was</span><span class="w"> </span><span class="n">cancelled</span>
<span class="w"> </span><span class="o">*/</span>
<span class="n">FOUNDATION_EXPORT</span><span class="w"> </span><span class="n">NSString</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">NSURLErrorBackgroundTaskCancelledReasonKey</span><span class="w"> </span><span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="mi">10</span><span class="n">_10</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="n">_0</span><span class="p">);</span>
</code></pre></div>
<p><a href="http://www.raywenderlich.com/forums/viewtopic.php?f=2&t=9819">Reading between the lines</a>, maybe this is code for "We know it is in iOS 7, but it will never actually work".</p>
<p>Next, take a look at how XCode has been released. A few weeks ago we had Xcode 6.1 GM (Gold Master). Then, a bit later, we had Xcode GM 2. Guessing the first one was more of a Silver Master then.</p>Caching broken on iOS 8 - 3/32014-10-21T12:00:00+01:002014-10-21T12:00:00+01:00Ben Blaukopftag:airsource.co.uk,2014-10-21:/blog/2014/10/21/nsurlcache-ios8-broken-3/<p><a href="https://airsource.co.uk/blog/2014/10/11/nsurlcache-ios8-broken/">In Part 1 I discussed how NSURLCache is broken on iOS 8</a>, and <a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">delivered some source code in Part 2</a>. </p>
<p>Now that iOS 8.1 has been released (with absolutely <a href="https://developer.apple.com/library/ios/releasenotes/Miscellaneous/RN-iOSSDK-8.1/">no mention in the release notes about this issue</a>, I ran all the tests again,<p><a href="https://airsource.co.uk/blog/2014/10/11/nsurlcache-ios8-broken/">In Part 1 I discussed how NSURLCache is broken on iOS 8</a>, and <a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">delivered some source code in Part 2</a>. </p>
<p>Now that iOS 8.1 has been released (with absolutely <a href="https://developer.apple.com/library/ios/releasenotes/Miscellaneous/RN-iOSSDK-8.1/">no mention in the release notes about this issue</a>, I ran all the tests again, and found that the results were the same as the betas.</p>
<ul>
<li>Cache reported size matches actual - <font color="#00ff00">PASS</font>. The reported size now includes the SHM and WAL files.</li>
<li>Clear cache on startup - <font color="#00ff00">PASS</font>.</li>
<li>Cache correctly purged - <font color="#ffa000">FLAWED</font>. Sort of - the disk usage sometimes exceeds the maximum size of the cache by a megabyte or two. I believe that the operating system is excluding the size of the WAL file when deciding if the cache is too big or not. </li>
<li>Cache reads - <font color="#00ff00">PASS</font>.</li>
<li>Cache clears on demand - <font color="#00ff00">PASS</font> (but does not delete the WAL, even though that is included in the reported size).</li>
<li>Clearing cache a second time resets the WAL - <font color="#00ff00">PASS</font> (determined by inspecting logs in XCode).</li>
<li>Can clear individual cache items - <font color="#ff0000">FAIL</font>.</li>
</ul>
<p>As I mentioned, the unlimited growth of the cache might cause problems for people upgrading, because application data would be too large. Sure enough, when one of our employees tried to upgrade to 8.1 last night, they found that Facebook was using over 600MB of data, and Twitter was using over 500MB. The cache problem was well and truly in effect.</p>Caching broken on iOS 8 - 2/32014-10-13T14:00:00+01:002014-10-13T14:00:00+01:00Ben Blaukopftag:airsource.co.uk,2014-10-13:/blog/2014/10/13/nsurlcache-ios8-broken-2/<p>The roundup for the released version of 8.1 is available in <a href="https://airsource.co.uk/blog/2014/10/21/nsurlcache-ios8-broken-3/">Part 3</a>.</p>
<p><a href="https://airsource.co.uk/blog/2014/10/11/nsurlcache-ios8-broken/">In Part 1 I discussed how NSURLCache is broken on iOS 8</a>, and promised some source code. <a href="https://airsource.co.uk/blog/images/TestURLCache.zip">The source code is available here</a>, and it is worth taking a closer look at some of the results.</p>
<p>The roundup for the released version of 8.1 is available in <a href="https://airsource.co.uk/blog/2014/10/21/nsurlcache-ios8-broken-3/">Part 3</a>.</p>
<p><a href="https://airsource.co.uk/blog/2014/10/11/nsurlcache-ios8-broken/">In Part 1 I discussed how NSURLCache is broken on iOS 8</a>, and promised some source code. <a href="https://airsource.co.uk/blog/images/TestURLCache.zip">The source code is available here</a>, and it is worth taking a closer look at some of the results.</p>
<p>The first thing to do is to edit the configuration section in AppDelegate.h and enter the URL of a suitable image to download. I have deliberately not specified an image here, because I do not want to send high traffic to any particular site. The URL specified will not work from outside the Airsource network, so do not try it!</p>
<div class="highlight"><pre><span></span><code><span class="c1">// Configuration section</span>
<span class="c1">// Minimum cache size to actually use store on iOS 8. Below this it doesn't cache anything!</span>
<span class="c1">// Things are much more resilient on iOS 7</span>
<span class="cp">#define CACHE_SIZE (5 * 1024 * 1024)</span>
<span class="c1">// We will fetch this URL both in its provided form, and by appending a fake GET parameter.</span>
<span class="c1">// Ensure that your webserver returns the same content for a URL of the form</span>
<span class="c1">// http://quincykit.airsource.co.uk/test3.png?5</span>
<span class="cp">#define DOWNLOAD_URL @"http:</span><span class="c1">//quincykit.airsource.co.uk/test3.png?%lu"</span>
<span class="c1">// End configuration section</span>
</code></pre></div>
<p>Then, build the code and run it on an iOS 7.1 device. If you do not have a suitable device, then use an iOS 7.1 simulator. The results are the same. I have not bothered creating a suitable Default-568h.png, so you will see top and bottom black bands.</p>
<p>There are four things you can do from this screen.</p>
<p><img alt="entry screen of test app" src="https://airsource.co.uk/blog/images/nsurlcache.png"></p>
<ol>
<li>Fetch - this will execute 10 fetches of an image, each with a different request URL, to ensure that they create separate cache entries.</li>
<li>Inspect the cache - updates the stats at the top of the page. The cache operates somewhat asynchronously, so while we update stats after doing fetches and so forth, the cache may add or delete files after that time. </li>
<li>Clear the cache. </li>
<li>Execute a fetch, always with the same URL, to test that reading a cached entry is working.</li>
</ol>
<p>The success state of fetches (whether cached or otherwise) will be shown at the bottom of the screen, while the top of the screen shows the latest stats on the cache - actual size (determined by inspecting the actual files stored), size reported by NSURLCache:currentDiskUsage, and the maximum size of cache allowable (NSURLCache:diskCapacity). iOS 7 ignores the sizes of files Cache.db-shm, and Cache.db-wal when reporting the cache size, while iOS 8.1 does not, so I display both exclusive and inclusive sizes.</p>
<p>On startup, you will note that the size of the cache on iOS 7 is non-zero. This is because the database file Cache.db has been initialised by the operation system, and although it has no data rows, it still takes space.</p>
<p>Now execute a few fetches (use the Fetch button). You should see the cache size increase, assuming your fetches succeed. The memory cache will be much larger than the disk cache, because NSURLCache doesn't move things into store until it needs to. Hit "Inspect cache" after a few seconds if the size has not updated.</p>
<p>Next, restart the application. The reason for this is that we included the code</p>
<div class="highlight"><pre><span></span><code><span class="bp">NSURLCache</span><span class="w"> </span><span class="o">*</span><span class="n">cache</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">MyCache</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithMemoryCapacity</span><span class="o">:</span><span class="n">sz</span><span class="w"> </span><span class="n">diskCapacity</span><span class="o">:</span><span class="n">sz</span><span class="w"> </span><span class="n">diskPath</span><span class="o">:</span><span class="s">@"nsurlcache"</span><span class="p">];</span>
<span class="p">[</span><span class="bp">NSURLCache</span><span class="w"> </span><span class="n">setSharedURLCache</span><span class="o">:</span><span class="n">cache</span><span class="p">];</span>
<span class="c1">// This apparently has no effect on iOS 7, whether done before or after the setSharedURLCache line</span>
<span class="c1">// However, on iOS 8 it works fine.</span>
<span class="c1">// BUT on iOS 8 it will not remove cached responses that would never have been stored in the cache.</span>
<span class="p">[</span><span class="n">cache</span><span class="w"> </span><span class="n">removeAllCachedResponses</span><span class="p">];</span>
</code></pre></div>
<p>in application:didFinishLaunchingWithOptions:</p>
<p>On restart, you will see that the cache has NOT been cleared. Moreover, everything that was in memory has been moved into store. Clearly it does not matter how the application exits - the cache can be treated as persistent. Clearly also, clearing the cache at startup does not work on iOS 7.</p>
<p>Now hit "Clear cache". The cache correctly clears out.</p>
<p>Next, hit "Fetch lots". This execute 10 fetches and will fill up the cache quickly. You should soon see that the disk size of the cache shrinks, indicating that the cache is being purged. Moreover, neither the disk size nor memory size of the cache will exceed the maximum specified. </p>
<p>An important test is to check that the cache is read from. Hit "Cacheable Fetch". Then hit it again. You should see, in the logs on XCode, the line "Retrieved <NSCachedURLResponse: ....>". If you are on a device, you can simply put the device into Airplane mode, and see if the fetch succeeds. If it does, you <em>must</em> be reading from the cache.</p>
<p>Finally, I <a href="http://stackoverflow.com/questions/26260401/nsurlcache-does-not-clear-stored-responses-in-ios8">noticed that a report</a> that removeCachedResponseForRequest: has no effect. My own experimentation confirmed this - on both iOS 8, and 8.1. I found that removeCachedResponsesSinceData: <em>did</em> work - a new API for iOS 8 that has yet to make it into the documentation (it is present in the <a href="https://developer.apple.com/library/ios/releasenotes/General/iOS80APIDiffs/frameworks/Foundation.html">API diffs for iOS 8</a>), but I fail to see what use this API is - surely I want to remove cached responses from <em>before</em> a particular date, not afterwards! I have not added test code for this yet, but I have noted the results below.</p>
<p>Results from iOS 7.1.2 (device or simulator)</p>
<ul>
<li>Cache reported size matches actual - <font color="#ffa000">FLAWED</font>. The reported size excludes the SHM and WAL files.</li>
<li>Clear cache on startup - <font color="#ff0000">FAIL</font>.</li>
<li>Cache correctly purged - <font color="#00ff00">PASS</font>.</li>
<li>Cache reads - <font color="#00ff00">PASS</font>.</li>
<li>Cache clears on demand - <font color="#00ff00">PASS</font>. The WAL is not deleted - and this can be large.</li>
<li>Clearing cache a second time resets the WAL - <font color="#00ff00">PASS</font> (determined by inspecting logs in XCode)</li>
<li>Can clear individual cache items - <font color="#00ff00">PASS</font>.</li>
</ul>
<p>Repeat the tests on a iOS 8.0.x device (simulator not tested), and you should get the following results:</p>
<ul>
<li>Cache reported size matches actual - <font color="#ff0000">FAIL</font>. The reported size is consistently far too small.</li>
<li>Clear cache on startup - <font color="#00ff00">PASS</font>. </li>
<li>Cache correctly purged - <font color="#ff0000">FAIL</font>. You should, by repeatedly hitting the "Fetch lots" button, be able to generate cache sizes (both actual and reported) clearly in excesss of the specified maximum. Eventually you will see the reported cache size reduce - but NOT the actual size.</li>
<li>Cache reads - <font color="#00ff00">PASS</font>.</li>
<li>Cache clears on demand - <font color="#00ff00">PASS</font>. </li>
<li>Clearing cache a second time resets the WAL - <font color="#00ff00">PASS</font> (determined by inspecting logs in XCode)</li>
<li>Can clear individual cache items - <font color="#ff0000">FAIL</font>.</li>
</ul>
<p>Repeat the tests on an iOS 8.1 beta device or simulator, and you should get the following results:</p>
<ul>
<li>Cache reported size matches actual - <font color="#00ff00">PASS</font>. The reported size now includes the SHM and WAL files.</li>
<li>Clear cache on startup - <font color="#00ff00">PASS</font>.</li>
<li>Cache correctly purged - <font color="#ffa000">FLAWED</font>. Sort of - the disk usage sometimes exceeds the maximum size of the cache by a megabyte or two. I believe that the operating system is excluding the size of the WAL file when deciding if the cache is too big or not. </li>
<li>Cache reads - <font color="#00ff00">PASS</font>.</li>
<li>Cache clears on demand - <font color="#00ff00">PASS</font> (but does not delete the WAL, even though that is included in the reported size).</li>
<li>Clearing cache a second time resets the WAL - <font color="#00ff00">PASS</font> (determined by inspecting logs in XCode).</li>
<li>Can clear individual cache items - <font color="#ff0000">FAIL</font>.</li>
</ul>
<p>There are two more potentially significant issue that I discovered while doing this research. The minimum disk cache capacity on iOS 8 appears to be 5 megabytes, determined by experimentation. When I set the disk capacity below that value, no data was created on the file system, nor was any memory used. No such issues showed up on iOS 7 for small cache sizes.</p>
<p>This has a potentially serious consequence - if a developer set the size of the cache explicitly on iOS 7 to a value smaller than 5 megabytes, they will find that the cache is not used at all (even if present on device) on iOS 8. The cache will instantiate properly - and storeCachedResponse:forRequest: will be called - but nothing will be stored - and any existing data present on the file system from when the app was used with iOS 7 will neither be retrieved nor cleared.</p>Caching broken on iOS 8 - 1/32014-10-11T22:00:00+01:002014-10-11T22:00:00+01:00Ben Blaukopftag:airsource.co.uk,2014-10-11:/blog/2014/10/11/nsurlcache-ios8-broken/<p><a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">Part 2, with source code and detailed results, is now available.</a></p>
<p>The roundup for the released version of 8.1 is available in <a href="https://airsource.co.uk/blog/2014/10/21/nsurlcache-ios8-broken-3/">Part 3</a>.</p>
<p>Apple have already experienced one PR disaster with the iOS 8.0.1 release which <a href="http://techcrunch.com/2014/09/24/apples-ios-8-0-1-update-says-healthkit-apps-can-now-come-to-the-app-store/">broke cell phone operation for some users</a>. They may be on their way to another problem - less serious this time, but still significant.</p>
<p><a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">Part 2, with source code and detailed results, is now available.</a></p>
<p>The roundup for the released version of 8.1 is available in <a href="https://airsource.co.uk/blog/2014/10/21/nsurlcache-ios8-broken-3/">Part 3</a>.</p>
<p>Apple have already experienced one PR disaster with the iOS 8.0.1 release which <a href="http://techcrunch.com/2014/09/24/apples-ios-8-0-1-update-says-healthkit-apps-can-now-come-to-the-app-store/">broke cell phone operation for some users</a>. They may be on their way to another problem - less serious this time, but still significant.</p>
<p><a href="https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/index.html">NSURLCache</a> has been around since iOS 2, and <a href="http://petersteinberger.com/blog/2012/nsurlcache-uses-a-disk-cache-as-of-ios5/">using a disk cache since iOS 5 (really using it, not just saying that it did)</a>. As you'd expect, this means that if you open up an NSURLConnection, it will cache responses, take account of <a href="http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/">Cache-control headers</a>, and purge old cached responses as needed to keep the cache size within <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLCache/diskCapacity">specified limits</a>.</p>
<p>Unfortunately, this is broken on iOS 8.0. There are several complaints circulating around about NSURLCache <a href="http://stackoverflow.com/questions/25964973/did-ios-8-break-nsurlcache">not working properly with NSURLSession</a> or <a href="http://stackoverflow.com/questions/25853470/shared-nsurlcache-and-uiwebview-on-ios-8">UIWebView</a>. However, that is not the topic of this article. It does work with NSURLConnection - but it never purges the cache. </p>
<p>Yes, that's right - the cache size grows indefinitely. Moreover, the reported cache size provided by <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLCache/currentDiskUsage">currentDiskUsage</a> is way too low.</p>
<p>I ran a simple test <a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">test suite presented in Part 2</a> on iOS 7.1.2, 8.0, 8.0.2, and 8.1 beta. My test used a single NSURLConnection, set up to fetch a PNG image from a locally hosted server. The server did not specify any Cache-control headers. I initialised an NSURLCache with:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// Set up a 1MB RAM, 10MB disk cache in the default location (Library/Caches/com.whatever.myapp)</span>
<span class="w"> </span><span class="bp">NSURLCache</span><span class="w"> </span><span class="o">*</span><span class="n">cache</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="bp">NSURLCache</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithMemoryCapacity</span><span class="o">:</span><span class="mi">1000000</span><span class="w"> </span><span class="n">diskCapacity</span><span class="o">:</span><span class="mi">10000000</span><span class="w"> </span><span class="n">diskPath</span><span class="o">:</span><span class="nb">nil</span><span class="p">];</span>
<span class="w"> </span><span class="p">[</span><span class="bp">NSURLCache</span><span class="w"> </span><span class="n">setSharedURLCache</span><span class="o">:</span><span class="n">cache</span><span class="p">];</span>
</code></pre></div>
<p>I then fetched an image, with the URL used to fetch the image changing each time so that every response would be cached. This ensured I would rapidly reach the 10MB disk limit. I also continually polled the number of files in the fsCachedData subdirectory of the cache, in order to determine when the operating system started deleting files.</p>
<p>iOS 7.1.2 reported a plausible disk usage for the cache at each stage. It was slightly incorrect (it excluded the write-ahead log file) but at least it was consistent with the amount of data stored on the file system. Once the disk capacity was reached, the operating system started deleting files from fsCachedData. In short, the cache worked properly.</p>
<p>However on iOS 8.0, and 8.0.2, the cache size grew indefinitely. The reported disk usage of the cache as provided by the property currentDiskCapacity was far below the true usage, as verified both by using <a href="http://www.macroplant.com/iexplorer/?gclid=CN6y3r20pcECFUPLtAodjTgA0w">iExplorer</a>, checking the files in fsCachedData, and viewing the usage report in the Settings app. The operating system never deleted any files. This corresponds with the bug report provided by one of the users of our <a href="https://itunes.apple.com/gb/app/the-early-edition-2/id471813327?mt=8">Early Edition 2 application</a> who complained that the app was using over a gigabyte of store on iOS 8!</p>
<p>It is clear that Apple changed some key aspects of caching on iOS 8 - just take a look at the <a href="https://developer.apple.com/library/ios/releasenotes/General/iOS80APIDiffs/frameworks/Foundation.html">API diffs for iOS 7.1 to 8.0</a>. NSURLCache gained some extra methods in the category NSURLSessionTaskAdditions, which rather suggests that the <a href="http://stackoverflow.com/questions/25964973/did-ios-8-break-nsurlcache">issues people have been experiencing with NSURLSessions</a> are related to this change. I checked the <a href="https://developer.apple.com/library/ios/releasenotes/General/iOS81APIDiffs/index.html">API diffs for iOS 8.0 to 8.1</a> but there were no relevant changes.</p>
<p><sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>However, on iOS 8.1 (both beta 1 and beta 2) the cache does appear to be working properly, <a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">with the exception of deleting individual cache items - see Part 2</a>. I am, however, extremely disappointed to see that the <sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup><a href="https://developer.apple.com/library/ios/releasenotes/Miscellaneous/RN-iOSSDK-8.1/index.html">Release Notes</a> make no mention of this issue, and I can only speculate as to why Apple might want to not draw attention to it. </p>
<p>The consequences of unlimited cache growth are, of course, that the device will inevitably run out of space. Since most apps do not provide a facility to clear the cache (an omission that <a href="http://www.airsource.co.uk">Airsource</a> have also made - I can assure our users that this oversight will not be repeated), this means that the only way to free up space is to backup the application data, uninstall it, and then reinstall it. I thought <a href="http://www.techradar.com/news/software/operating-systems/how-to-reinstall-windows-1053331">I had left that behind when I moved from a PC to a Mac</a>.</p>
<p>The longer Apple waits before releasing iOS 8.1, the less space users will have on their devices. iOS 8 has already <a href="http://daringfireball.net/2014/10/ios_8_storage_space">experienced reduced install rates due to its storage requirements</a>. There is a very real risk that some (many?) users will be unable to update their devices OTA without uninstalling apps first - and the apps they will have to uninstall will, by the nature of this bug, be the apps that they most frequently use. Not impressed, Tim, not impressed.</p>
<p><a href="https://airsource.co.uk/blog/2014/10/13/nsurlcache-ios8-broken-2/">Part 2, with source code and detailed results, is now available.</a></p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>Apple used to heavily restrict what developers could say about beta releases of iOS (i.e. you weren't allowed to say anything). However, this <a href="http://oleb.net/blog/2014/06/apple-lifted-beta-nda/">recently changed</a>, and technical information is fine. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>The Release Notes for the first beta are <a href="https://developer.apple.com/library/ios/releasenotes/General/RN-iOSSDK-8.0/index.html">available here</a>, cunningly hidden under an 8.0 URL. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>Keynote reactions2014-09-16T11:06:00+01:002014-09-16T11:06:00+01:00Nick Clareytag:airsource.co.uk,2014-09-16:/blog/2014/09/16/keynote-reactions-2014/<p>The dust has now settled after last week's Apple keynote. What were the most interesting announcements for consumers and developers? For me - Apple Pay and the Apple Watch.</p>
<p>The dust has now settled after last week's Apple keynote. What were the most interesting announcements for consumers and developers? For me - Apple Pay and the Apple Watch.</p>
<h1>Apple Pay</h1>
<p>One of the key things Apple announced was the arrival, at long last, of an NFC-based payment solution. Once again, Apple stepped back and rethought the most effective way a consumer might interact in the checkout till payment process.</p>
<p><img alt="Apple Pay" src="https://airsource.co.uk/blog/images/ApplePay.png"></p>
<p>This is particularly relevant in the USA, where payment processing at the till is a drawn-out and painful process, involving little touch screens and silly pens, magstripes that don't quite scan, and the production of additional ID in order to verify signatures and ensure you aren't a scam artist. In the UK, where Chip-and-PIN based payments are now the way business is done, these systems are rightly seen as antiquated. We've even got NFC payments widely available at the till and built into many of our credit cards and debit cards.</p>
<p>But in adversity springs creativity; and it looks like Apple has nailed this one. From a consumer perspective, Apple has made getting your credit cards into the device nice and easy - take a photo of them and in they go. Payments are simple, and appear to require a modern device with TouchID to ensure that your details are kept safe from prying eyes. Apple has put the NFC antenna at the top of the device, so touching the scanner and having your thumb on the fingerprint sensor looks like a wonderfully smooth experience.</p>
<p>Financially, with what looks likely to be a 0.15% cut for transactions via Apple Pay this should be a tidy earner for Apple, and consumers, at least in the USA, will flock to it. How it plays elsewhere is less certain, as till payments already have somewhat less friction, but in the UK the ability to remove the PIN from transactions will help Apple make progress here.</p>
<p class="boxout">Fun fact - PayPal are feeling so threatened by this that they took out a <a href="http://pando.com/2014/09/15/paypal-drops-the-gloves-calling-out-apples-icloud-security-in-new-payments-ad/">full-page attack ad in the New York Times</a>, implying that your payments will be no safer than your selfies were.</p>
<h1>Apple Watch</h1>
<p>The other big announcement, of course, was the Apple watch. I was somewhat unenthusiastic about the last product Apple announced - the iPad - but this time they have my attention.</p>
<p><img alt="Apple Watch" src="https://airsource.co.uk/blog/images/AppleWatch.png"></p>
<p>Apple aren't the first to the table with connected watches. As I <a href="https://airsource.co.uk/blog/2014/07/15/watch-the-wearables/">discussed previously</a> there have been plenty of attempts by Android vendors and others to lay out their stands in this space and try to claim people's wrists. So far the most promising attempt has been the <a href="https://getpebble.com/">Pebble</a>, but that still looks too geeky, has too poor a screen and just doesn't inspire. The Pebble made too many compromises for the sake of battery life, and consumers have become increasingly accustomed to charging devices overnight, at least in western economies.</p>
<p>The two key thing which makes the Apple Watch for me:</p>
<ul>
<li>The <strong>haptic ("tactic") engine</strong> coupled with the interactions made possible with contacts. The idea of sending a "tap" to another person and receiving little pictures allows for a much more intimate form of communications which I think consumers will gravitate to. </li>
<li>The <strong>physical customisability</strong>, which will drive users to buy the watch and stock up on accessories. Given Apple's ability to make money out from it's stores, authorised (and unauthorised) accessories will create a huge secondary market. This will be beneficial both for the bottom line and create an entirely new ecosystem for third parties.</li>
</ul>
<p>I find the digital crown less convincing. It's a cute idea, retaining the traditional look of the watch and leveraging a familiar interface to create digital input, but watch crowns are usually used by removing the watch from the wrist. They are also an infrequent input in traditional watches, for winding or for changing the time when you get off the plane in a new timezone. Trying to shift the crown from a peripheral input method to a core interaction mechanism is quite a risky move.</p>
<p>Frustrating as well is the mandatory iPhone link. I think an opportunity was missed there to blow Android Wear out of the water by allowing the Apple Watch to be used with Android devices and by doing so attract people who don't already have an iDevice. It would have surprised analysts and excited people, attracted developers and opened an entirely new market for Apple. Maybe we'll see that in the future.</p>
<h1>Where to for developers?</h1>
<p>Apple Pay is mainly a benefit to consumers (and Apple themselves), but the Apple Watch is an exciting prospect for developers. Unlike the current iteration of Android Wear devices, I think consumers will buy the Apple Watch in large numbers. They will customise their watches with applications. Developers need to plan accordingly.</p>
<p>So if you've got an application for the Apple Watch in mind, don't hesitate to <a href="http://www.airsource.co.uk/contact.html">get in touch with us</a> - we'd love to help.</p>iWatch Preview2014-09-09T11:30:00+01:002014-09-09T11:30:00+01:00Nick Clareytag:airsource.co.uk,2014-09-09:/blog/2014/09/09/iwatch-preview/<p>The countdown has begun - so much so that <a href="http://www.apple.com/">Apple</a> have taken the unprecedented step of putting the timer to the announcement front-and-centre on their website.</p>
<p>The countdown has begun - so much so that <a href="http://www.apple.com/">Apple</a> have taken the unprecedented step of putting the timer to the announcement front-and-centre on their website.</p>
<p><img alt="Apple iWatch Announcement Countdown" src="https://airsource.co.uk/blog/images/apple-iwatch-announcement.png"></p>
<p>To keep momentum going, Apple is more-or-less obligated to put out a new product or a radically rethought one every few years. The last announcement of any major significance was probably the iPad, though arguably the re-engineered Mac Pro was also in the same category. It now seems almost certain that Apple will be announcing a wearable device today. How's it going to fare?</p>
<p>I stopped wearing a watch when I started rock climbing, and haven't regretted it's absence. My phone or a cut-off watch in my pocket have substituted just fine. There are still a lot of watches being sold, but the market hasn't really had any major shakeups since digital watches arrived several decades ago. Could Apple provide it?</p>
<p>Apple is entering a tricky market with the iWatch. They have a great brand, but despite the somewhat bold claim by Jonny Ive (who reportedly stated when interviewed that the Swiss were <em>ahem</em> "in trouble"), the Swiss have managed to lock away the high-end pretty successfully. Apple does so well in new product categories that I can't help but think it's going to be successful in this one as well. They're very good at the process of re-imagination, and watches are a category that is ripe for some destablising.</p>
<p>The wristwatch remains the only socially-acceptable form of jewellery for conservative middle-aged males, other than perhaps the wedding band. There is a low-end market for cheap disposable watches dominated by the Japanese. Lee Ka-Shing, the Hong Kong business magnate behind Hutchisson Whampoa famously still wears a $50 Citizen watch:</p>
<p><center><img alt="Li Ka-Shing won't be wearing it" src="https://airsource.co.uk/blog/images/li-ka-shing.jpg">
<em>AP Photo/Alamy</em>
</center></p>
<p>There is a high-end market for conspicuous consumers or for those who value craft, dominated by European manufacturers, especially the Swiss. In this market, spending $10-15k on a watch is considered a good starting budget, unless you're willing to buy second-hand.</p>
<p>Then there's a bit of a messy middle ground, again mainly owned by the Swiss where you can spend a few thousand dollars on a watch, where your choices are probably driven by aspirational advertising. This must be what Apple is after - the Tag Heuers and low-end Rolex folks. The watches have no real function to distinguish them from the low-end watch, they have some appearance of craft ("faux-craft") and they are purchased by the less discerning. The prices, facilities and image of the watch will be very much aimed at these sorts of people. But will people put up with poor battery life on a wristwatch? This is the key question.</p>
<p>So what do I expect?</p>
<h1>Medical and wellness integration</h1>
<p>Expect some nifty sensors that track blood pressure, pulse - perhaps even some form of glucose monitoring. Nike abandoned development of their Fuelband for a reason.</p>
<h1>"Gorgeous" display</h1>
<p>Expect a display that will inevitably be described in the keynote as "gorgeous". Sapphire glass, at least as an option. It will dazzle, but it won't blow the battery.</p>
<h1>Charging niftiness</h1>
<p>Expect a short battery life, only 3-4 days maximum, but more likely an overnight charge required. Wireless charging is a possibility, but consumers haven't shown much love for this. They'll have come up with something clever in this department.</p>
<h1>Brushed metallic body</h1>
<p>With options, like the iPhone 5s. They've got the mass manufacturing of solid blocks of aluminium all worked out now, so that will be put to good use.</p>
<h1>Bluetooth Low-Energy</h1>
<p>This will have to be there for the medical/welness stuff, but I also expect fully-fledged Bluetooth for communication. Apple may have rolled their own protocol for this, though I hope not. Will it integrate with Android?</p>
<h1>NFC</h1>
<p>If they're going to roll out payments then integrating this with the watch would make sense, much less hassle.</p>
<p>We'll be watching the announcement carefully here at Airsource - get the popcorn out, it's going to be a fun ride.</p>
<p><img alt="Popcorn baby." src="https://media.giphy.com/media/epxDzItQhxAzK/giphy.gif"></p>Markdown has been standardised – and the creator ain't happy2014-09-04T16:11:00+01:002014-09-04T16:11:00+01:00Nick Clareytag:airsource.co.uk,2014-09-04:/blog/2014/09/04/markdown-has-been-standardised-and-the-creator-aint-happy/<p><a href="http://daringfireball.net/projects/markdown/">Markdown</a> is a great invention. This blog uses it; I've used variations of it for all kinds of different things, and I love it. It's at once simple and powerful, which is the greatest achievement of anything that is well designed.</p>
<p><a href="http://daringfireball.net/projects/markdown/">Markdown</a> is a great invention. This blog uses it; I've used variations of it for all kinds of different things, and I love it. It's at once simple and powerful, which is the greatest achievement of anything that is well designed.</p>
<p>Our wiki, <a href="http://www.atlassian.com/confluence">Confluence</a> originally used a Markdown-like language which was insanely popular, but they changed it out for a WYSIWYG mode. I'm still on the lookout for a great wiki that uses Markdown.</p>
<p>Markdown was originally invented by <a href="http://daringfireball.net/">John Gruber</a>. His was the idea and his was the first implementation. A long-time Apple fan, his "Daring Fireball" blog is a must-read for up-to-date information and opinion on Apple products, and John's love of design can be seen in the original expression of Markdown.</p>
<p>The problem with Markdown, however, is that as it has gained popularity it has fragmented. Quite badly. Unfortunately the Markdown that you write isn't interpreted in quite the same way by different implementations, which means you end up with a lot of incompatibilities. It means content sharing can be limited and this is not a good thing.</p>
<p>However, some enterprising gents from industry luminaries such as <a href="http://www.github.com/">GitHub</a>, <a href="http://www.meteor.com/">Meteor</a> and <a href="http://www.stackexchange.com/">Stack Exchange</a> got together and decided to produce a formal specification and reference implementation. <a href="http://blog.codinghorror.com/standard-flavored-markdown/">Jeff Atwood announced it yesterday</a>, and it is called (appropriately enough) <a href="http://www.standardmarkdown.com/">"Standard Markdown"</a>.</p>
<p>But goodness me, John Gruber was seriously unimpressed. He owns the Markdown twitter account:</p>
<p><img alt=""Standard Markdown is neither"" src="https://airsource.co.uk/blog/images/standard-markdown.png"></p>
<p>When pressed to explain himself about why he thought it was such a bad idea:</p>
<p><img alt=""Success of Markdown is due to lack of standard"" src="https://airsource.co.uk/blog/images/markdown-no-standard.png"></p>
<p>I suppose you can imagine Markdown being like English. Unlike French or German or other languages, there is no "institute" that controls English. It has flourished into a genuine Lingua Franca partially because it has been uncontrolled, partially due to English colonialism and partially due to convienence. It could have been French, or Esperanto, or something else. But it's not, it's English. So Jeff's argument is that for Markdown to be truly successful it has to be outside the realm of standards.</p>
<p>The problem with imagining Markdown as English is that computers don't do ambiguity, and English is terribly ambiguous. Markdown has more in common with TCP or PPP or any number of other protocols and formats that have been laboriously worked out through the RFC process (used for the internet protocols to standardise implementations). Through that process and high-quality reference implementations (such as the BSD Network Stack), we have the internet we see today.</p>
<p>So I disagree with John Gruber. There may be a coming spat about the use of the word "Markdown", but frankly if I were him I would wish it well and accept that the inevitable outcome of your format becoming ubiquitous is that it ceases to be the expression of your individual creativity and turns into community property. Your baby needs to grow up. Thanks for Markdown, John. Let Standard Markdown flourish.</p>
<p>Header image from the brilliant <a href="http://xkcd.com/927/">xkcd</a></p>iCloud photo leak and product design2014-09-02T12:18:00+01:002014-09-02T12:18:00+01:00Nick Clareytag:airsource.co.uk,2014-09-02:/blog/2014/09/02/celeb-photo-leak-and-product-design/<p>Unless you've been hiding under a rock over the last few days, you will have seen the celebrity photo leaking scandal.</p>
<p>Unless you've been hiding under a rock over the last few days, you will have seen the celebrity photo leaking scandal.</p>
<p>Hackers, most likely using a brute force attack on Apple's "Find My iPhone" iCloud service, managed to compromise a large number of celebrity accounts, and consequently download their photo streams and backups. Kirsten Dunst has left us in no doubt who she blames for the whole affair:</p>
<p><img alt="Kirsten Dunst's opinion of iCloud" src="https://airsource.co.uk/blog/images/kirstendunst.png"></p>
<p>Unfortunately, your desirability as a female celebrity is mostly driven by your appearance. Hackers being generally young and male means that your private intimate pictures are therefore a particularly inviting target. It's worth stating this fact clearly, to remove any doubt - anything you put on a remote server may some day be downloaded by someone who doesn't own it. Before you upload something anywhere, think about the consequences if someone who wasn't you gets hold of it.</p>
<p>What is most interesting about this saga, however, is the role played by product design. Apple has struggled to attract people to using their network services, despite experiencing phenomenal success in device sales. This has resulted in a perceived "competence gap" - where Google can be trusted with network services, Apple can't be.</p>
<p>We've no idea about the conversation that happen behind closed doors at Apple, but I can imagine that the iCloud team has been under a lot of pressure to increase adoption. Apple want to own the user, from the devices they use to where their data is stored. The pressure is on to pull more people into a service which internally is seen as strategically vital. Combine this pressure with Apple's skill in User Experience (UX) design, and users are given a seamless "on-boarding" experience when they start using their Apple devices.</p>
<p>After adding your Apple credentials, a single dialog is displayed to the user with potentially far-reaching consequences. Note that the user is "sold" the benefits of the service with no hint about the potential consequences if their account is in any way compromised. In the interests of simplicity, the description of what actually happens when the service is activated are left a bit vague and wooly.</p>
<p><img alt="Initial iCloud dialog" src="https://airsource.co.uk/blog/images/iCloud-dialog.png"></p>
<p>Under the hood, however, your iCloud account is immediately activated to synchronise all of your photos, documents and data. You can see this from the default iCloud settings on a clean device:</p>
<p><img alt="Default iCloud settings" src="https://airsource.co.uk/blog/images/iCloud-default-settings.png"></p>
<p>One tap, sold to the user as a wonderful and practical convenience (who wants to have to manually copy photos backwards and forwards from their Mac or iPad?), means that any photo you have taken on your phone is immediately uploaded to a server, somewhere.</p>
<p>The server team who decided to leave out the brute-forcing check on the "Find my iPhone" service will be facing some stiff criticism internally. Heads may well roll. But it's important that we accept several inevitabilities in system design:</p>
<ul>
<li>Users will choose bad passwords</li>
<li>Users will upload things they would rather the entire world didn't see</li>
<li>Servers will get compromised</li>
</ul>
<p>Bearing these things in mind, the people who should face censure here are not the engineers who forgot a brute force password check. The criticism should be levelled at Product Managers who, in the interests of encouraging uptake of the service, sold it to users as a convenience without ensuring they were warned of the potential damage if the service was compromised.</p>Bound services and retained fragments2014-08-29T14:48:00+01:002014-08-29T14:48:00+01:00Sam Duketag:airsource.co.uk,2014-08-29:/blog/2014/08/29/android-bound-services/<p>Getting background tasks right is important. We want our UIs to remain responsive while the background task does its thing. Here I'm going to focus on Android's 'bound services' and a neat pattern for getting background work done on some shared resource, such as an internet connection and updating UI …</p><p>Getting background tasks right is important. We want our UIs to remain responsive while the background task does its thing. Here I'm going to focus on Android's 'bound services' and a neat pattern for getting background work done on some shared resource, such as an internet connection and updating UI and/or model components.</p>
<h1>Motivation</h1>
<p>Let's suppose we want our app to have an activity that can fetch some data from the internet when the user clicks a button. The internet request will likely take some time so we have some background task to perform.</p>
<p>The obvious way to get background work done on Android is to use an <a href="http://developer.android.com/reference/android/os/AsyncTask.html">AsyncTask</a>. So we go ahead and subclass AsyncTask, create the code to make a network query and then in <a href="http://developer.android.com/reference/android/os/AsyncTask.html#onPostExecute(Result)"><code>onPostExecute()</code></a> we use the result to update the UI. Most likely we made the AsyncTask some nested class of our activity and it looks something like this:</p>
<div class="highlight"><pre><span></span><code><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MainActivity</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Activity</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w"> </span><span class="nd">@Override</span>
<span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onCreate</span><span class="p">(</span><span class="n">Bundle</span><span class="w"> </span><span class="n">savedInstanceState</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">super</span><span class="p">.</span><span class="na">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">);</span>
<span class="w"> </span><span class="n">setContentView</span><span class="p">(</span><span class="n">R</span><span class="p">.</span><span class="na">layout</span><span class="p">.</span><span class="na">activity_main</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onClick</span><span class="p">(</span><span class="n">View</span><span class="w"> </span><span class="n">v</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Start task</span>
<span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">MyAsyncTask</span><span class="p">().</span><span class="na">execute</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MyAsyncTask</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">AsyncTask</span><span class="o"><</span><span class="n">Void</span><span class="p">,</span><span class="w"> </span><span class="n">Void</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="o">></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Override</span>
<span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">doInBackground</span><span class="p">(</span><span class="n">Void</span><span class="p">...</span><span class="w"> </span><span class="n">params</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Do work and make requests over the network</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">result</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="nd">@Override</span>
<span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onPostExecute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">//Update the UI, here we </span>
<span class="w"> </span><span class="n">TextView</span><span class="w"> </span><span class="n">textView</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">TextView</span><span class="p">)</span><span class="n">findViewById</span><span class="p">(</span><span class="n">R</span><span class="p">.</span><span class="na">id</span><span class="p">.</span><span class="na">text</span><span class="p">);</span>
<span class="w"> </span><span class="n">textView</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Got result: "</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">result</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>By doing this we create an implicit reference to our activity when we make the task. Which leads to...</p>
<h5>First problem! What about config changes?</h5>
<p>The problem with this solution is when we rotate the phone, the current activity instance is <strong>destroyed</strong> and a new one created, leaving the AsyncTask holding onto a dead object. This causes results to not be delivered to our UI. In fact, this will happen for any config changes that may happen to our activity (<a href="http://developer.android.com/reference/android/R.attr.html#configChanges">and there are a few</a>). This problem is outlined in the <a href="http://developer.android.com/guide/components/processes-and-threads.html#WorkerThreads">developer docs</a>, but often overlooked:</p>
<p><img alt="Caution: Another problem you might encounter when using a worker thread is unexpected restarts in your activity due to a runtime configuration change (such as when the user changes the screen orientation), which may destroy your worker thread. To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application." src="https://airsource.co.uk/blog/images/config-warning.png"></p>
<p>If you want to read more about this problem, here's another <a href="http://simonvt.net/2014/04/17/asynctask-is-bad-and-you-should-feel-bad/">blog post</a>.</p>
<h5>Second problem</h5>
<p>Now, let's say we also have another background task that is currently syncing the users data at the same time as we want to make our quick network fetch for the user. This may be ongoing for some time and hog the network connection and so seriously slow down the experience for the user. If your interaction is more timing-critical this becomes a real problem. There is no way to prioritise the button push over the ongoing sync.</p>
<p><strong>So what we really need is for some way to do background work on a shared resource (the internet connection) and have the results delivered to the UI reliably.</strong></p>
<h1>Bad solutions</h1>
<ul>
<li><em>Using singletons to hold the asynctask.</em> A singleton is probably overkill for this problem. You would also difficulty to tracking what currently needs to use the network in order to resolve conflicts.</li>
<li><em>Disabling config changes.</em> This is a common solution to the rotation problem as it prevents the activity being destroyed but is actually bad practice and can cause trouble later. (see Romain Guy's comment on this <a href="http://stackoverflow.com/a/2620949/1137254">answer</a>)</li>
<li><em>Making a new AsyncTask after each rotation.</em> This solution can actually be preferable. See Romain's <a href="http://stackoverflow.com/a/2624569/1137254">actual answer</a> to the question linked above.</li>
<li><em>Permanent service.</em> We could use a regular service and leave it running whilever our app is active (or longer?). But this is a very poor memory management - <a href="https://developer.android.com/training/articles/memory.html#Services">we should use services sparingly</a> - and trying to improve this by managing the service lifecycle ourself seems painful.</li>
</ul>
<h1>Rules and aims</h1>
<p>So for our solution we can set out the following targets:</p>
<ul>
<li>Don't disable config changes.</li>
<li>Allow multiple application components to access the resource at once. Possibly adding priorities to this to favour some operations over others.</li>
<li>Be a good citizen. Don't just use a permanent service to host background work. We probably need some kind of service to allow different application components to access the same resource but we shouldn't leave that running forever more.</li>
</ul>
<p><strong>Now let's introduce a couple of techniques to solve this:</strong></p>
<h2>Retained Fragments</h2>
<p>These are our solution to the first problem. As of Android API level 13, retained fragments are the new <a href="http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject">recommended alternative</a> to the now deprecated <code>onRetainNonConfigurationInstance()</code>. A retained fragment is not detroyed during a configuration change and can be reconnected to an activity after the change in <code>onCreate()</code>. Declaring and using one is simple:</p>
<p>In the fragment:</p>
<div class="highlight"><pre><span></span><code><span class="c1">// this method is only called once for this fragment</span>
<span class="nd">@Override</span>
<span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onCreate</span><span class="p">(</span><span class="n">Bundle</span><span class="w"> </span><span class="n">savedInstanceState</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="kd">super</span><span class="p">.</span><span class="na">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// retain this fragment</span>
<span class="w"> </span><span class="n">setRetainInstance</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>In the activity:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@Override</span>
<span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onCreate</span><span class="p">(</span><span class="n">Bundle</span><span class="w"> </span><span class="n">savedInstanceState</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">super</span><span class="p">.</span><span class="na">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">);</span>
<span class="w"> </span><span class="n">setContentView</span><span class="p">(</span><span class="n">R</span><span class="p">.</span><span class="na">layout</span><span class="p">.</span><span class="na">main</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// find the retained fragment on activity restarts</span>
<span class="w"> </span><span class="n">FragmentManager</span><span class="w"> </span><span class="n">fm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">getFragmentManager</span><span class="p">();</span>
<span class="w"> </span><span class="n">dataFragment</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">DataFragment</span><span class="p">)</span><span class="w"> </span><span class="n">fm</span><span class="p">.</span><span class="na">findFragmentByTag</span><span class="p">(</span><span class="err">“</span><span class="n">data</span><span class="err">”</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// create the fragment and data the first time</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">dataFragment</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// add the fragment</span>
<span class="w"> </span><span class="n">dataFragment</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">DataFragment</span><span class="p">();</span>
<span class="w"> </span><span class="n">fm</span><span class="p">.</span><span class="na">beginTransaction</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="n">dataFragment</span><span class="p">,</span><span class="w"> </span><span class="err">“</span><span class="n">data</span><span class="err">”</span><span class="p">).</span><span class="na">commit</span><span class="p">();</span>
<span class="w"> </span><span class="c1">// load the data from the web</span>
<span class="w"> </span><span class="n">dataFragment</span><span class="p">.</span><span class="na">setData</span><span class="p">(</span><span class="n">loadMyData</span><span class="p">());</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// the data is available in dataFragment.getData()))</span>
<span class="w"> </span><span class="p">...</span>
<span class="p">}</span>
</code></pre></div>
<p>So, by storing objects related to ongoing work in the fragment we can keep stuff connected to the Activity across config changes and deliver our results to the UI, but we are still no closer to being able to prioritise internet connections in a central place without using a singleton or firing up a permanent service.</p>
<h2>Enter: Bound services</h2>
<p>The solution to problem #2, the <a href="http://developer.android.com/guide/components/bound-services.html">docs</a> sum these up nicely:</p>
<blockquote>
<p>A bound service allows components (such as activities) to bind to the service, send requests, receive responses, and even perform interprocess communication (IPC). A bound service typically lives only while it serves another application component and does not run in the background indefinitely.</p>
<p>...</p>
<p>A client can bind to the service by calling bindService(). When it does, it must provide an implementation of ServiceConnection, which monitors the connection with the service.</p>
<p>...</p>
<p>When the last client unbinds from the service, the system destroys the service</p>
</blockquote>
<p>The really nice thing about this is each application component can strictly worry about whether <em>it</em> needs the service. Here, the an activity unbinds from the service after its request is finished without any consideration for whether another object may be using the service:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@Override</span>
<span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onRequestFinished</span><span class="p">()</span>
<span class="p">{</span>
<span class="w"> </span><span class="c1">//Our request is finished and we don't need the service any more.</span>
<span class="w"> </span><span class="n">getApplicationContext</span><span class="p">().</span><span class="na">unbindService</span><span class="p">(</span><span class="n">dataFragment</span><span class="p">.</span><span class="na">mConnection</span><span class="p">);</span>
<span class="w"> </span><span class="n">dataFragment</span><span class="p">.</span><span class="na">mConnection</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span>
<span class="w"> </span><span class="c1">//Update the UI</span>
<span class="w"> </span><span class="n">mQuickButton</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="w"> </span><span class="n">mQuickButton</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Quick Operation"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>This is the key to ensuring we keep a service alive only exactly as long as required.</p>
<h5>Bound services sound perfect, but we need to use them just right...</h5>
<p>Specific questions about their use arise (see my original StackOverflow question about this <a href="http://stackoverflow.com/questions/24312016/bind-to-service-from-new-context-for-configuration-changes-or-bind-from-app-cont">here</a>):</p>
<blockquote>
<p>"Should I bind from the app context or the activity context?"</p>
</blockquote>
<p>If we bind from the activity context, we would be left with the same problem we had in problem #1 - we would be leaking the activity after it's supposed to be destroyed. This may crash or even if we could still get the results delivered to the UI using retained fragments we'd likely be getting ourselves in trouble later. (It's worth having a read about context memory leaks <a href="http://android-developers.blogspot.co.uk/2009/01/avoiding-memory-leaks.html">here</a>)</p>
<blockquote>
<p>"But if I bind from the application context, can I bind from it multiple times in different places or do I need to reference count and work out when to unbind myself?"</p>
</blockquote>
<p>This is actually poorly documented and inspection of the Android source reveals that this it is safe (again, see my StackOverflow answer <a href="http://stackoverflow.com/a/24458955/1137254">here</a>) to bind several times from the application context without worrying who else may have bound. It's key to keeping bound services easy to use.</p>
<h1>Solution and explanation</h1>
<p>Bringing this all together, I have created a <a href="https://github.com/samskiter/BoundServiceTest">test application</a> for using bound services.</p>
<p><img alt="Screenshot showing the test application with "Quick Operation" and a "Slow Operation" buttons" src="https://airsource.co.uk/blog/images/bound-service-ss.png"></p>
<p>This uses two methods of making requests on our bound service. - one short, high priority request that updates the UI, "Quick Operation", and one long running background task that the model performs, "Slow Operation".</p>
<p><img alt="A Diagram showing the structure of the bound service pattern" src="https://airsource.co.uk/blog/images/bound-service-diagram.jpg"></p>
<p>Our BoundService lies at the heart of the work. Via their connection (and binder), components can get access to the service and run their requests:</p>
<div class="highlight"><pre><span></span><code><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onServiceConnected</span><span class="p">(</span><span class="n">ComponentName</span><span class="w"> </span><span class="n">className</span><span class="p">,</span><span class="w"> </span><span class="n">IBinder</span><span class="w"> </span><span class="n">binder</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">mRequest</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// We've bound to LocalService, cast the IBinder and get BoundService instance</span>
<span class="w"> </span><span class="n">BoundService</span><span class="p">.</span><span class="na">LocalBinder</span><span class="w"> </span><span class="n">localBinderinder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">BoundService</span><span class="p">.</span><span class="na">LocalBinder</span><span class="p">)</span><span class="w"> </span><span class="n">binder</span><span class="p">;</span>
<span class="w"> </span><span class="n">BoundService</span><span class="w"> </span><span class="n">service</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">localBinderinder</span><span class="p">.</span><span class="na">getService</span><span class="p">();</span>
<span class="w"> </span><span class="n">service</span><span class="p">.</span><span class="na">makeRequest</span><span class="p">(</span><span class="n">mRequest</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>A retained fragment is set to be the listener to the results of requests:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@Override</span>
<span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onRequestFinished</span><span class="p">()</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">getActivity</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">((</span><span class="n">MainActivity</span><span class="p">)</span><span class="n">getActivity</span><span class="p">()).</span><span class="na">onRequestFinished</span><span class="p">();</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">else</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">cachedResult</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>If the activity is currently not attached to fragment (as could happen during a rotation), we cache the result in the fragment until it reattaches.</p>
<div class="highlight"><pre><span></span><code><span class="nd">@Override</span>
<span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onAttach</span><span class="p">(</span><span class="n">Activity</span><span class="w"> </span><span class="n">activity</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">cachedResult</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">((</span><span class="n">MainActivity</span><span class="p">)</span><span class="w"> </span><span class="n">activity</span><span class="p">).</span><span class="na">onRequestFinished</span><span class="p">();</span>
<span class="w"> </span><span class="n">cachedResult</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">super</span><span class="p">.</span><span class="na">onAttach</span><span class="p">(</span><span class="n">activity</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>The service connection and request mechanism is exactly the same for model objects, but they can listen for results themselves. The service maintains a simple priority-ordered queue of requests and stops ongoing requests if those with higher priority arrive:</p>
<div class="highlight"><pre><span></span><code><span class="kd">private</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">cancelOnGoingIfNecessary</span><span class="p">()</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">mOnGoingRequest</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">mQueue</span><span class="p">.</span><span class="na">peek</span><span class="p">().</span><span class="na">mPriority</span><span class="p">.</span><span class="na">ordinal</span><span class="p">()</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">mOnGoingRequest</span><span class="p">.</span><span class="na">mPriority</span><span class="p">.</span><span class="na">ordinal</span><span class="p">())</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">mOnGoingTask</span><span class="p">.</span><span class="na">cancel</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h1>When is this appropriate</h1>
<p>When you are working with a shared, scarce resource (internet, bluetooth connection, lengthy processing pipelines) and want to make sure your requests are guaranteed to make it to the UI. Even if you aren't using a shared resource and don't require a bound service, you can still revert to using an AsyncTask with a retained fragment. Ultimately, there are a couple of tools here you can put to good use in the right circumstances.</p>
<h1>Other solutions</h1>
<p>Loaders and loader manager. These function similarly to a retained fragment. They can be created and reconnected to across activity config changes. These are more appropriate for lists with cursors. Specifically, a <code>Loader</code> should 'monitor the source of their data and deliver new results when the contents change'. For more information, check out the <a href="http://developer.android.com/guide/components/loaders.html">Loaders</a> dev docs.</p>Default value for kCGImageSource ShouldCache2014-08-28T16:00:00+01:002014-08-28T16:00:00+01:00Ben Blaukopftag:airsource.co.uk,2014-08-28:/blog/2014/08/28/coregraphics-default-caching-value/<p>There's an interesting conflict between the iOS docs and the iOS headers.</p>
<p>There's an interesting conflict between the iOS docs and the iOS headers.</p>
<p>The docs say:
<img alt="kCGImageSourceShouldCache-docs" src="https://airsource.co.uk/blog/images/kCGImageSourceShouldCache-docs.png"></p>
<p>The header, however, says</p>
<div class="highlight"><pre><span></span><code><span class="o">/*</span><span class="w"> </span><span class="n">Specifies</span><span class="w"> </span><span class="n">whether</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">image</span><span class="w"> </span><span class="n">should</span><span class="w"> </span><span class="n">be</span><span class="w"> </span><span class="n">cached</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">decoded</span><span class="w"> </span><span class="n">form</span><span class="o">.</span><span class="w"> </span><span class="n">The</span>
<span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">key</span><span class="w"> </span><span class="n">must</span><span class="w"> </span><span class="n">be</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">CFBooleanRef</span><span class="o">.</span>
<span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">kCFBooleanFalse</span><span class="w"> </span><span class="n">indicates</span><span class="w"> </span><span class="n">no</span><span class="w"> </span><span class="n">caching</span><span class="p">,</span><span class="w"> </span><span class="n">kCFBooleanTrue</span><span class="w"> </span><span class="n">indicates</span><span class="w"> </span><span class="n">caching</span><span class="o">.</span>
<span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">For</span><span class="w"> </span><span class="mi">64</span><span class="o">-</span><span class="n">bit</span><span class="w"> </span><span class="n">architectures</span><span class="p">,</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">default</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">kCFBooleanTrue</span><span class="p">,</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="mi">32</span><span class="o">-</span><span class="n">bit</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">default</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">kCFBooleanFalse</span><span class="o">.</span>
<span class="w"> </span><span class="o">*/</span>
<span class="n">IMAGEIO_EXTERN</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">CFStringRef</span><span class="w"> </span><span class="n">kCGImageSourceShouldCache</span><span class="w"> </span><span class="n">IMAGEIO_AVAILABLE_STARTING</span><span class="p">(</span><span class="n">__MAC_10_4</span><span class="p">,</span><span class="w"> </span><span class="n">__IPHONE_4_0</span><span class="p">);</span>
</code></pre></div>
<p>To summarise - the docs say that the default value is True on 32 bit, false on 64 bit. The header says exactly the opposite - False on 32 bit, True on 64 bit.</p>
<p>To settle the disagreement, I wrote a short test where I checked the memory usage before and after calling CGImageSourceCreateThumbnailAtIndex. I modified the value of kCGImageSourceShouldCache passed in as an option, and I ran the test on both 32 bit and 64 bit devices, all on iOS 7.1.1 or 7.1.2. </p>
<p>This demonstrated a clear increase in memory usage (I used an 80MB image) when kCGImageSourceShouldCache was True.</p>
<p>I then ran the test again, but without specifying the value of kCGImageSourceShouldCache - so it picked up the default.</p>
<p>The results were clear - the default value of kCGImageSourceShouldCache on iOS 7, on both 32-bit and 64-bit systems, is False. Both the docs and the header are wrong.</p>Playing with Meteor: A modern framework for rapid prototyping2014-07-22T11:30:00+01:002014-07-22T11:30:00+01:00James Urentag:airsource.co.uk,2014-07-22:/blog/2014/07/22/playing-with-meteor-a-modern-framework-for-rapid-prototyping/<p>Developer James Uren took two weeks out to work with Ravensbourne and students from the PRATT institute in New York. Together they built a prototype interactive installation for the National Maritime Museum with a new JavaScript framework - Meteor.</p><p>Many modern mobile applications are actually just websites that the app displays in a simplified browser window. This means that while software can be easily updated and is immediately cross-platform, access to device hardware is limited, and of course an internet connection is required to use the app.</p>
<p>Traditionally the screens for these web apps would be written with HTML, CSS and JavaScript, and the dynamic data provided by server-side technologies. When a page is loaded, data is pulled from a database and the page’s HTML is created from this. Only being able to load new data when a page is refreshed is a huge limitation, and increasingly websites employ technologies such as AJAX to mitigate this problem.</p>
<p><img alt="Developing the prototype" src="http://1.bp.blogspot.com/-FgEhCCwP28k/U8WKljuIbwI/AAAAAAAAAMU/Wey47FNJ-lc/s1600/IMG_1170.JPG"></p>
<p>Over the last two weeks I have been involved in a project with <a href="http://www.ravensbourne.ac.uk">Ravensbourne</a>, New York design institute <a href="https://www.pratt.edu">PRATT</a> and the <a href="http://www.rmg.co.uk/national-maritime-museum">National Maritime Museum</a> to create a prototype interactive installation. I chose to use a new web framework — <a href="https://www.meteor.com">Meteor</a> — that addresses the problems of traditional web development by allowing you to build both the client and server sides of a web application in JavaScript. All the data synchronisation is managed by Meteor, providing ‘reactive’ data on multiple screens. When the data in the database changes, all the client pages are automatically updated.</p>
<p>This is a very powerful tool, particularly for rapid prototyping. In just four days of coding the students — with very little prior software development experience — were able to build and deploy a working prototype of a treasure hunt game, and wrap this website in an iPhone app. You can <a href="http://timeandtides.meteor.com">play the game here</a> (click on ‘Tom Binnacle’ to play), see <a href="http://ravepratt2014.blogspot.co.uk">how it was made</a> and <a href="https://github.com/jamesuren/ravepratt">check out the code</a>.</p>
<p>Meteor’s <a href="http://docs.meteor.com">documentation</a> and <a href="https://www.meteor.com/examples/leaderboard">examples</a> are excellent and the community is very helpful. There is a <a href="http://www.meetup.com/Meteor-London/">Meetup in London</a> where beta testers meet every month to share demos and compare notes.</p>
<p>Frameworks like Meteor are the future of dynamic web applications, but there is still a long way to go for mobile apps. You can’t beat a native mobile app for performance and flexibility, but the iOS and Android APIs could learn a lot from the reactive data model that Meteor provides.</p>Bermuda for Android Wear™2014-07-18T16:01:49+01:002014-07-18T16:01:49+01:00Nick Clareytag:airsource.co.uk,2014-07-18:/blog/2014/07/18/bermuda-watch-face-for-android-wear/<p>Today Airsource announced the release of <a href="https://play.google.com/store/apps/details?id=uk.co.airsource.android.bermuda">Bermuda</a>, an elegant, minimalist watch face for your Android Wear™ device.</p>
<p>Today Airsource announced the release of <a href="https://play.google.com/store/apps/details?id=uk.co.airsource.android.bermuda">Bermuda</a>, an elegant, minimalist watch face for your Android Wear™ device.</p>
<p><img alt="Bermuda Promo Graphic" src="https://airsource.co.uk/blog/images/Bermuda-blog-graphic.png"></p>
<p>Carefully crafted to give you access to the time without cluttering your screen with unnecessary information, Bermuda is designed to complement your timepiece perfectly. Offering a choice of 8 different face colours, Bermuda is available from today on the <a href="https://play.google.com/store/apps/details?id=uk.co.airsource.android.bermuda">Android Play Store</a>.</p>
<p>We'd love to hear your feedback - let us know from inside the app on your smartphone if there's something you're dying to see included.</p>Watch the wearables!2014-07-15T16:52:41+01:002014-07-15T16:52:41+01:00Nick Clareytag:airsource.co.uk,2014-07-15:/blog/2014/07/15/watch-the-wearables/<p>In preparation for the expected launch of the iWatch in October 2014, other manufacturers are moving quickly to release their so-called "wearables" to get in early.</p>
<p>In preparation for the expected launch of the iWatch in October 2014, other manufacturers are moving quickly to release their so-called "wearables" to get in early.</p>
<h1>Where it all began: The Pebble</h1>
<p><img alt="Pebble Steel" class="img-centre" src="https://airsource.co.uk/blog/images/PebbleSteel.png"></p>
<p>This wearables rush really started with the "Pebble", which raised 100 times the originally requested $100,000, making it one of the most successful projects ever on Kickstarter. The Pebble is now in it's second generation, featuring a steel-banded version selling for $150.</p>
<p>It's a relatively simple watch - it can hold 8 applications, it features an ePaper screen (just like your Kindle) and the battery lasts for about a week. There are about 1000 applications available for the Pebble, from watchfaces to fitness and navigation apps. You install apps via your smartphone, using an "pebble appstore" app. Apps can be free or paid for.</p>
<p>Key problems? The ePaper screen, although it's very battery efficient, isn't great for applications. ePaper has a slow refresh rate and this places severe limitations on applications. User input options are also very limited, with four hard buttons on the device.</p>
<h1>The next generation: Samsung's Tizen watches</h1>
<p><img alt="Gear 2" class="img-centre" src="https://airsource.co.uk/blog/images/Gear2.png"></p>
<p>Following the Pebble, and on rumours that Apple was working on a wearable device, Samsung has released several watches.</p>
<p>The <a href="http://www.samsung.com/uk/consumer/mobile-devices/wearables/">"Gear"</a> series has now also moved through several iterations - the Gear and the Gear 2, as well as the fitbit / fuelband inspired Gear Fit. If you want to develop and app for these devices, you'll be writing it using Samsung's "Tizen" environment.</p>
<p>These watches are designed to be more appealing than the Pebble, usually sporting a touchscreen and a colour screen, often AMOLED. Battery life is considerably worse - what you'd expect with these kinds of screens - usually maxing out at a day.</p>
<p>Key problems? You're forced to develop in "Yet Another OS" - quite annoying. Battery life is generally poor. Apps are installed and managed via a "companion app", much like the Pebble, however the app will only run on Android devices - and even worse, only Samsung devices. This short-sighted move means that iOS users can't buy apps for Gear watches.</p>
<h1>And now: Android Wear devices</h1>
<p><img alt="LG G" class="img-centre" src="https://airsource.co.uk/blog/images/lg-g.png"></p>
<p>Where are we at today? At Google I/O, <a href="http://www.android.com/wear/">Android Wear</a> was announced, and with it two watches from the leading Android device manufacturers. From LG, the "G", and from Samsung the "Gear Live".</p>
<p>Unlike the Tizen models, Android is actually running on these devices. Expect a lot of custom ROMs (some have already emerged for the LG "G") and lots of "innovative designs" (the upcoming Moto 360 has - gasp - a circular watch face!).</p>
<p>Writing applications for these devices is easy, as it uses the same Android SDK you are accustomed to, although the APIs on offer are somewhat more limited. Distribution is through the Google Play Store. Applications can either be fully native or they can use standard notification APIs in order to send specific snippets of content to the device.</p>
<p>Key problems? Same problems as the existing Samsung Tizen watches with poor battery life and limited installation options. The big benefit over the Tizen watches is that you aren't limited to Samsung's smartphones to host installs, but you can use any Android device running 4.3 or later.</p>
<h1>What's required for success in building a smartwatch?</h1>
<ul>
<li>Don't depend on a single host operating system. Pebble has it right in letting people install smartwatch apps from iOS <em>or</em> Android.</li>
<li>Find a compromise on battery life and display. People want their watches to survive longer than a day, but they also want more zing than ePaper can offer.</li>
<li>Don't depend on hard buttons. People expect touch and will buy devices that offer it.</li>
</ul>
<h1>One more thing...</h1>
<p>The biggest surprise Apple might spring is to allow Android devices to act as hosts to the iWatch. This would fit into their practice of allowing some of their desktop applications (for example, iTunes) to run on Windows. Android customers would pay for a well designed Apple watch and this manoeuvre might wrong-foot Google, who would struggle to justify restricting access to Apple's host application on the Play Store.</p>A glimpse of Nokia's future at IQ20122012-09-11T10:41:00+01:002012-09-11T10:41:00+01:00Nick Clareytag:airsource.co.uk,2012-09-11:/blog/2012/09/11/a-glimpse-of-nokias-future-at-iq2012/<p>Qualcomm's <a href="http://www.iqevent.eu/">IQ2012 EU</a> event this year moved to the Kosmos Centre in Berlin. An annual one-day shindig designed to give a more regional feel to Qualcomm's roadmap presentation, it's well attended by operators, app developers and partner hopefuls.</p>
<p>Qualcomm's <a href="http://www.iqevent.eu/">IQ2012 EU</a> event this year moved to the Kosmos Centre in Berlin. An annual one-day shindig designed to give a more regional feel to Qualcomm's roadmap presentation, it's well attended by operators, app developers and partner hopefuls.</p>
<p><img alt="IQ2012 at the Kosmos Centre, Berlin" src="https://airsource.co.uk/blog/images/2012/09/iq2012_0013.jpg"></p>
<p>Steve Mollenkopf (President and COO) gave a good overview of the status of the mobile market and particularly how things were going for Qualcomm. In summary - very well. More chips, more devices and more innovative technologies that Qualcomm are integrating into their chip designs. Some useful demos, particularly of the <a href="http://androidandme.com/2012/08/smartphones-2/qualcomm-explains-the-system-of-tiers-for-snapdragon-s4/">next-generation SnapDragon processor</a> and of the <a href="http://www.qualcomm.com/solutions/augmented-reality">Augmented Reality Vuforia SDK</a>, showed that Qualcomm are not just good with <a href="http://en.wikipedia.org/wiki/VHDL">VHDL</a>.</p>
<p>The highlight, however, was a segment by Nokia's Jo Harlow (Exec. VP). A lot of questions persist about how Nokia will compete and whether it is worth app developers spending effort on redesign and reworking their apps to suit Windows Phone 8. Jo's presentation showed that there is still plenty of fight left in Nokia, with clever technology pieces such as <a href="http://www.nokia.com/global/products/wireless-charging/">wireless charging</a>, exchangeable covers and clever hardware/software tricks in video stabilisation. Of course, Nokia gets key billing because it has included the latest Snapdragon processor in the <a href="http://www.nokia.com/gb-en/products/phone/lumia920/">920 device</a> - which received a good plug from Jo.</p>
<p>Nokia still have an uphill battle, but with the key gadget purchasing season round the corner they are well placed to compete. Notable by their absence, though, are Apple. By fiat, no Apple devices are shown in any slideware, by anyone, in anything, despite the fact that most attendees have at least one, possibly two Apple devices in their possession. When Apple unleash their latest tomorrow, we'll see whether Nokia - or anyone else - are doing enough to convince consumers to choose them instead.</p>
<p><img alt="IQ 2012" src="https://airsource.co.uk/blog/images/2012/09/iq2012_003.jpg"></p>Return To Mobile World Congress2012-02-24T12:48:00+00:002012-02-24T12:48:00+00:00Nick Clareytag:airsource.co.uk,2012-02-24:/blog/2012/02/24/return-to-mobile-world-congress/<p>After a three year hiatus, I'll be visiting the <a href="http://www.mobileworldcongress.com/index.html">Mobile World Congress 2012</a> (MWC) from next Tuesday to see how the landscape has changed after the "app revolution".</p>
<p>MWC, the mobile industry's annual get-together, starts on Monday, February 27th. About 60,000 people will descend on Barcelona from all parts …</p><p>After a three year hiatus, I'll be visiting the <a href="http://www.mobileworldcongress.com/index.html">Mobile World Congress 2012</a> (MWC) from next Tuesday to see how the landscape has changed after the "app revolution".</p>
<p>MWC, the mobile industry's annual get-together, starts on Monday, February 27th. About 60,000 people will descend on Barcelona from all parts of the globe hoping to hawk their wares, find suppliers, establish new relationships or renew old ones. It's like a week of corporate speed dating.</p>
<p>For small developers, especially those who self-fund and lack the deep pockets necessary to set up a stand, MWC represents a yearly dilemma.</p>
<ul>
<li>Attending MWC can be expensive (see my <a href="https://airsource.co.uk/blog/2007/02/19/3gsm-for-100/">earlier post on avoiding extortion</a>).</li>
<li>It's a huge stage for a small developer so it's easy to blend into the background.</li>
<li>Even if you do get there, most people on stands are there to sell, not to be sold to. </li>
</ul>
<p>So what's the point in turning up?</p>
<p>When Airsource started 5 years ago, it was virtually impossible to make money selling software directly to consumers. The only hope you had was to establish a relationship with an operator or a handset manufacturer. There were so many people trying to do this that only the well-funded - with significant sales teams - could hope to endure the year-long sales cycle.</p>
<p>Today, app stores allow developers to find customers and sell to them without needing to engage with either operators or device manufacturers. What makes or breaks a device for a consumer is less about the whizzbang hardware features and more about how users can extend the experience of ownership by buying apps. The balance of power is shifting away from the few organisations who can license radio spectrum or build mass-market mobile phones and towards the developers who write the apps.</p>
<p>The last time I attended MWC was 2009. Apple's appstore was growing fast. The iPad did not exist. Small software companies were viewed with mild amusement and general skepticism.</p>
<p>Today I can tell my barber that my company writes apps and he understands me. Everybody seems to have ideas for apps. An explosion of creativity is taking place. Possibilities seem endless.</p>
<p>So I want to see how this new reality is changing things. Over the next week I'll be posting regularly as I investigate the effects on both the mobile behemoths and the little guys, Airsource included. </p>
<p>My freebie ticket this year is thanks to mobile device management company <a href="http://www.mformation.com/">MFormation</a> (via a giveaway at <a href="http://www.mobilemonday.org.uk/">Mobile Monday London</a>).</p>Optiscan QR Code® Scanner and Generator - version 2.0 released2012-02-01T17:00:00+00:002012-02-01T17:00:00+00:00Steve O'Connortag:airsource.co.uk,2012-02-01:/blog/2012/02/01/optiscan-qr-code-scanner-and-generator-version-2-0-released/<p>Today we <a href="http://itunes.apple.com/app/optiscan/id304099767?mt=8">released the latest revision of Optiscan to the App Store</a>. With an all-new look and some greatly enhanced features, this version is the king of QR Code scanners on your Apple device!</p><p>Today we <a href="http://itunes.apple.com/app/optiscan/id304099767?mt=8">released the latest revision of Optiscan to the App Store</a>. With an all-new look and some greatly enhanced features, this version is the king of QR Code scanners on your Apple device!</p>
<p><img alt="Create a QR Code" src="https://airsource.co.uk/blog/images/2012/02/IMG_0208-200x300.png" title="Create a QR Code - so many options!"></p>
<p><img alt="Optiscan 2.0 Scanning screen" src="https://airsource.co.uk/blog/images/2012/02/scan_english-200x300.png" title="Optiscan 2.0 Scanning screen"></p>
<p>What's new in 2.0:</p>
<ul>
<li><strong>No more tap to scan!</strong> Optiscan now launches straight into scan mode for ease of use.</li>
<li><strong>Icon and UI redesign.</strong> We thought it could do with a sprucing up, so we tweaked the look to be more up-to-date and utilitarian. What do you think?</li>
<li><strong>Enhanced scanning engine.</strong> With every release of Optiscan we tweak the scanning software more and more to cope with the plethora of designer codes and locations. After testing, we scan more QR Codes than any other app!</li>
<li><strong>Scan from clipboard.</strong><em>"I can't scan from my mobile browser!"</em>, we heard you cry. Well now you can!</li>
<li><strong>QR Codes for Events.</strong> We wanted to include this last time, but we just weren't happy enough with the way they where dealt with. Now we are!</li>
</ul>
<p>We hope you enjoy using this version of Optiscan. Show us what you've done with it and we'll pass it on!</p>Optiscan QR Code Scanner and Generator 1.9.3 update released to the iTunes App Store!2011-10-13T14:27:00+01:002011-10-13T14:27:00+01:00Steve O'Connortag:airsource.co.uk,2011-10-13:/blog/2011/10/13/optiscan-qr-code-scanner-and-generator-1-9-3-update-released-to-the-itunes-app-store/<p>Today we released the latest update for Optiscan, our QR Code scanner and generator, on the iTunes App Store.</p>
<p>Today we released the latest update for Optiscan, our QR Code scanner and generator, on the iTunes App Store.</p>
<p><img alt="Optiscan Create Screen" src="https://airsource.co.uk/blog/images/2011/10/IMG_1197.png" title="Optiscan 1.9.3 Create screen"></p>
<p>You've always been able to create a plethora of QR Code content types with Optiscan, but now we've made it even easier for you with a new menu of options. These include:</p>
<ul>
<li><strong>SMS</strong> Create a QR Code containing a message and the phone number to SMS it to - great for marketing!</li>
<li><strong>Location</strong> To our knowledge Optiscan is the <em>only</em> QR Code generator app that lets you pinpoint a map location <em>without</em> having to type the address in!</li>
<li><strong>Email</strong> Similar to the SMS option, setup an email message, subject line and recipients all in one QR Code</li>
<li><strong>Telephone</strong> Keep it simple - your phone number in a QR Code</li>
<li><strong>Wi-Fi </strong>Need to share your wi-fi details easily? Put them in a QR Code and pass it round!</li>
</ul>
<p>All these options have been added at the request of our users in addition to our standard <strong>Contact</strong> (with vCard or MeCard options), <strong>URL</strong> and <strong>Text</strong> note options. We hope you enjoy using Optiscan even more now to scan and create all those QR Codes!</p>
<p>Coming to an iPhone near you soon: <em>Optiscan 2.0: The Scanner Strikes Back!</em></p>A Summer At Airsource2011-09-23T16:05:00+01:002011-09-23T16:05:00+01:00ehughestag:airsource.co.uk,2011-09-23:/blog/2011/09/23/a-summer-at-airsource/<p>Well – this is it! After a fantastic 9 week internship it’s sad to be leaving behind the code, co-workers and, of course, coffee machine I have got to know so well. From the very first day I was plunged straight in the deep end, coding new features and fixing …</p><p>Well – this is it! After a fantastic 9 week internship it’s sad to be leaving behind the code, co-workers and, of course, coffee machine I have got to know so well. From the very first day I was plunged straight in the deep end, coding new features and fixing bugs. It’s been fun, frustrating and interesting in equal measures, but I can assure you that an internship here is never dull!</p>
<p>So what is it that I’ve been doing? My first few weeks were a steep learning curve, as I battled to comprehend the delegate design pattern while also trying to work out why there’s a small toy bear which lives in the office. (Though I am now completely au fait with the former, the latter continues to bemuse me)! A couple of code reviews later I had free reign to amend or add code, and moved onto adding social media compatibility to several of our apps. If you’re planning to do this yourself, I would recommend ShareKit as a good starting point.</p>
<p>By mid-August I was working full time on <a href="http://itunes.apple.com/us/app/optiscan-qr-code-scanner-generator/id304099767?mt=8">Optiscan</a>. There’s a host of new features which will arrive on the AppStore soon, including much more support for creating QR codes. As a member of the engineering team I played a big role in delivering this functionality, both from a coding perspective and by participating in numerous (friendly if raucous) office arguments! The last couple of weeks have seen development give way to testing, so I’ve been scrambling to fix any bugs before I leave. For any of you who are newbie iPhone developers I’d have 3 words of advice – watch out for memory warnings, be careful when using UITableViews, and always keep an eye on your reference counting!</p>
<p>It hasn’t all been coding though. I’ve spent many a lunchtime dreaming up app ideas on the office kitchen whiteboard, with concepts ranging from the sublime to the ridiculous. The company lunches and barbecues were a particular highlight, as was a (slightly surreal) morning of set theory in order to write a Python script. These and other amusements unique to a small company like Airsource made this summer particularly enjoyable, even on the odd occasions where I did stare at the same bug for hours before seeing the simple solution!</p>
<p>Time to say farewell to everyone here, and thanks for a great summer. Keep an eye out for future releases of Optiscan and <a href="http://itunes.apple.com/us/app/cellar-manage-your-wine-collection/id321262925?mt=8">Cellar</a> (there’s some quite cool stuff to come, by the sounds of things in the office). Adios!</p>
<p>(Oh and I must ask someone about that bear before I go…)</p>Awww, shucks...2011-08-31T09:51:00+01:002011-08-31T09:51:00+01:00Nick Clareytag:airsource.co.uk,2011-08-31:/blog/2011/08/31/awww-shucks/<p>It's always nice when you read a lovely review of one of your products, and this month's <a href="http://www.icreatemagazine.com/">iCreate magazine</a> in the UK has given <a href="http://cellar-app.com/">Cellar</a> a test drive and decided they love it.</p>
<p>It's always nice when you read a lovely review of one of your products, and this month's <a href="http://www.icreatemagazine.com/">iCreate magazine</a> in the UK has given <a href="http://cellar-app.com/">Cellar</a> a test drive and decided they love it.</p>
<p>Thanks guys :-)</p>
<p><a href="https://airsource.co.uk/blog/images/2011/08/Cellar_review_iCreate.jpg"><img alt="Cellar review in iCreate Magazine" src="https://airsource.co.uk/blog/images/2011/08/Cellar_review_iCreate.jpg"></a></p>4Music sting featuring QR code2011-07-18T12:12:00+01:002011-07-18T12:12:00+01:00Steve O'Connortag:airsource.co.uk,2011-07-18:/blog/2011/07/18/4music-sting-featuring-qr-code/<p>Last night, during the 19:30 ad break, More 4 ran a sting for their sister channel, 4Music. The surreal set included a QR code on a TV in the left hand corner and, in a change from previous televised QR codes, the voiceover dialogue was doing nothing more than …</p><p>Last night, during the 19:30 ad break, More 4 ran a sting for their sister channel, 4Music. The surreal set included a QR code on a TV in the left hand corner and, in a change from previous televised QR codes, the voiceover dialogue was doing nothing more than hinting that the viewer "knew what to do". The code was onscreen for the majority of the 30 second sting. When scanned the code takes you (via a bit.ly shorturl) to <a href="http://www.4music.com/news/features/645/Free-N-Dubz-Download">an article on the 4Music site</a> where you are congratulated for being tech-savvy and 'rewarded' with a free N-Dubz download. Sounds good, right? Almost. Just two minor points:</p>
<ol>
<li>
<p><strong>The website isn't mobile.</strong>
This is supposed to be the (quote) "new and improved" 4Music site - so why is there no mobile optimised version? Surely a consideration for your average site now, but a must for a youth oriented site such as this.</p>
</li>
<li>
<p><strong>The track cannot be downloaded on all mobile phones.</strong>
Yes, if you are running Android or Windows 7 there should be no problems but, in a about face from the usual turn of events, iPhone users are denied the free track. Why is that? It could have easily been included on iTunes, no?</p>
</li>
</ol>
<p>It's a shame to see a QR code campaign that looked promising to begin with, be let down <em>yet again</em> by the end result.</p>
<p><img alt="4Music QR code sting image" src="https://airsource.co.uk/blog/images/2011/07/IMG_0642_crop.jpg" title="4Music QR code sting"></p>QR code chest tattoo? No, it's not...2011-07-11T12:22:00+01:002011-07-11T12:22:00+01:00Steve O'Connortag:airsource.co.uk,2011-07-11:/blog/2011/07/11/qr-code-chest-tattoo-no-its-not/<p>Everyone seems to be talking about a guy getting a QR code tattoo, from Paris based artist K.A.R.L., that scans to reveal an animated version of said tattoo. It's a video created for Scotch whiskey producer <a href="http://www.ballantines.com/">Ballantine's'</a> <em>'Leave an impression'</em> campaign, designed to coherce a younger market …</p><p>Everyone seems to be talking about a guy getting a QR code tattoo, from Paris based artist K.A.R.L., that scans to reveal an animated version of said tattoo. It's a video created for Scotch whiskey producer <a href="http://www.ballantines.com/">Ballantine's'</a> <em>'Leave an impression'</em> campaign, designed to coherce a younger market to drink their product. All well and good ,except for one small fact - it's not a QR code!</p>
<p>The tattoo is of an EZ Code, a proprietary 2D code format from Scan Life and is not a QR code at all. We were amazed to see the number of people retweeting and blogging about the story, with not one single person picking up on this salient fact. What gives? Nobody thought to scan it? Nobody recognised the different pattern style?!</p>
<p>We did.</p>
<p><img alt="EZcode tattoo" src="https://airsource.co.uk/blog/images/2011/07/tattoo.jpg" title="Tattoo closeup">
<img alt="Example of QR code and EZ code" src="https://airsource.co.uk/blog/images/2011/07/2dcodes.jpg"></p>Scanning the Royal Dutch Mint's new QR Code coin - what's behind that code2011-07-04T12:08:00+01:002011-07-04T12:08:00+01:00Steve O'Connortag:airsource.co.uk,2011-07-04:/blog/2011/07/04/scanning-the-royal-dutch-mints-new-qr-code-coin-whats-behind-that-code/<p><img alt="Royal Dutch Mint 5 Euro coin" src="https://airsource.co.uk/blog/images/2011/07/dutch-mint-001-2.jpg" title="Royal Dutch Mint 5 Euro coin, collectors pack"></p>
<p>This morning I finally took delivery of the 2011 Five Euro coin from the <a href="http://www.knm.nl/">Koninklijke Nederlandse Munt</a> (Royal Dutch Mint). This coin has received plenty of coverage over the last few weeks due to the inclusion of a <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">QR code</a> in it's design - the first time we have seen the …</p><p><img alt="Royal Dutch Mint 5 Euro coin" src="https://airsource.co.uk/blog/images/2011/07/dutch-mint-001-2.jpg" title="Royal Dutch Mint 5 Euro coin, collectors pack"></p>
<p>This morning I finally took delivery of the 2011 Five Euro coin from the <a href="http://www.knm.nl/">Koninklijke Nederlandse Munt</a> (Royal Dutch Mint). This coin has received plenty of coverage over the last few weeks due to the inclusion of a <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">QR code</a> in it's design - the first time we have seen the codes being used in such a permanent and far reaching way. Permanent in that these coins will be in circulation in the Netherlands long after many QR code campaign will have been and gone, far reaching in that they will pass through many hundreds of hands over their lifetime.</p>
<p>That's all very interesting, but the big question for those of us who have been following the progress of QR code usage is, does it work? By that I don't just mean, "Does it scan?", but "Does it lead somewhere with added value?". Does it give the user a reward for taking out their mobile phone, opening their scanning app (Optiscan for you iPhone users of course ;)) and capturing the QR code? Well, sort of.</p>
<p><a href="http://youtu.be/rWVUwTs9iak" title="Dutch Mint QR code site initial video"><img alt="Dutch Mint QR code site initial video" src="https://airsource.co.uk/blog/images/2011/07/dutch-mint-site-001-200x300.jpg"></a></p>
<p>When scanned, you are prompted to watch a short video. An overclocked journey in through the front door of the Mint leading to a bin full of the new coins, with a soundtrack straight from a suspense thriller. Odd, but better than an overly long video. Most people scanning the code (in the long term) probably wouldn't be interested in the process of decision making and creation that led to the QR code use anyway (although a link to that on the page would be good). You can see the video by clicking the image to the right.</p>
<p>Then we are presented with the site - a non-mobile site! A bad start, perpetrating the most complained about aspect of many QR code marketing campaigns. You would think that, if they were going to add a QR code to something as important and representational as their own currency, the homework would have been done on how best to implement it. This can of course be rectified, and hopefully will, but will it have already put too many people off?</p>
<p>Once zoomed in, we can at least still play the game they have embedded here as it is not Flash based. It's a simple game of memory, matching pairs of cards - all front or rear designs of commemorative Dutch coins. A nice enough distraction for a few minutes, but again, there is no further incentive. Where is my option to post my score online and share the link with friends? Maybe winning games could open up more of the website or other, more marketable incentives? What do you think?</p>
<p><img alt="Dutch Mint QR code site, memory game" src="https://airsource.co.uk/blog/images/2011/07/dutch-mint-site-002-200x300.jpg" title="Dutch Mint QR code site, memory game"></p>
<p>It's good to see QR codes used in this way, but I hope that the Royal Dutch Mint are paying attention and make suitable alterations to the landing page. There is also scope for them to change the destination over time, allowing for the QR code to have more longevity as people scan it to see where they are taken next.</p>Sunday Times recommends Optiscan for your mobile QR code needs2011-02-28T14:46:00+00:002011-02-28T14:46:00+00:00Steve O'Connortag:airsource.co.uk,2011-02-28:/blog/2011/02/28/sunday-times-recommends-optiscan-for-your-mobile-qr-code-needs/<p>So there I was, scanning for the latest in the world of QR codes and what should pop up? A mention of The Sunday Times recommending three QR code apps. Curiosity got the better of me so I paid the ferryman and jumped behind the paywall for a day to …</p><p>So there I was, scanning for the latest in the world of QR codes and what should pop up? A mention of The Sunday Times recommending three QR code apps. Curiosity got the better of me so I paid the ferryman and jumped behind the paywall for a day to see this article for myself. Planet of the Apps is The Sunday Times apps blog reviewing "the best and the brightest" on smartphones. Yesterday they chose three apps to talk about QR codes and guess what? The only paid app being recommended was - Optiscan!</p>
<p>We like that.</p>
<p>Not using Optiscan yet? <a href="http://bit.ly/optiscanapp">Get it on your iPhone today.</a></p>
<p><img alt="Loveit" src="https://airsource.co.uk/blog/images/2011/02/sunday-times-optiscan.png"></p>The inventors of the 'designer' QR Code love Optiscan2011-02-09T12:42:00+00:002011-02-09T12:42:00+00:00Steve O'Connortag:airsource.co.uk,2011-02-09:/blog/2011/02/09/the-inventors-of-the-designer-qr-code-love-optiscan/<p>You may not have heard of <a href="http://www.setjapan.com/category/qrcode/?lang=en">SET Japan</a>, but if you've seen some great looking QR codes around you'll probably have seen their work. Producing designer codes for the likes of TIME magazine and Louis Vuitton, SET Japan are recognised as the 'inventors' of the designer QR code that is …</p><p>You may not have heard of <a href="http://www.setjapan.com/category/qrcode/?lang=en">SET Japan</a>, but if you've seen some great looking QR codes around you'll probably have seen their work. Producing designer codes for the likes of TIME magazine and Louis Vuitton, SET Japan are recognised as the 'inventors' of the designer QR code that is becoming so popular now. They created their first a few years back and have been involved in virtually every major new use of them since, working with companies such as <a href="http://www.warbassedesign.com/">Warbasse Design</a> in the US recently.</p>
<p>With all their great experience of working with QR codes, it was with great pleasure and honour that we received an email from their development team praising Optiscan!</p>
<blockquote>
<p>"We use a lot of readers when we are testing and the team is always looking for the fastest reader - it is like a badge of honor if you can bring in the best reader to a meeting - and we are all huge fans of your Optiscan reader. It is our go to app and the one we recommend to anyone looking to get clicking."</p>
</blockquote>
<p>It's always good to be appreciated! <a href="http://bit.ly/bwTybZ">Get your copy of Optiscan today.</a></p>
<p><img alt="Louis Vuitton designer QR code" src="https://airsource.co.uk/blog/images/2011/02/vuitton-qr.thumbnail.jpg" title="Louis Vuitton designer QR code">
<img alt="True Blood designer QR code" src="https://airsource.co.uk/blog/images/2011/02/trueblood-qr.thumbnail.jpg" title="True Blood designer QR code"></p>Happy Holidays!2010-12-23T15:31:00+00:002010-12-23T15:31:00+00:00Steve O'Connortag:airsource.co.uk,2010-12-23:/blog/2010/12/23/happy-holidays/<p>Just like everyone else, Airsource will be closing down for the winter holiday. Tucking into lots of food, gifts good and bad, a drink or two and an afternoon nap, we'll be back in 2011 refreshed and ready to go.</p>
<p>We have lots of exciting projects to look forward to …</p><p>Just like everyone else, Airsource will be closing down for the winter holiday. Tucking into lots of food, gifts good and bad, a drink or two and an afternoon nap, we'll be back in 2011 refreshed and ready to go.</p>
<p>We have lots of exciting projects to look forward to - we think you'll be impressed too! See you then!</p>
<p><img alt="Christmas QR Code" src="https://airsource.co.uk/blog/images/2010/12/optiscan-christmas.jpg"></p>Our new look for 20112010-11-19T11:42:00+00:002010-11-19T11:42:00+00:00Steve O'Connortag:airsource.co.uk,2010-11-19:/blog/2010/11/19/our-new-look-for-2011/<p>Some of the more eagle eyed of you will have noticed what looks like a new logo floating around - it is! We've had a complete refresh of our logo and branding. So far it's only made a public appearance via our spangly new business cards (they are lovely, eh?) but …</p><p>Some of the more eagle eyed of you will have noticed what looks like a new logo floating around - it is! We've had a complete refresh of our logo and branding. So far it's only made a public appearance via our spangly new business cards (they are lovely, eh?) but you'll soon be privy to our new website and blog - all coming in time for the New Year!</p>
<p><img alt="New Airsource business cards" src="https://airsource.co.uk/blog/images/2010/11/business-card-tabletop.jpg" title="New Airsource business cards"></p>Optiscan iPhone app 1.8.5 update released2010-11-15T16:02:00+00:002010-11-15T16:02:00+00:00Steve O'Connortag:airsource.co.uk,2010-11-15:/blog/2010/11/15/optiscan-iphone-app-185-update-released/<p>We released the latest update of our QR Code scanner and creator - Optiscan - onto the <a href="http://bit.ly/bwTybZ">iTunes store</a> last Thursday (12th). Along with stability improvements in preparation for iOS 4.2, you'll find better decoding for newer devices plus a surprise new look!</p>
<p>We released the latest update of our QR Code scanner and creator - Optiscan - onto the <a href="http://bit.ly/bwTybZ">iTunes store</a> last Thursday (12th). Along with stability improvements in preparation for iOS 4.2, you'll find better decoding for newer devices plus a surprise new look!</p>
<p><img alt="Optiscan 1.8.5 scanning screen" src="https://airsource.co.uk/blog/images/2010/11/optiscan-scan-screen.jpg"></p>
<p>The new icons and UI tweaks have been brought in to match Optiscan's continually growing success in the field of QR Code scanning. Expect more tweaks and new features to come in future releases as QR Codes take the world by storm in 2011!</p>UI Automation on the iPhone2010-08-13T09:48:00+01:002010-08-13T09:48:00+01:00Iestyn Prycetag:airsource.co.uk,2010-08-13:/blog/2010/08/13/ui-automation-on-the-iphone/<p>As a summer intern I could have worried about just being given tasks such as making the tea, but here at <a href="http://www.airsource.co.uk">Airsource</a>, among other challenges, I was given the chance to work on improving some the QA infrastructure via automated testing. I study Engineering and have learnt some theory regarding …</p><p>As a summer intern I could have worried about just being given tasks such as making the tea, but here at <a href="http://www.airsource.co.uk">Airsource</a>, among other challenges, I was given the chance to work on improving some the QA infrastructure via automated testing. I study Engineering and have learnt some theory regarding testing practices in software development, so it was great to put that to a practical use!</p>
<p>Apple announced with iOS4 the ability to do <a href="http://developer.apple.com/iphone/library/documentation/DeveloperTools/Reference/UIAutomationRef/index.html">automated UI testing</a> on the iPhone as a part of the Instruments development tool. Such testing is done with the help of a number of JavaScript libraries which allow you to access an application on a device and tap buttons, scroll views and varify text as if it were a human interacting with the hardware, and not a script.</p>
<p>Why is this - other than the fact it's something from Apple - cool? It essentially means that it is possible for us as app developers and testers to automate some of the tests we always do to ensure we have not broken anything in our latest development cycle. There is nothing worse than working hard on adding a new feature to an app, testing it and releasing it only to discover that your testing missed a regression in your previous functionality. This forces you to make a brown paper bag release to fix it, for which you have to test everything again in addition to the things not fully tested the first time. This takes up time and effort which could be used to add more features, and more importantly can lead to your users having a broken app.</p>
<p>Automating the tests does not mean that they are better tests than if they were done manually. What it does mean is that tester time is saved on doing the mundane day to day tests so that they can spend more of their time on testing edge cases and new features, where the harder to find bugs are more likely to be and less likely to be found by the developers as they write code.</p>
<h1>What Airsource is doing with it</h1>
<p>At Airsource we've been busy automating tests for our app, <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>. Doing this we have found that, despite what Apple have tried to do with using timeouts, it is sometimes necessary to have delays in the tests. The main shortcoming in this area is the fact UI Automation swallows taps on elements which are not visible; meaning that your tests script may expect a changed state and fail because the tap to change the state was never registered. The reason that timeouts do not work on this is that the element you are attempting to tap exists and valid, so it can be tapped, but it may not be visible (due to an animation sequence for example) thus the tap is never registered by the app. This is a big shortcoming in the system of timeouts, it would be great if Apple fixed this by attempting taps until the tap occurs on a visible element or the timeout is reached. </p>
<p>When you are writing a test you do not want to have to worry if the button is ready to be tapped or not, you just want to tap the button! So as a part of the testing effort we have written a bunch of useful little functions which encapsulate all the delay logic and mean than we don't worry about delays when we're writing the tests.</p>
<p>The code below allows us to scroll to an element with a particular name in a scroll view, before waiting for it to become visible and then tapping it. This simple encapsulation of a common task means that there is no need for the test writer to worry about the button becoming visible to tap before tapping it, just worry about how to actually test the app.</p>
<div class="highlight"><pre><span></span><code><span class="o">//</span><span class="w"> </span><span class="n">Allows</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">scroll</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">element</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">particular</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">tap</span><span class="w"> </span><span class="n">it</span><span class="o">.</span>
<span class="n">function</span><span class="w"> </span><span class="n">scrollToElementWithNameAndTap</span><span class="p">(</span><span class="n">scrollView</span><span class="p">,</span><span class="n">name</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="w"> </span><span class="p">(</span><span class="n">elementArray</span><span class="w"> </span><span class="n">instanceof</span><span class="w"> </span><span class="n">UIAScrollView</span><span class="p">))</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">throw</span><span class="w"> </span><span class="p">(</span><span class="s2">"Expected a UIAScrollView"</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">e</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elementArray</span><span class="o">.</span><span class="n">scrollToElementWithName</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
<span class="w"> </span><span class="n">waitForVisible</span><span class="p">(</span><span class="n">e</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mf">0.25</span><span class="p">);</span>
<span class="w"> </span><span class="n">e</span><span class="o">.</span><span class="n">tap</span><span class="p">();</span>
<span class="p">}</span>
<span class="o">//</span><span class="w"> </span><span class="n">Poll</span><span class="w"> </span><span class="n">till</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="n">becomes</span><span class="w"> </span><span class="n">visible</span><span class="p">,</span><span class="w"> </span><span class="n">up</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">specified</span><span class="w"> </span><span class="n">timeout</span>
<span class="n">function</span><span class="w"> </span><span class="n">waitForVisible</span><span class="p">(</span><span class="n">element</span><span class="p">,</span><span class="w"> </span><span class="n">timeout</span><span class="p">,</span><span class="w"> </span><span class="n">step</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">step</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb nb-Type">null</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">step</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.5</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">stop</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">timeout</span><span class="o">/</span><span class="n">step</span><span class="p">;</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">var</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">stop</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">target</span><span class="o">.</span><span class="n">delay</span><span class="p">(</span><span class="n">step</span><span class="p">);</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">animation</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">element</span><span class="o">.</span><span class="n">isVisible</span><span class="p">())</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="n">element</span><span class="o">.</span><span class="n">logElement</span><span class="p">();</span>
<span class="w"> </span><span class="n">throw</span><span class="p">(</span><span class="s2">"Not visible"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>Using JavaScript objects' <code>prototype</code> property it is even possible to make <code>scrollToElementWithNameAndTap(name)</code> a method of each instance of <code>UIAScrollView</code>. Hence if you have a constructor for your tests you can use</p>
<div class="highlight"><pre><span></span><code>UIAScrollView.prototype.scrollToElementWithNameAndTap = function(name){
scrollToElementWithNameAndTap(this,name)
};
</code></pre></div>
<p>and now you can call the method on all your UIAScrollViews just as if it were a native UI Automation method.</p>
<h1>Doing more than it says on the tin</h1>
<p>An unfortunate aspect of the UI Automation kit is that there is no reset button or method. The result of this is that if a test fails, then your app is in an unknown state, thus it is very likely that all subsiquent tests will also fail. Ideally you do not want it to appear as if 20 tests have failed when in fact only one has failed. Having the ability to <code>target.reset()</code> to reset the app to a known state would be very useful, and it is a pain that Apple have not implemented it in some form. </p>
<p>To remedy this we've had to add a small amount of code to our apps which makes it possible for us to interact with them on the level of the program, not just the UI level. UI Automation allows us to change the volume on the device using methods in <a href="http://developer.apple.com/iphone/library/documentation/ToolsLanguages/Reference/UIATargetClassReference/UIATargetClass/UIATargetClass.html#//apple_ref/doc/uid/TP40009924">UIATarget</a>, so we've added a listener for the device volume to our app, which means we can run code when we detect a volume change. Code for a volume listener is shown below.</p>
<div class="highlight"><pre><span></span><code><span class="c1">// Set up a listener to act on volume changes</span>
<span class="n">AudioSessionInitialize</span><span class="p">(</span><span class="nb">nil</span><span class="p">,</span><span class="w"> </span><span class="nb">nil</span><span class="p">,</span><span class="w"> </span><span class="nb">nil</span><span class="p">,</span><span class="w"> </span><span class="nb">nil</span><span class="p">);</span>
<span class="n">AudioSessionSetActive</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="n">AudioSessionAddPropertyListener</span><span class="p">(</span>
<span class="w"> </span><span class="n">kAudioSessionProperty_CurrentHardwareOutputVolume</span><span class="p">,</span>
<span class="w"> </span><span class="n">applicationVolumeDidChange</span><span class="p">,</span>
<span class="w"> </span><span class="nb">self</span><span class="p">);</span>
<span class="c1">// Note that this callback will only be called if the mute button is off.</span>
<span class="kt">void</span><span class="w"> </span><span class="nf">applicationVolumeDidChange</span><span class="p">(</span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">inClientData</span><span class="p">,</span>
<span class="w"> </span><span class="n">AudioSessionPropertyID</span><span class="w"> </span><span class="n">inID</span><span class="p">,</span>
<span class="w"> </span><span class="kt">UInt32</span><span class="w"> </span><span class="n">inDataSize</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">inData</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="n">NSLog</span><span class="p">(</span><span class="s">@"Volume changed"</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Do something like reset the system</span>
<span class="p">}</span>
</code></pre></div>
<p>This way calling <code>target.clickVolumeUp();</code> just after <code>UIALogger.logFail(...)</code> in your testing script will mean that you can reset the app before the next test is run. Note that it is important that the hardware mute button is off when you are testing otherwise no signals will be sent that the volume has changed!</p>
<h1>Shortcomings</h1>
<p>Other than the swallowed taps and lack of reset method mentioned above, there are a couple of other shortcomings with UI Automation. The most obvious being the lack of screenshots when running the tests on the simulator; this makes it hard to verify UI and record what the app's state was when a test failed. To be honest it feels rather bizarre that you can take remote screenshots on the actual hardware iPhone, but not in the simulator. I hope that Apple fix this in future releases. </p>
<p>The less obvious, but rather annoying shortcoming (especially if you are building a large library of testing scripts) is the lack of a decent preprocessor. JavaScript does not have native support for preprocessing or any kind of #import structure, so it is good that UI Automation scripts support #import, but the lack of define statements and logic such as #define and #ifdef is a pain when you want only to run a certain initialisation script once, but it is called by each test you run. Emulation of this behaviour using JavaScript variables is not helped by the fact that variables can remain defined between different runs in Instruments.</p>
<h1>Conclusions</h1>
<p>Automated UI testing does not mean bug free code. No testing on any non-trivial piece of software can guarantee bug free code, and the testing is only as good as the tests themselves. Using automated testing means that it is easier to stop regressions by having a test in place for each previous bug which has cropped up, it also means that the tester's time can be utilised more effectively. Apple's UI Automation ticks many boxes for good automated testing despite its shortcommings, and we hope will prove an invaluable tool in our development cycle.</p>Quelle heure est-il? or "What's the time, Mr Jobs?"2010-03-15T15:41:00+00:002010-03-15T15:41:00+00:00Teanlorg Chantag:airsource.co.uk,2010-03-15:/blog/2010/03/15/quelle-heure-est-il/<p>A while ago, I was tracking down some NaNs in accelerometer-based code (smoothing device orientation for an OpenGL AR view). It turns out it wasn't my bug — <code>UIAcceleration.timestamp</code> was going backwards approximately every 12 minutes! Naturally, the documentation doesn't mention anything about this:</p>
<blockquote>
<p>This value indicates the time relative to the device CPU time base register. Compare acceleration event timestamps to determine the elapsed time between them.</p>
</blockquote>
<p>Assuming iPhone OS is similar enough to Mac OS X, it <a href="http://lists.apple.com/archives/mac-games-dev/2004/Feb/msg00126.html">must</a> be using mach_absolute_time():<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>
<p>A while ago, I was tracking down some NaNs in accelerometer-based code (smoothing device orientation for an OpenGL AR view). It turns out it wasn't my bug — <code>UIAcceleration.timestamp</code> was going backwards approximately every 12 minutes! Naturally, the documentation doesn't mention anything about this:</p>
<blockquote>
<p>This value indicates the time relative to the device CPU time base register. Compare acceleration event timestamps to determine the elapsed time between them.</p>
</blockquote>
<p>Assuming iPhone OS is similar enough to Mac OS X, it <a href="http://lists.apple.com/archives/mac-games-dev/2004/Feb/msg00126.html">must</a> be using mach_absolute_time():<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>
<blockquote>
<p>Uptime is the highest resolution (64-bits) timer on any PowerPC processor. It counts bus cycles (usually 1/4 the CPU speed) since the last reset (or (re?)boot). All other system timers and clocks are based off of this counter (including gettimeofday).</p>
</blockquote>
<p><a href="http://developer.apple.com/mac/library/qa/qa2004/qa1398.html">QA1398</a> appears to be the earliest<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup> official documentation of mach_absolute_time() and gives two definitions of GetPIDTimeInNanoseconds(). The first relies on CoreServices' AbsoluteToNanoseconds() which isn't in iPhone OS; we're interested in the second (casts added for a little extra clarity):</p>
<div class="highlight"><pre><span></span><code><span class="c1">// Do the maths. We hope that the multiplication doesn't</span>
<span class="c1">// overflow; the price you pay for working in fixed point.</span>
<span class="n">elapsedNano</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">(</span><span class="n">uint64_t</span><span class="p">)</span><span class="n">elapsed</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">(</span><span class="n">uint32_t</span><span class="p">)</span><span class="n">sTimebaseInfo</span><span class="p">.</span><span class="nb">numer</span>
<span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="p">(</span><span class="n">uint32_t</span><span class="p">)</span><span class="n">sTimebaseInfo</span><span class="p">.</span><span class="nb">denom</span><span class="p">;</span>
</code></pre></div>
<p>On OS X/i386, it's fine — the <a href="http://fxr.watson.org/fxr/source/osfmk/i386/hpet.c?v=xnu-1456.1.26#L263">several</a> <a href="http://fxr.watson.org/fxr/source/osfmk/i386/rtclock.c?v=xnu-1456.1.26#L102">timers</a> <a href="http://fxr.watson.org/fxr/source/osfmk/i386/tsc.c?v=xnu-1456.1.26#L206">are</a> <a href="http://fxr.watson.org/fxr/source/osfmk/i386/rtclock.c?v=xnu-1456.1.26#L568">all</a> <a href="http://fxr.watson.org/fxr/source/osfmk/i386/rtclock.c?v=xnu-1456.1.26#L283">converted</a> <a href="http://fxr.watson.org/fxr/source/osfmk/i386/machine_routines_asm.s?v=xnu-1456.1.26#L184">to</a> <a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Ffxr.watson.org%2Ffxr%2Fsource%2Fosfmk%2Fi386%2F%3Fv%3Dxnu-1456.1.26+nanoseconds&filter=0">nanoseconds</a> (this seems to miss the point entirely), so numer = denom = 1 and there's no overflow.</p>
<p>On an iPhone 3G S, it ticks at 24 MHz and (on OS 3) mach_timebase_info() returns 1000000000/24000000.<sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup> The multiply overflows after 264/109 ≈ 1.84e10 ticks — about 768.6 seconds or 12.8 minutes. Bingo.<sup id="fnref:5"><a class="footnote-ref" href="#fn:5">5</a></sup></p>
<p>But <code>UIAcceleration.timestamp</code> is a double — so it's converting timestamps to nanoseconds, overflowing, and dividing by a billion (or multiplying by 1.0e-9) to get the timestamp in seconds. I'm not sure why they do all this; I'd just calculate <code>numer/(denom*1.0e9)</code> at startup and multiply timestamps by it. It's also pretty easy to convert to nanoseconds without unnecessary<sup id="fnref:6"><a class="footnote-ref" href="#fn:6">6</a></sup> overflow:</p>
<div class="highlight"><pre><span></span><code>nanos = elapsed/denom*numer + elapsed%denom*numer/denom;
</code></pre></div>
<p>I ended up fixing the bug by throwing away accelerometer updates (but storing the new timestamp) when time has gone backwards. Who cares if you skip an accelerometer update every 12 minutes?</p>
<h1>Afterword</h1>
<p>I'm pretty sure that mach_absolute_time() is still the "best" way to measure timestamps — it's existed since Mac OS 10.0 and seems to be a highly stable API. <a href="http://www.google.com/search?q=site:developer.apple.com/iphone/+mach_absolute_time">CoreAnimation and GCD</a> mention it, along with <a href="http://developer.apple.com/iphone/library/qa/qa2009/qa1643.html">QA1643</a> which suggests either using it directly or inlining it. Since iOS 4, there's also CVGetCurrentHostTime() which the docs say is equivalent to the CoreAudio timebase (and thus mach_absolute_time). There's a <a href="http://www.macresearch.org/tutorial_performance_and_time">reasonably accurate article</a> with more details (but I'd divide by 109 instead of multiplying by 10−9). </p>
<p>For completeness, there's also <code>AudioQueueDeviceGetCurrentTime()</code> which requires an audio queue and returns a lot of extra things you probably don't care about. In Snow Leopard/iOS 4 there's <code>[[NSProcessInfo processInfo] systemUptime]</code>. <code>AudioGetCurrentHostTime()</code> exists but is "private" due to the lack of an iPhone OS header (the Mac OS X header says since iPhone OS 2).</p>
<p>I used to be lazy and use <code>CFAbsoluteTimeGetCurrent()</code>, but since iOS 4 it can change arbitrarily (due to app backgrounding, NTP/cell network updates, or leap seconds). <code>[NSDate timeIntervalSinceReferenceDate]</code> is equivalent, <code>[NSDate date]</code> is much slower (~4000 CPU clocks!). Moreover, both seem to round to the microsecond (presumably they're based on <code>gettimeofday()</code>).</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>UpTime() is the Mac OS 8 name for mach_absolute_time(). On a PowerPC, it retrieves the value of the timebase register using the <code>mftb</code> and <code>mftbu</code> instructions. It's not always the "bus clock"; my iBook G4 says it runs at 18431630 Hz, which doesn't correspond to any bus unless the CPU has a 57.5× multiplier (<a href="http://en.wikipedia.org/w/index.php?title=Crystal_oscillator_frequencies&oldid=373999844">apparently</a> it'll be a 18.432 MHz UART clock, despite the lack of a serial port). As far as I know, ARMs don't usually have a time base register; mach_timebase_info() on a 3GS takes over 700 CPU clocks, consistent with using an external device. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p><a href="http://developer.apple.com/mac/library/documentation/Performance/Conceptual/LaunchTime/Articles/MeasuringLaunch.html#//apple_ref/doc/uid/20001856-100475">Launch Time Performance Guidelines: Measuring Launch Speed: Using Explicit Timestamps</a> has been around for longer and says mach_absolute_time reads the CPU time base register and is the basis for other time measurement functions, but it's unclear which revision it was added in and is more of a brief mention than documentation. Interestingly, it's the only other document mentioning <a href="http://www.google.com/search?q=site:developer.apple.com+%22CPU+time+base+register%22&filter=0">"CPU time base register"</a>. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>The iPhone and iPhone 3G use 6 MHz. The iPhone 3G S, iPhone 4, and iPad use 24 MHz. The iPod Touches presumably are the same as the corresponding iPhone. On OS 4, mach_timebase_info() reduces the fraction, returning 125/3 on a 3G S, postponing overflow for 195 years, and making it difficult to tell whether UIAcceleration has been fixed.<sup id="fnref:4"><a class="footnote-ref" href="#fn:4">4</a></sup> <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p><code>UIAcceleration.timestamp</code> values are still all close to a nanosecond boundary, so it still seems to convert to nanoseconds first. Presumably it'll break noticeably on devices with an odd clock frequency (33.333333 MHz?). It's also odd that it's not fixed given the existence of <code>-[NSProcessInfo systemUptime]</code> which returns a double without nanosecond-rounding. <a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:5">
<p>It shouldn't be difficult to figure out how many times it's overflowed and thus get the corresponding mach_absolute_time() value, but that just seems like too much effort and is likely to break across releases. <a class="footnote-backref" href="#fnref:5" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:6">
<p>If the interval is more than 264 ticks, the input has overflowed. If the interval is more than 264 nanoseconds, the output must overflow (or be clipped, or something). Otherwise, it'll work; the "price you pay" for integer arithmetic is just that you need a little more care to prevent intermediate overflow. It's "fixed point" either. <a class="footnote-backref" href="#fnref:6" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:7">
<p>Interestingly, <code>CMSampleBufferGetSampleTimingInfo()</code> says that frames from <code>AVCaptureVideoDataOutput</code> use a nanosecond timescale, despite the lack of a nanosecond clock. <a class="footnote-backref" href="#fnref:7" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
</ol>
</div>Optiscan update teething troubles2010-03-12T22:51:00+00:002010-03-12T22:51:00+00:00John Earltag:airsource.co.uk,2010-03-12:/blog/2010/03/12/optiscan-update-teething-troubles/<p>We've been making continual minor improvements to <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>, fixing some issues with cut and paste and handling of certain types of addresses. Unfortunately a serious bug slipped into release 1.8.2, which hit the App Store yesterday. Apple helped us immensely by fast-tracking approval of version 1.8.3 …</p><p>We've been making continual minor improvements to <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>, fixing some issues with cut and paste and handling of certain types of addresses. Unfortunately a serious bug slipped into release 1.8.2, which hit the App Store yesterday. Apple helped us immensely by fast-tracking approval of version 1.8.3. If you are seeing crashes in Optiscan today, check if you are still running 1.8.2. If so, check the App Store for an update! (It may take up to another couple of hours till it's fully propagated through their distribution network.)</p>Optiscan developments2010-02-08T12:03:00+00:002010-02-08T12:03:00+00:00John Earltag:airsource.co.uk,2010-02-08:/blog/2010/02/08/optiscan-developments/<p>Several news items on <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>! Last week we sent version 1.8.0 to Apple (hopefully it will make the App Store this week). Together with some other cool stuff, this version adds support for structured append, which has been widely requested by users of the <a href="http://www.kingjim.co.jp/pomera/dm20/index.html">King Jim Pomera DM-20 …</a></p><p>Several news items on <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>! Last week we sent version 1.8.0 to Apple (hopefully it will make the App Store this week). Together with some other cool stuff, this version adds support for structured append, which has been widely requested by users of the <a href="http://www.kingjim.co.jp/pomera/dm20/index.html">King Jim Pomera DM-20</a>. An update to the Optiscan library for our <a href="https://airsource.co.uk/blog/2010/01/08/optiscan-licensing/">commercial licensees</a> will follow shortly.</p>
<p>We've had a note from Greg at <a href="http://www.setjapan.com/">SET Japan</a>: their latest designer QR code promotional video features Optiscan, which seems to scan it better than the competition. Check it out: <a href="http://youtube.com/watch?v=M5lAT3gVzFc">http://youtube.com/watch?v=M5lAT3gVzFc</a>.</p>
<p>Also Optiscan is now featured on the <a href="http://www.sparqcode.com/sparqreader/">readers page</a> by <a href="http://www.mskynet.com/">MSKYNET</a>, which has a rather nice <a href="http://www.mskynet.com/static/maestro">online QR code generator</a>. They "rebrand" QR codes as sparqcodes in the US.</p>Optiscan licensing2010-01-08T18:32:00+00:002010-01-08T18:32:00+00:00John Earltag:airsource.co.uk,2010-01-08:/blog/2010/01/08/optiscan-licensing/<p><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> has been successful with consumers on the Apple App Store, but it has also piqued the interest of businesses. If you are interested in licensing the image processing library of Optiscan for your own application, or in developing a white-label version of Optiscan for use on devices specific to …</p><p><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> has been successful with consumers on the Apple App Store, but it has also piqued the interest of businesses. If you are interested in licensing the image processing library of Optiscan for your own application, or in developing a white-label version of Optiscan for use on devices specific to your company, we want to hear from you.</p>
<p>We will only license the library for use on the App Store if the intended use does not directly compete with Optiscan. To request additional details or an evaluation package, send details of your intended use to <a href="mailto:optiscan-licensing@airsource.co.uk">optiscan-licensing@airsource.co.uk</a>.</p>Over 30,000 iPhone app sales2010-01-08T18:32:00+00:002010-01-08T18:32:00+00:00John Earltag:airsource.co.uk,2010-01-08:/blog/2010/01/08/over-30000-iphone-app-sales/<p>It's been not quite a year since we released our first application onto the App Store, and we've sold over 30,000 units in total across 68 countries. This comes despite the fact that Optiscan was off the store for 6 months for reasons largely beyond our control.</p>
<p>This year …</p><p>It's been not quite a year since we released our first application onto the App Store, and we've sold over 30,000 units in total across 68 countries. This comes despite the fact that Optiscan was off the store for 6 months for reasons largely beyond our control.</p>
<p>This year we'll be looking to more than double that, and I think we've got a good chance!</p>Optiscan reviewed on CNET2009-12-11T09:15:00+00:002009-12-11T09:15:00+00:00John Earltag:airsource.co.uk,2009-12-11:/blog/2009/12/11/optiscan-reviewed-on-cnet/<p><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> has just been <a href="http://news.cnet.com/8301-17939_109-10412329-2.html">reviewed on CNET</a>. They say:</p>
<blockquote>
<p>"Overall, Optiscan is a really nice QR Code reader. ... It's a full-featured app that should satisfy most users."</p>
</blockquote>Optiscan 1.6 released2009-12-09T11:03:00+00:002009-12-09T11:03:00+00:00John Earltag:airsource.co.uk,2009-12-09:/blog/2009/12/09/optiscan-16-released/<p>Optiscan 1.6 is now available on the <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">App Store</a>. This is a minor update, containing some essential bug fixes.</p>Whisper now Free!2009-12-08T16:31:00+00:002009-12-08T16:31:00+00:00John Earltag:airsource.co.uk,2009-12-08:/blog/2009/12/08/whisper-now-free/<p>Today we made <a href="http://www.whisper-app.com/">Whisper</a> <strong>free</strong> to download <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=335760235&mt=8">on the Apple App Store</a>! This will last for a limited time only. When you give it a try, please take the time to leave a review!</p>Google Favourite Places2009-12-08T10:01:00+00:002009-12-08T10:01:00+00:00Ben Blaukopftag:airsource.co.uk,2009-12-08:/blog/2009/12/08/google-favourite-places/<p>Airsource were very pleased to see <a href="http://www.youtube.com/watch?v=zuVSpG-ZdkU&feature=player_embedded">Google promoting QR Codes</a>. However, we pointed <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> at the code in the video - and obviously it picked it up. What confuses us is that the code is, shock horror, NOT for the Kasa Indian Eatery in San Francisco- it's actually for <a href="http://google.com/m/place?georestrict=input_srcid%3A63b87b248f6d96a5">Sweetgreen, over …</a></p><p>Airsource were very pleased to see <a href="http://www.youtube.com/watch?v=zuVSpG-ZdkU&feature=player_embedded">Google promoting QR Codes</a>. However, we pointed <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> at the code in the video - and obviously it picked it up. What confuses us is that the code is, shock horror, NOT for the Kasa Indian Eatery in San Francisco- it's actually for <a href="http://google.com/m/place?georestrict=input_srcid%3A63b87b248f6d96a5">Sweetgreen, over in Washington DC</a>.</p>Optiscan 1.5 bugs and workarounds2009-11-26T17:41:00+00:002009-11-26T17:41:00+00:00John Earltag:airsource.co.uk,2009-11-26:/blog/2009/11/26/optiscan-bugs-and-workarounds/<p>The release of <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> 1.5 was very rushed in response to a request from Apple, and a few new bugs have sneaked in. We've submitted version 1.6, fixing these problems, to Apple today, but because Apple is very busy we don't expect approval of the update until mid-December …</p><p>The release of <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> 1.5 was very rushed in response to a request from Apple, and a few new bugs have sneaked in. We've submitted version 1.6, fixing these problems, to Apple today, but because Apple is very busy we don't expect approval of the update until mid-December.</p>
<p>Due to the delay, we thought we'd offer some notes about the known issues and suggest work-arounds.</p>
<h1>Potential crashes</h1>
<p>We are aware that a few users have been experiencing instability in the application. There are known crashes in Optiscan 1.5 in two cases:</p>
<ul>
<li>Have automatic opening turned on for a type of QR code content (one of the options in the Settings tab), and automatically scan an image containing that content type with the camera, or</li>
<li>Delete the last saved item on the "Saved" tab</li>
</ul>
<p>As a work-around, please avoid deleting the last saved item on the "Saved" tab (perhaps keep a dummy empty item there), and turn off any automatic opening preferences you may have set.</p>
<p>Please note: there is <strong>no</strong> problem with automatic scanning - merely with the logic that can automatically open a browser in the app when you scan a URL.</p>
<h1>Encoding selection problem</h1>
<p>Optiscan correctly decodes a wide range of character sets that are used in encoding QR codes. However, version 1.5 may be confused by QR codes which use the Shift-JIS character set, and fail to present any available actions.</p>
<p>The work-around is to manually select the encoding on the action selection page. Generally, selecting either UTF-8 or Shift-JIS will resolve the problem.</p>Optiscan updates on Twitter2009-11-26T17:30:00+00:002009-11-26T17:30:00+00:00John Earltag:airsource.co.uk,2009-11-26:/blog/2009/11/26/optiscan-updates-on-twitter/<p>Starting today, we're adding a new way that we will be keeping in touch with <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> users - via Twitter, as <a href="http://twitter.com/OptiscanApp">@OptiscanApp</a>. We're hoping to make this a way that people can make feature requests and we can disseminate hints and tips a little more easily.</p>
<p>Come and let us know …</p><p>Starting today, we're adding a new way that we will be keeping in touch with <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> users - via Twitter, as <a href="http://twitter.com/OptiscanApp">@OptiscanApp</a>. We're hoping to make this a way that people can make feature requests and we can disseminate hints and tips a little more easily.</p>
<p>Come and let us know what you think!</p>Announcing - Whisper!2009-11-25T18:19:00+00:002009-11-25T18:19:00+00:00John Earltag:airsource.co.uk,2009-11-25:/blog/2009/11/25/announcing-whisper/<p><a href="http://www.airsource.co.uk/">Airsource</a> are pleased to announce the release of our newest application, <a href="http://www.whisper-app.com/">Whisper</a>, available today <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=335760235&mt=8">on the Apple App Store</a>!</p>
<p><a href="http://www.airsource.co.uk/">Airsource</a> are pleased to announce the release of our newest application, <a href="http://www.whisper-app.com/">Whisper</a>, available today <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=335760235&mt=8">on the Apple App Store</a>!</p>
<h1>Why Whisper?</h1>
<p>In a class and want to send someone a quick sketch?</p>
<p>In a meeting and want to send a colleague a quick note or a business card? Try Whisper! What is Whisper? Whisper is the new and innovative local chat client for iPhone and iPod Touch. It works over either Bluetooth or local WiFi so you can chat to anyone nearby. There is no server involved, and no need for a cellular connection.</p>
<p><img alt="Whisper" src="https://airsource.co.uk/blog/images/2009/11/img_0002.png"></p>
<h1>Features</h1>
<ul>
<li>Send and receive Pictures, from your Photos or taken with the camera!</li>
</ul>
<p><img alt="sketch_rotated" src="https://airsource.co.uk/blog/images/2009/11/sketch_rotated.thumbnail.png"></p>
<ul>
<li>Send and receive Text messages</li>
<li>Send and receive Contacts, selecting which details you want to send</li>
<li>Send and receive Sketches, drawn with the doodle tool</li>
<li>Save received contacts to your address book</li>
<li>Save pictures and sketches back to your photo album</li>
<li>Whisper keeps a complete history of your exchanges, including pictures and contacts sent and received</li>
<li>Set a profile picture and status message to share with other users</li>
<li>Tired of writing in the cramped portrait mode keyboard? Every screen in Whisper will smoothly rotate to landscape</li>
<li>Clear your chat history</li>
</ul>
<p><img alt="img_0016" src="https://airsource.co.uk/blog/images/2009/11/img_0016.thumbnail.png"></p>
<p>Whispered with someone before and want to pick up where you left off? If they're nearby, but don't have the app running, just use Whisper to send them a chat request using Apple's push notification service.</p>
<p>Whisper uses a new, specially designed reliability protocol, so it keeps working smoothly even if the network is slow. Even so, if a message doesn't get through for some reason, Whisper shows this by turning the message red. If you send a text message while a large picture or contact is being transmitted, Whisper gets the text through while the other transfers continue in the background.</p>
<h1>Tips on Getting Connected for the First Time</h1>
<p><strong>Getting connected with Wifi</strong>: Both devices must have Whisper installed, and both must be connected to the same Wifi access point. Simply start Whisper on each, and they should find automatically locate each other.</p>
<p><strong>Getting connected with Bluetooth:</strong> Both devices must have Whisper installed, and each must be an iPhone 3G or better, iPod Touch 2G or better. Turn Bluetooth on in Settings in each device. Then start Whisper! It may take up to 30 seconds for the devices to pair, so be patien</p>
<h1>Feedback</h1>
<p>We want your feedback! Whisper has an in-app feedback button, so if you think of any features you want added, please just drop us a line. Anyone who submits a new feature may also be offered the opportunity to try out new versions before they make it to the App Store. Find more tips, screenshots, and general information about the application on the <a href="http://www.whisper-app.com/">Whisper website</a>! Also see our <a href="http://twitter.com/whisperapp">twitter feed</a> for info on future developments.</p>Optiscan 1.5 released2009-11-24T13:15:00+00:002009-11-24T13:15:00+00:00John Earltag:airsource.co.uk,2009-11-24:/blog/2009/11/24/optiscan-15-released/<p><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8"><img alt="optiscantransbak" src="https://airsource.co.uk/blog/images/2009/03/optiscantransbak.png"></a>
We're <strong>very</strong> pleased to announce that version 1.5 of Optiscan is now <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">available on the Apple App Store</a>!</p>
<p>This update brings:</p>
<ul>
<li>Auto scan functionality on iPhone OS 3.x</li>
<li>Support for the iPhone 3GS</li>
<li>Various stability improvements</li>
<li>Can now often handle multiple QR codes visible at the same time …</li></ul><p><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8"><img alt="optiscantransbak" src="https://airsource.co.uk/blog/images/2009/03/optiscantransbak.png"></a>
We're <strong>very</strong> pleased to announce that version 1.5 of Optiscan is now <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">available on the Apple App Store</a>!</p>
<p>This update brings:</p>
<ul>
<li>Auto scan functionality on iPhone OS 3.x</li>
<li>Support for the iPhone 3GS</li>
<li>Various stability improvements</li>
<li>Can now often handle multiple QR codes visible at the same time</li>
<li>Feedback emails now automatically include a copy the image that didn't decode, so we see what you're seeing</li>
<li>Better handling of manually captured images</li>
<li>Send QR codes by email</li>
</ul>
<p>As a special offer, for two weeks only, we are reducing the price to US$1.99 (from US$4.99, with equivalent changes on other App Stores). The update is, of course, free for existing customers. If this update works for you, please consider recommending it to friends and colleagues!</p>
<p>We have had communication with Apple which indicates that there will be no further obstacle to our putting updates on the App Store, which gives us the green light to further develop this application. Thanks Apple!!!!</p>Coming Soon: Optiscan 1.52009-11-23T12:49:00+00:002009-11-23T12:49:00+00:00John Earltag:airsource.co.uk,2009-11-23:/blog/2009/11/23/coming-soon-optiscan-15/<p>Last week Airsource finally submitted an update to Optiscan, version 1.5. At long last, we've had some positive, direct communication with people at Apple, which leads us to believe that they will approve our update within the next week or so. This will allow us to return Optiscan to …</p><p>Last week Airsource finally submitted an update to Optiscan, version 1.5. At long last, we've had some positive, direct communication with people at Apple, which leads us to believe that they will approve our update within the next week or so. This will allow us to return Optiscan to the App Store!</p>
<p>The new release will include automatic scanning. It will also fully support the iPhone 3GS and iPhone OS 3.0+, and incorporate various performance improvements that we've developed over the seven months since the 1.4 update. Most excitingly, the green light from Apple will open the way for us to commence further development of this product -- watch this space!</p>Announcing - Cellar!2009-07-13T11:52:00+01:002009-07-13T11:52:00+01:00John Earltag:airsource.co.uk,2009-07-13:/blog/2009/07/13/announcing-cellar/<p><a href="http://www.airsource.co.uk/">Airsource</a> are pleased to announce the release of <a href="http://www.cellar-app.com/">Cellar</a>, developed together with Glasshouse Apps, <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=321262925&mt=8">on the Apple App Store</a>!</p>
<p><a href="http://www.airsource.co.uk/">Airsource</a> are pleased to announce the release of <a href="http://www.cellar-app.com/">Cellar</a>, developed together with Glasshouse Apps, <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=321262925&mt=8">on the Apple App Store</a>!</p>
<p>Cellar is designed to keep track of your wine, and remember what to buy next time you visit the store, thanks to the app's unique 'Garage' feature.</p>
<p><img alt="Cellar" src="https://airsource.co.uk/blog/images/2009/07/picture-2.thumbnail.png"></p>
<p>Cellar is not just a list, it's an actual Cellar! Using it is "almost as fun as drinking the wine you collect" - Michael Rose, tuaw.com. Cellar is a visual, editable, swipeable showcase. And when you have finished a bottle, if you would like to keep it for reference in the future, store it in the Garage where all your archived bottles are ordered by star rating, ready to be repurchased and moved back to the Cellar.</p>
<p>Organizing your wine collection has never been so much fun!</p>
<p>Now available at a release sale price of 99 cents on <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=321262925&mt=8">the App Store</a>.</p>Optiscan removed from sale2009-07-08T10:10:00+01:002009-07-08T10:10:00+01:00John Earltag:airsource.co.uk,2009-07-08:/blog/2009/07/08/optiscan-removed-from-sale/<p>We have decided to remove version 1.4 of <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">Optiscan</a> from sale until Apple approve version 1.5, due to <a href="https://airsource.co.uk/blog/2009/06/18/optiscan-bug-on-iphone-os-30/">a bug</a> which affects automatic scanning devices that have been upgraded to iPhone OS 3.0. The user experience using version 1.4 on iPhone OS 3.0 is not …</p><p>We have decided to remove version 1.4 of <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">Optiscan</a> from sale until Apple approve version 1.5, due to <a href="https://airsource.co.uk/blog/2009/06/18/optiscan-bug-on-iphone-os-30/">a bug</a> which affects automatic scanning devices that have been upgraded to iPhone OS 3.0. The user experience using version 1.4 on iPhone OS 3.0 is not acceptable, and we don't want to introduce new users to the application this way.</p>
<p>Hopefully Apple will approve version 1.5 soon, at which point we will return Optiscan to the App Store.</p>
<p>We still want to support our existing users. Owners of Optiscan with an urgent need to have the application working on iPhone OS 3.0 should contact us to explain their situation, using the problem reporting features of the application. Subject to workload, we may be able to assist.</p>Optiscan bug on iPhone OS 3.02009-06-18T18:43:00+01:002009-06-18T18:43:00+01:00John Earltag:airsource.co.uk,2009-06-18:/blog/2009/06/18/optiscan-bug-on-iphone-os-30/<p>To all our customers who have purchased <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">Optiscan</a>:</p>
<p>Version 1.4 of Optiscan, currently on the App Store, does not support automatic scanning on iPhone OS 3.0. We sent version 1.5, which fixes this problem and adds some new features, to Apple about three weeks ago, but it …</p><p>To all our customers who have purchased <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">Optiscan</a>:</p>
<p>Version 1.4 of Optiscan, currently on the App Store, does not support automatic scanning on iPhone OS 3.0. We sent version 1.5, which fixes this problem and adds some new features, to Apple about three weeks ago, but it has not yet been approved for distribution. Apple have been extremely busy, but with the OS update expected out on Friday and no word on our update, we felt it was important to post a warning!</p>
<p>The application still functions correctly in all other respects. But, if the automatic scanning function is important to you, please delay updating to iPhone OS 3.0 until Apple publishes our more recent release.</p>Announcing - Optishare!2009-05-05T13:17:00+01:002009-05-05T13:17:00+01:00John Earltag:airsource.co.uk,2009-05-05:/blog/2009/05/05/announcing-optishare/<p><a href="http://www.airsource.co.uk/">Airsource</a> are delighted to announce that Optishare is now available <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=313391711&mt=8">on the App Store</a>.</p>
<p><a href="http://www.airsource.co.uk/">Airsource</a> are delighted to announce that Optishare is now available <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=313391711&mt=8">on the App Store</a>.</p>
<p><a href="http://blog.airsource.co.uk/index.php/what-is-optishare/">What is Optishare?</a> It's a simple app that allows the creation of QR codes on any iPhone or iPod Touch, which can be scanned by any QR-enabled device, such as an iPhone using <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>, the fastest QR code scanner on the App Store.</p>
<ul>
<li>Share contacts, web addresses and text with other devices.</li>
<li>Save specific QR codes for quick sharing — perfect for sharing your business card.</li>
<li>Keeps a history of QR codes created and shared for easy recall.</li>
<li>Optishare supports encoding to UTF-8, ISO-8859, and Shift-JIS.</li>
<li>Select the contact details you want to send, to ensure the right people get the right information.</li>
</ul>
<p>Optishare runs without a network connection, and keeps your data private. Why put up with anything less?</p>
<p><img alt="Optishare" src="https://airsource.co.uk/blog/images/2009/05/primaryscreenshot.thumbnail.jpg"></p>
<p>Optishare is available right now <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=313391711&mt=8">from the App Store</a>.</p>Viral Fou now available!2009-04-01T16:02:00+01:002009-04-01T16:02:00+01:00Ben Blaukopftag:airsource.co.uk,2009-04-01:/blog/2009/04/01/viral-fou-now-available/<p>Airsource are delighted to announce their latest application, Viral Fou. Viral Fou leverages the power of crowd-sourcing, to identify potential mischief makers in their community. The initial release of Viral Fou is only available in France. It is free, but time-limited, and will expire at midnight tonight. </p>iPhone 3.02009-03-18T12:23:00+00:002009-03-18T12:23:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-18:/blog/2009/03/18/iphone-30/<p>Airsource was very pleased to see the <a href="http://developer.apple.com/iphone/prerelease/library/releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOSv3.html#//apple_ref/doc/uid/TP40008245-SW1">contents</a> (login required) of <a href="http://www.macworld.com/article/139401/2009/03/appleevent.html?lsrc=top_1">yesterday's announcements</a> about the new version of the iPhone SDK. Naturally we will be updating our existing applications to take advantage of the new features.</p>Optiscan 1.2 now available!2009-03-18T12:16:00+00:002009-03-18T12:16:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-18:/blog/2009/03/18/optiscan-12/<p>The update to <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> has been approved by Apple - get it on the App Store now! The update is free for all existing customers, and includes several bug fixes, including three which could causes the application to crash. </p>Barista is App Store "Pick of the Week"!2009-03-11T19:23:00+00:002009-03-11T19:23:00+00:00Nick Clareytag:airsource.co.uk,2009-03-11:/blog/2009/03/11/barista-is-app-store-pick-of-the-week/<p>Wow. Obviously great espresso is at least partially responsible for Apple's success with the iPhone - their staff have graciously awarded Barista "Pick of the Week"!</p>
<p>Wow. Obviously great espresso is at least partially responsible for Apple's success with the iPhone - their staff have graciously awarded Barista "Pick of the Week"!</p>
<p>Read more <a href="http://www.apple.com/hotnews/#section=iphone">here</a>, and if you want to brush up on your espresso skills - go get <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=302556126&mt=8">Barista</a>!</p>App Store Localizations2009-03-11T11:55:00+00:002009-03-11T11:55:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-11:/blog/2009/03/11/app-store-localizations/<p>Your app is on the App Store, and you've just sorted out the translations for all the metadata. So you go upload them on iTunes Connect, and then obviously you check that they look right in iTunes, changing country to make sure each language looks right. English is unchanged and …</p><p>Your app is on the App Store, and you've just sorted out the translations for all the metadata. So you go upload them on iTunes Connect, and then obviously you check that they look right in iTunes, changing country to make sure each language looks right. English is unchanged and obviously looks okay. France - tick. Deutschland- tick. Nederland - Whoah! That's not right. My Dutch isn't that hot but I know it isn't identical to English. Check the rest, and it turns out Japanese has the same problem.</p>
<p><img alt="picture-7" src="https://airsource.co.uk/blog/images/2009/03/picture-7.png"></p>
<p>Let's back up a step. Firstly, we're changing our region using the My Store setting at the bottom of the homepage of iTunes Store.</p>
<p>That's what we do for all the regions - and French works. Why doesn't Dutch? Why doesn't Japanese? I've been struggling to figure out what these two languages - or at least regional App Stores - have in common. And I have no ideas. Answers on a postcard to...</p>
<p>However, it is possible to check the localizations for Dutch and Japanese. The answer can be found in an <a href="http://support.apple.com/kb/HT2242">Apple Support article</a>, which I've summarised here.</p>
<p><strong>On a Mac</strong></p>
<ul>
<li>In iTunes, select any regional store other than Nederlands</li>
<li>Quit iTunes</li>
<li>Go to System Preferences->International->Language and drag Nederlands up above English</li>
<li>Quit System Preferences</li>
<li>Run iTunes and select the Nederlands regional store.</li>
<li>Find your app. If you correctly uploaded Dutch metadata to iTunes connect, then your app's App Store page will now display in Dutch</li>
</ul>
<p><strong>On a PC</strong></p>
<ul>
<li>Forget the Windows language settings. They aren't used.</li>
<li>In iTunes, select any regional store other than Nederlands</li>
<li>Go to Edit->Preferences->General and select Dutch (Netherlands)</li>
<li>Quit iTunes</li>
<li>Run iTunes and select the Nederlands regional store.</li>
<li>Find your app. If you correctly uploaded Dutch metadata to iTunes connect, then your app's App Store page will now display in Dutch</li>
</ul>
<p>The same principle works for Japanese.</p>Optiscan Update2009-03-09T22:02:00+00:002009-03-09T22:02:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-09:/blog/2009/03/09/optiscan-update/<p>The update to Optiscan has been approved by Apple - get it on the <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">App Store</a> now! The update is free for all existing customers, and includes several improvements such as better image processing, internationalisation, and bug fixes. Try it out!</p>Androidinous Efficiency: To bool or not to bool?2009-03-04T16:11:00+00:002009-03-04T16:11:00+00:00Teanlorg Chantag:airsource.co.uk,2009-03-04:/blog/2009/03/04/androidinous-efficiency-to-bool-or-not-to-bool/<p>A common belief among programmers is that you should write code that says what you mean, because the compiler will probably do a better job at optimising than you can do off the top of your head.</p>
<p>On the other hand, <a href="http://code.google.com/android/toolbox/performance.html">Writing Efficient Android Code</a> says "<em>It is unwise to rely on a compiler to "save" you and make your code fast enough</em>" and recommends that programmers do things like caching member variables (like array lengths) in local variables. This isn't just premature optimisation; it's trivial stuff we expect the most basic compilers to handle!</p>
<p>To understand why, it helps to know how your code ends up running on device:</p>
<p>A common belief among programmers is that you should write code that says what you mean, because the compiler will probably do a better job at optimising than you can do off the top of your head.</p>
<p>On the other hand, <a href="http://code.google.com/android/toolbox/performance.html">Writing Efficient Android Code</a> says "<em>It is unwise to rely on a compiler to "save" you and make your code fast enough</em>" and recommends that programmers do things like caching member variables (like array lengths) in local variables. This isn't just premature optimisation; it's trivial stuff we expect the most basic compilers to handle!</p>
<p>To understand why, it helps to know how your code ends up running on device:</p>
<ol>
<li>Source files are compiled to Java class files with (usually Sun's) javac.</li>
<li>Java class files are compiled to Dalvik bytecode with the Android SDK's dx.</li>
<li>Dalvik bytecode is <em>interpreted</em> by the Dalvik VM running on device. Dalvik (in Android 1.0) has no JIT.<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></li>
</ol>
<p>All three stages can perform optimisation (after all, a JIT is effectively an interpreter optimisation). So why aren't they there?</p>
<ul>
<li>There's not much point optimising Java bytecode if you have an optimising JIT, so Java compilers don't do it:<ul>
<li>You need to handle inefficient bytecode anyway (from existing JARs, competitors' compilers, old versions of your compiler, ...). Optimised bytecode is probably harder to optimise in a JIT.<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup></li>
<li>The only thing you save is the size of your class files -- It's a waste of effort.</li>
<li>Efficient Java bytecode helps <em>everybody's</em> VM, whereas an optimising JIT gives you a competitive edge.<sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup></li>
</ul>
</li>
<li>If you're in a rush to ship (and Android 1.0 feels slightly rushed), you gain a lot more by rewriting the bottlenecks in C than you do optimising (optimising is <em>hard</em> -- if it was easy, GCC wouldn't be so bad at it).</li>
<li>A fast, optimising JIT is even harder to get right. Sun's JVM <a href="http://en.wikipedia.org/wiki/Java_version_history">didn't have a JIT for nearly 3 years</a> -- plenty of time to gain a reputation of being reeeeeally slow.</li>
</ul>
<p>Let's take some sample code: We'll start with a familiar paradigm to most programmers... </p>
<div class="highlight"><pre><span></span><code><span class="nv">public</span><span class="w"> </span><span class="nv">void</span><span class="w"> </span><span class="nv">toggle</span><span class="ss">()</span><span class="w"> </span>{
<span class="w"> </span><span class="nv">mRunning</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nv">mRunning</span><span class="c1">;</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="ss">(</span><span class="nv">mRunning</span><span class="ss">)</span><span class="w"> </span>{
<span class="w"> </span><span class="nv">start</span><span class="ss">()</span><span class="c1">;</span>
<span class="w"> </span>}<span class="w"> </span><span class="k">else</span><span class="w"> </span>{
<span class="w"> </span><span class="nv">stop</span><span class="ss">()</span><span class="c1">;</span>
<span class="w"> </span>}
}
</code></pre></div>
<p>There are two main ways the resulting Dalvik bytecode is suboptimal (one has already been pointed out). For brevity, we'll look at functions that don't call <code>start()</code> and <code>stop()</code> (the code there is fairly uninteresting):</p>
<div class="highlight"><pre><span></span><code><span class="nv">public</span><span class="w"> </span><span class="nv">boolean</span><span class="w"> </span><span class="nv">toggle1</span><span class="ss">()</span><span class="w"> </span>{
<span class="w"> </span><span class="nv">mB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nv">mB</span><span class="c1">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nv">mB</span><span class="c1">;</span>
}
<span class="nv">public</span><span class="w"> </span><span class="nv">boolean</span><span class="w"> </span><span class="nv">toggle2</span><span class="ss">()</span><span class="w"> </span>{
<span class="w"> </span><span class="nv">boolean</span><span class="w"> </span><span class="nv">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nv">mB</span><span class="c1">;</span>
<span class="w"> </span><span class="nv">mB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">b</span><span class="c1">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nv">b</span><span class="c1">;</span>
}
<span class="nv">public</span><span class="w"> </span><span class="nv">boolean</span><span class="w"> </span><span class="nv">toggle3</span><span class="ss">()</span><span class="w"> </span>{
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="ss">(</span><span class="nv">mB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nv">mB</span><span class="ss">)</span><span class="c1">;</span>
}
</code></pre></div>
<p>The resulting bytecode is not as succinct:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">Java</span><span class="w"> </span><span class="n">Dalvik</span>
<span class="n">public</span><span class="w"> </span><span class="n">boolean</span><span class="w"> </span><span class="n">toggle1</span><span class="p">();</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">toggle1</span><span class="p">:()</span><span class="n">Z</span><span class="p">:</span>
<span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="n">regs</span><span class="p">:</span><span class="w"> </span><span class="mi">0002</span><span class="p">;</span><span class="w"> </span><span class="n">ins</span><span class="p">:</span><span class="w"> </span><span class="mi">0001</span><span class="p">;</span><span class="w"> </span><span class="n">outs</span><span class="p">:</span><span class="w"> </span><span class="mi">0000</span>
<span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">iget</span><span class="o">-</span><span class="n">boolean</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">mB</span><span class="p">:</span><span class="n">Z</span>
<span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">getfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z 2: if-nez v0, 000a // +0008</span>
<span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">ifne</span><span class="w"> </span><span class="mi">12</span><span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 1 // #1</span>
<span class="w"> </span><span class="mi">8</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_1</span><span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">iput</span><span class="o">-</span><span class="n">boolean</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">mB</span><span class="p">:</span><span class="n">Z</span>
<span class="w"> </span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">13</span><span class="w"> </span><span class="mi">7</span><span class="p">:</span><span class="w"> </span><span class="n">iget</span><span class="o">-</span><span class="n">boolean</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">mB</span><span class="p">:</span><span class="n">Z</span>
<span class="w"> </span><span class="mi">12</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_0</span><span class="w"> </span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">v0</span>
<span class="w"> </span><span class="mi">13</span><span class="p">:</span><span class="w"> </span><span class="n">putfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z a: const/4 v0, #int 0 // #0</span>
<span class="w"> </span><span class="mi">16</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">0005</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">-</span><span class="mi">0006</span>
<span class="w"> </span><span class="mi">17</span><span class="p">:</span><span class="w"> </span><span class="n">getfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z </span>
<span class="w"> </span><span class="mi">20</span><span class="p">:</span><span class="w"> </span><span class="n">ireturn</span>
<span class="n">public</span><span class="w"> </span><span class="n">boolean</span><span class="w"> </span><span class="n">toggle2</span><span class="p">();</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">toggle2</span><span class="p">:()</span><span class="n">Z</span><span class="p">:</span>
<span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="n">regs</span><span class="p">:</span><span class="w"> </span><span class="mi">0002</span><span class="p">;</span><span class="w"> </span><span class="n">ins</span><span class="p">:</span><span class="w"> </span><span class="mi">0001</span><span class="p">;</span><span class="w"> </span><span class="n">outs</span><span class="p">:</span><span class="w"> </span><span class="mi">0000</span>
<span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">getfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z 0: iget-boolean v0, v1, Test.mB:Z</span>
<span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="n">ifne</span><span class="w"> </span><span class="mi">11</span><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="k">if</span><span class="o">-</span><span class="n">nez</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">0008</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">+</span><span class="mi">0006</span>
<span class="w"> </span><span class="mi">7</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_1</span><span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 1 // #1</span>
<span class="w"> </span><span class="mi">8</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">12</span><span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">iput</span><span class="o">-</span><span class="n">boolean</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">mB</span><span class="p">:</span><span class="n">Z</span>
<span class="w"> </span><span class="mi">11</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_0</span><span class="w"> </span><span class="mi">7</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">v0</span>
<span class="w"> </span><span class="mi">12</span><span class="p">:</span><span class="w"> </span><span class="n">istore_1</span><span class="w"> </span><span class="mi">8</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 0 // #0</span>
<span class="w"> </span><span class="mi">13</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">0005</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">-</span><span class="mi">0004</span>
<span class="w"> </span><span class="mi">14</span><span class="p">:</span><span class="w"> </span><span class="n">iload_1</span><span class="w"> </span>
<span class="w"> </span><span class="mi">15</span><span class="p">:</span><span class="w"> </span><span class="n">putfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z </span>
<span class="w"> </span><span class="mi">18</span><span class="p">:</span><span class="w"> </span><span class="n">iload_1</span><span class="w"> </span>
<span class="w"> </span><span class="mi">19</span><span class="p">:</span><span class="w"> </span><span class="n">ireturn</span>
<span class="n">public</span><span class="w"> </span><span class="n">boolean</span><span class="w"> </span><span class="n">toggle3</span><span class="p">();</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">toggle3</span><span class="p">:()</span><span class="n">Z</span><span class="p">:</span>
<span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="n">regs</span><span class="p">:</span><span class="w"> </span><span class="mi">0002</span><span class="p">;</span><span class="w"> </span><span class="n">ins</span><span class="p">:</span><span class="w"> </span><span class="mi">0001</span><span class="p">;</span><span class="w"> </span><span class="n">outs</span><span class="p">:</span><span class="w"> </span><span class="mi">0000</span>
<span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">aload_0</span><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">iget</span><span class="o">-</span><span class="n">boolean</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">mB</span><span class="p">:</span><span class="n">Z</span>
<span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">getfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z 2: if-nez v0, 0008 // +0006</span>
<span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">ifne</span><span class="w"> </span><span class="mi">12</span><span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 1 // #1</span>
<span class="w"> </span><span class="mi">8</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_1</span><span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">iput</span><span class="o">-</span><span class="n">boolean</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">mB</span><span class="p">:</span><span class="n">Z</span>
<span class="w"> </span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">13</span><span class="w"> </span><span class="mi">7</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">v0</span>
<span class="w"> </span><span class="mi">12</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_0</span><span class="w"> </span><span class="mi">8</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 0 // #0</span>
<span class="w"> </span><span class="mi">13</span><span class="p">:</span><span class="w"> </span><span class="n">dup_x1</span><span class="w"> </span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">0005</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">-</span><span class="mi">0004</span>
<span class="w"> </span><span class="mi">14</span><span class="p">:</span><span class="w"> </span><span class="n">putfield</span><span class="w"> </span><span class="c1">#5; //Field mB:Z </span>
<span class="w"> </span><span class="mi">17</span><span class="p">:</span><span class="w"> </span><span class="n">ireturn</span>
</code></pre></div>
<ul>
<li>The original Java bytecode is terrible.</li>
<li>Using a local variable replaces <code>aload-…-putfield-aload-getfield</code> with <code>istore-aload-iload-putfield-iload</code>. That's two more instructions just to use a local variable (no wonder Java interpreters are slow!).</li>
<li>Using the result of the assignment replaces <code>aload-…-putfield-aload-getfield</code> with <code>aload-…-dup-putfield</code>, thus saving one instruction (and member-variable load).</li>
</ul>
<p><code>dx</code> translates Java's stack and local variable accesses into Dalvik register operations. Since there's no longer a differentiation between the stack and local variables, <code>dup</code>, <code>iload</code>, and <code>istore</code> should all get compiled into <code>move</code>. Let's see what actually happens:</p>
<ul>
<li>The original Java bytecode turns into equally terrible Dalvik bytecode. It's safe to cache<code>mRunning</code> since it's not <code>volatile</code>, but <code>dx</code> doesn't realise this.</li>
<li>Both the local-variable and assignment-result versions produce the <em>exact same</em> Dalvik bytecode, replacing the second <code>iget-boolean</code> with nothing. The local-variable access is optimised away!<sup id="fnref:4"><a class="footnote-ref" href="#fn:4">4</a></sup></li>
</ul>
<p>So neither javac nor dx get rid of the second member-variable read, but dx optimises away local variable usage. We knew this -- Google told us. This still doesn't explain why boolean negation results in a compare-and-branch; it <em>should</em> just be a XOR. Let's look at some more code:</p>
<div class="highlight"><pre><span></span><code><span class="nv">public</span><span class="w"> </span><span class="nv">static</span><span class="w"> </span><span class="nv">final</span><span class="w"> </span><span class="nv">boolean</span><span class="w"> </span><span class="nv">not1</span><span class="ss">(</span><span class="nv">boolean</span><span class="w"> </span><span class="nv">b</span><span class="ss">)</span><span class="w"> </span>{
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">!</span><span class="nv">b</span><span class="c1">;</span>
}
<span class="nv">public</span><span class="w"> </span><span class="nv">static</span><span class="w"> </span><span class="nv">final</span><span class="w"> </span><span class="nv">boolean</span><span class="w"> </span><span class="nv">not2</span><span class="ss">(</span><span class="nv">boolean</span><span class="w"> </span><span class="nv">b</span><span class="ss">)</span><span class="w"> </span>{
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nv">b</span><span class="w"> </span><span class="o">^</span><span class="w"> </span><span class="nv">true</span><span class="c1">;</span>
}
</code></pre></div>
<p>The difference in bytecode is staggering:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">Java</span><span class="w"> </span><span class="n">Dalvik</span>
<span class="n">boolean</span><span class="w"> </span><span class="n">not1</span><span class="p">(</span><span class="n">boolean</span><span class="p">):</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">not1</span><span class="p">:(</span><span class="n">Z</span><span class="p">)</span><span class="n">Z</span><span class="p">:</span>
<span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">iload_0</span><span class="w"> </span><span class="n">regs</span><span class="p">:</span><span class="w"> </span><span class="mi">0002</span><span class="p">;</span><span class="w"> </span><span class="n">ins</span><span class="p">:</span><span class="w"> </span><span class="mi">0001</span><span class="p">;</span><span class="w"> </span><span class="n">outs</span><span class="p">:</span><span class="w"> </span><span class="mi">0000</span>
<span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">ifeq</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="k">if</span><span class="o">-</span><span class="n">eqz</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="mi">0004</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">+</span><span class="mi">0004</span>
<span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_0</span><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 0 // #0</span>
<span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">3</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">v0</span>
<span class="w"> </span><span class="mi">8</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_1</span><span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="k">const</span><span class="o">/</span><span class="mi">4</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 1 // #1</span>
<span class="w"> </span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">ireturn</span><span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">goto</span><span class="w"> </span><span class="mi">0003</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">-</span><span class="mi">0002</span>
<span class="n">boolean</span><span class="w"> </span><span class="n">not2</span><span class="p">(</span><span class="n">boolean</span><span class="p">):</span><span class="w"> </span><span class="n">Test</span><span class="o">.</span><span class="n">not2</span><span class="p">:(</span><span class="n">Z</span><span class="p">)</span><span class="n">Z</span><span class="p">:</span>
<span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">iload_0</span><span class="w"> </span><span class="n">regs</span><span class="p">:</span><span class="w"> </span><span class="mi">0002</span><span class="p">;</span><span class="w"> </span><span class="n">ins</span><span class="p">:</span><span class="w"> </span><span class="mi">0001</span><span class="p">;</span><span class="w"> </span><span class="n">outs</span><span class="p">:</span><span class="w"> </span><span class="mi">0000</span>
<span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">iconst_1</span><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">xor</span><span class="o">-</span><span class="nb nb-Type">int</span><span class="o">/</span><span class="n">lit8</span><span class="w"> </span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="c1">#int 1 // #01</span>
<span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">ixor</span><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">v0</span>
<span class="w"> </span><span class="mi">3</span><span class="p">:</span><span class="w"> </span><span class="n">ireturn</span>
</code></pre></div>
<p>javac seems to compile <code>!b</code> as <code>b ? false : true</code>. dx isn't clever enough to realise that bools can only be 0 or 1 and optimise it into a XOR.</p>
<p>At the end of the day, I settled on <code>if (mRunning ^= true) { ... }</code>. It's probably terrible "code style", but it means there are less things to get wrong when I end up copy-and-pasting it.</p>
<p>I tried similar code<sup id="fnref:6"><a class="footnote-ref" href="#fn:6">6</a></sup> with gcc -std=c99 for x86, ARM, and Thumb. GCC seems to treat <code>!b</code> exactly like <code>c == 0</code>, and <code>b^true</code> to <em>nearly</em> like <code>c == 1</code> or <code>(bool)(c^1)</code> (but not like <code>(c^1) != 0</code>). <code>b == 2</code> is optimised away to 0 (even on -O0) so it knows that bools can only be 0 or 1, but it seems that bool is mostly treated unsigned char for reading.</p>
<p><strong>In summary?</strong></p>
<ul>
<li>With the Android SDK, member variables access is expensive, but local variable "allocation" is free.</li>
<li>A built-in boolean type might not optimise as well as you'd expect, even for "mature" compilers like GCC.</li>
<li>While you might blame inefficiency on javac, gcj does not do much better.</li>
<li>Most importantly, look at compiler output before you try to "optimise" your source code. You'll probably be surprised.</li>
</ul>
<p>In the next issue I'll look at the register allocator.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>The Dalvik VM is designed to conserve memory, which is one of the reasons it doesn't use a JIT. Interestingly, Dalvik bytecode is about 30% larger than the corresponding Java bytecode, since Dalvik instructions are larger. Try <a href="http://sites.google.com/site/io/dalvik-vm-internals">Dalvik VM internals</a>@Google I/O for a productive use of YouTube. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>Inefficient stack-based bytecode is <em>so easy</em> to generate -- take the parse tree, output it in reverse-polish notation, and <em>voila</em>: A=B+(C<em>D) translates to load B, load C, load D, mul, add, store A. If you don't optimise, reversing this is easy: apply a bunch of rules like load X, load Y, mul -> X</em>Y. If you optimise A=A+A*A to load A, dup, dup, mul, add, store A, you need to do a lot more work to extract any meaning out of it. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p><a href="http://gcc.gnu.org/java/">GCJ</a> doesn't optimise either -- it produces nearly the same terrible code. <a href="http://jikes.sourceforge.net/">Jikes</a> compiles <code>!b</code> as <code>b^true</code>, but this is due to a better parse-tree-to-bytecode translation rule for <code>!</code>, not an optimisation pass -- the --optimize option is ignored according to the man page. <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p>A naive Java-to-Dalvik compiler could use one register for each local variable and one more for each "slot" of stack space (Java bytecode methods specify their maximum stack size), but this would cause a lot of unnecessary copying.
<a href="http://android.git.kernel.org/?p=platform/dalvik.git;a=blob;f=dx/src/com/android/dx/ssa/Optimizer.java;h=cee6d7b37d293313fa54fffd5b12af3e7564b22e;hb=cd18d5743d1957b1c1f57c0abe7ea28187c63a9f">com.android.dx.ssa.Optimizer</a> converts code to SSA form and performs move-combining, constant popagation, "literal upgrade", "constant collection", dead code removal, and register allocation on conversion back to register-op form. It's <a href="http://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/">pretty basic</a> and quite effective -- dx --no-optimize results in about 55% larger code<sup id="fnref:5"><a class="footnote-ref" href="#fn:5">5</a></sup> -- but <em>these are the only optimisations performed</em> as far as I can tell. <a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:5">
<p>dexdump classes.dex | grep 'insns size' | cut -d: -f2 | cut -d' ' -f2 | python -c 'import sys; print reduce(lambda a,b:a+int(b),sys.stdin.read().split(),0)' <a class="footnote-backref" href="#fnref:5" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:6">
<p>Actually <code>_Bool</code> and <code>((_Bool)1)</code>, defined in C99. It means I don't have to worry about <stdbool.h>. <a class="footnote-backref" href="#fnref:6" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
</ol>
</div>App Store Feedback2009-03-03T16:30:00+00:002009-03-03T16:30:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-03:/blog/2009/03/03/app-store-feedback/<p>A lot of developers have complained about the App Store feedback process. For one thing, nothing prevents a rival developer leaving a bad review. For another, Apple only <a href="http://www.appletell.com/apple/comment/iphone-22-software-requires-app-rating-with-deletion/">solicit reviews from dissatisfied users</a>. What can be done to get better reviews to counter-balance the bad ones? Maybe we could <a href="http://infinite-labs.net/blog/l0solicitreview-or-asking-happy-users-to-share-their-joy">invite …</a></p><p>A lot of developers have complained about the App Store feedback process. For one thing, nothing prevents a rival developer leaving a bad review. For another, Apple only <a href="http://www.appletell.com/apple/comment/iphone-22-software-requires-app-rating-with-deletion/">solicit reviews from dissatisfied users</a>. What can be done to get better reviews to counter-balance the bad ones? Maybe we could <a href="http://infinite-labs.net/blog/l0solicitreview-or-asking-happy-users-to-share-their-joy">invite happy users to leave a review...</a> </p>
<p>I'm a little sceptical that this will work. As a developer it's easy to imagine that a user will be happy to sing the praises of your application. As a user, however, my time is precious, and it's just too easy to hit the cancel button. Having a "Review this app" button on the Info page might work better -- I'd be interested to see what people think though!</p>Valgrind for iPhone2009-03-03T15:46:00+00:002009-03-03T15:46:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-03:/blog/2009/03/03/valgrind-for-iphone/<p>A friend of mine over at <a href="http://taptu.com">Taptu Mobile Search</a> pointed me to <a href="http://valgrind.org">Valgrind</a>. Valgrind is a debugging and profiling tool, and was <a href="http://www.sealiesoftware.com/valgrind/">ported to OS X</a> by <a href="http://www.sealiesoftware.com/">Greg Parker</a>. It works for iPhone development too (simulator only) with a <a href="http://landonf.bikemonkey.org/code/iphone/iPhone_Simulator_Valgrind.20081224.html">simple mod</a>. We haven't used it in anger yet at Airsource …</p><p>A friend of mine over at <a href="http://taptu.com">Taptu Mobile Search</a> pointed me to <a href="http://valgrind.org">Valgrind</a>. Valgrind is a debugging and profiling tool, and was <a href="http://www.sealiesoftware.com/valgrind/">ported to OS X</a> by <a href="http://www.sealiesoftware.com/">Greg Parker</a>. It works for iPhone development too (simulator only) with a <a href="http://landonf.bikemonkey.org/code/iphone/iPhone_Simulator_Valgrind.20081224.html">simple mod</a>. We haven't used it in anger yet at Airsource, but we'll be sure to report results when we do....</p>QR Codes in Japan2009-03-03T15:39:00+00:002009-03-03T15:39:00+00:00Ben Blaukopftag:airsource.co.uk,2009-03-03:/blog/2009/03/03/qr-codes-in-japan/<p>I was interested to discover that <a href="http://whatjapanthinks.com/2007/10/30/qr-code-usage-in-japan/">at the end of 2007, nearly 90% of sampled Japanese users</a> knew what a QR code was - pretty impressive compared to my informal survey results in Cambridge, UK where just about no one outside of the techie world has heart of them, let alone …</p><p>I was interested to discover that <a href="http://whatjapanthinks.com/2007/10/30/qr-code-usage-in-japan/">at the end of 2007, nearly 90% of sampled Japanese users</a> knew what a QR code was - pretty impressive compared to my informal survey results in Cambridge, UK where just about no one outside of the techie world has heart of them, let alone knows what they are. I knew the figure would be high - but not that high...</p>Optiscan Video2009-02-26T17:02:00+00:002009-02-26T17:02:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-26:/blog/2009/02/26/optiscan-video/<p>There is now a short video demonstrating the scanning process with Optiscan, at <a href="http://www.youtube.com/watch?v=h9pB-i0xQKs">http://www.youtube.com/watch?v=h9pB-i0xQKs</a></p>Optiscan Sale2009-02-25T11:04:00+00:002009-02-25T11:04:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-25:/blog/2009/02/25/optiscan-sale/<p>We are about to release the next version of <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>, featuring improved image processing algorithms, internationalisation (including Japanese), and bug fixes. It has been submitted to Apple, and will go on sale at the normal Optiscan price of $4.99. BUT - you can get Optiscan <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">right now</a> for $3.99 …</p><p>We are about to release the next version of <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a>, featuring improved image processing algorithms, internationalisation (including Japanese), and bug fixes. It has been submitted to Apple, and will go on sale at the normal Optiscan price of $4.99. BUT - you can get Optiscan <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">right now</a> for $3.99, and benefit from the reduced price AND free updates.</p>Why OVI beats the App Store2009-02-25T11:01:00+00:002009-02-25T11:01:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-25:/blog/2009/02/25/why-ovi-beats-the-app-store/<p>Finally, we have a <a href="http://www.ovi.com">distribution mechanism for S60 applications</a>. Despite all the problems with developing for S60 -- the steep learning curve, the painful testing procedure, the complex UIs -- the primary barrier to releasing an S60 product has always been the same. How do we sell the thing?! The success of …</p><p>Finally, we have a <a href="http://www.ovi.com">distribution mechanism for S60 applications</a>. Despite all the problems with developing for S60 -- the steep learning curve, the painful testing procedure, the complex UIs -- the primary barrier to releasing an S60 product has always been the same. How do we sell the thing?! The success of the App Store is purely driven by the ease of distribution<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>, allowing a developer to ship applications around the world at the touch of a button. Prior to OVI, the only way to sell an app on S60 was to either do it yourself (painful and expensive), use <a href="http://www.handango.com">Handango</a> (which takes a 50% revenue share!), or talk to an operator (requires the patience of Job). </p>
<p>Obviously it is going to be harder to develop an S60 application than an iPhone one. But the S60 platform is so much more capable than the iPhone that we can hope to see seriously innovative applications out there. Admittedly these will be priced at a premium level. But quality applications will drive a market, and increase the popularity of the S60 platform. It will be interesting to see whether an S60 phone becomes a status symbol in the same way that iPhone has done - and whether the applications you have on your idle screen become as telling as they do on iPhone!</p>
<p>Time will tell. Airsource will certainly be watching OVI with interest.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>Let's be clear what we mean here. Distribution is not the same as Marketing. You still need to market your applications, no matter how you sell them. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Why the App Store beats OVI2009-02-24T17:39:00+00:002009-02-24T17:39:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-24:/blog/2009/02/24/why-the-app-store-beats-ovi/<p>I have just spent four days in Barcelona at MWC catching up on the industry developments. The main topic on everyone's lips was The App Store, and by "The" App Store I mean Apple's, not Nokia's OVI offering or any of the other contenders.</p>
<p>Let's take a little look at …</p><p>I have just spent four days in Barcelona at MWC catching up on the industry developments. The main topic on everyone's lips was The App Store, and by "The" App Store I mean Apple's, not Nokia's OVI offering or any of the other contenders.</p>
<p>Let's take a little look at <a href="http://www.ovi.com">OVI</a>. Nokia claim that there is an initial market of 50 million handsets. I disagree. Unless Carphone Warehouse go out onto the high street and start pulling people into their store and upgrading their phones for them, the vast majority of smart phone users are not going to install the OVI software. There are two reasons. Firstly, the entire motivation for an App Store on device is that users are not prepared to go through the pain of downloading software and installing it. So why, exactly, are they going to download software (OVI) and install it? Secondly, developing applications for S60<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup> is simply a lot harder than developing them for iPhone, and takes more time. Moreover, those applications need to be tested on the entire range of OVI devices. So the applications either need to cost more (for the same number of sales), or have a wider market (which they haven't got). With over 10 million iPhones out there, all with an active user population downloading and buying applications, Nokia need a very substantial uptake of their S60 handsets just to get applications shipping at anywhere near the same price level. </p>
<p>It isn't going to happen. You read it here first.</p>
<p>Tomorrow - why I think that OVI beats the App Store!</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>I realise that OVI supports S40 -- i.e. MIDP -- as well. I am ignoring MIDP since you can't do anything that useful in it (games notwithstanding). It's even more restrictive than the iPhone SDK. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Optiscan user feedback2009-02-19T11:03:00+00:002009-02-19T11:03:00+00:00John Earltag:airsource.co.uk,2009-02-19:/blog/2009/02/19/optiscan-user-feedback/<p>One of the features of Optiscan about which we thought quite carefully prior to the initial release was how to stay connected to our users. We included several entry points through which our users can send us feedback when they have trouble with the application.</p>
<p>Here is some analysis of …</p><p>One of the features of Optiscan about which we thought quite carefully prior to the initial release was how to stay connected to our users. We included several entry points through which our users can send us feedback when they have trouble with the application.</p>
<p>Here is some analysis of the user reports we've had, some of which contributed to the <a href="http://blog.airsource.co.uk/index.php/2009/02/13/optiscan-upcoming-features/">new features</a> we've been working on for our first post-1.0 release, which should be out shortly.</p>
<ul>
<li>Several users have reported a <strong>crash when selecting a contact created by Google sync</strong>. Contacts <a href="http://www.google.com/mobile/apple/sync.html">synced from gmail</a> using Google's Exchange server appear to have email addresses with no label (such as "other", "work", "home", etc.). If you select such a contact, Optiscan 1.0.2 will crash. We have a fix for this issue but it will be a few more days before it's available on the App Store. <strong>Work-around</strong>: add a label to your contact's email address, or simply sync via a different method for the moment.</li>
<li><strong>What can we do with a QR code generated on the iPhone?</strong> During our pre-release testing, one of our beta testers asked this question. They wanted to (e.g.) email it to someone, or save it to their photo album. We didn't add this feature for the initial release because we didn't really believe it – we thought it would be obvious that sharing a QR code is something you do <em>in person</em>, with someone else with scanning the code you have displayed on your screen. We were, of course, completely wrong. Several reviews and feedback emails made this very clear to us. Chastened, we've added a feature to save a generated QR code to the photo album to the next release. At this point we're not planning to support direct export by email due to limitations in the iPhone SDK (you will still have to go via the Photos app). If Apple opens up more functionality to us, we will issue an update allowing you to email straight from the app. <strong>Work-around</strong>: in the mean time, you can just take a screenshot and email that.</li>
<li><strong>Failed scans</strong>. Although we've found and fixed a few small image processing bugs identified as a result of user reports, Optiscan's QR code scanning is actually pretty good. Most of the reports we get turn out to be people scanning visual codes that are not QR codes, or scanning QR codes that have been printed quite small, so that the iPhone camera struggles to cope. As for the former problem, we're considering adding support for other 2D barcode formats, but the challenge when doing so is to avoid slowing down the scan process too much; this may take a bit longer to sort out. As for the latter, we have yet to add software deblurring, so in the meantime must refer users who really need this functionality to <a href="http://www.griffintechnology.com/products/clarifi">Griffin Clarifi</a>, which adds an extra lens to help the iPhone camera focus at short range.</li>
<li><strong>Bad QR code generators</strong>. For instance, one user has sent us a picture of a QR code with the version bits inserted in reverse order (we're introducing a work-around, ignoring the version bits where they're clearly invalid). In another case, a <a href="http://www.mobile-barcodes.com/qr-code-generator/">certain website</a> claims to generate QR codes encoding vcards but instead generates something that is a slightly broken and hence unparseable VCard (at least at present). There's not a lot we can do about these, except try to extract email addresses and phone numbers that might be of use.</li>
</ul>
<p>So in summary, if something doesn't work, tell us and we'll fix it. If Optiscan doesn't do what you want it to, tell us, and we'll fix that too.</p>Optiscan - Upcoming Features2009-02-13T14:57:00+00:002009-02-13T14:57:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-13:/blog/2009/02/13/optiscan-upcoming-features/<p>We've had quite a lot of feedback about Optiscan, from various sources. Where possible we've replied, but in some cases email addresses haven't been correct, and in others no reply is possible -- such as on the App Store Reviews. </p>
<p>We've had quite a lot of feedback about Optiscan, from various sources. Where possible we've replied, but in some cases email addresses haven't been correct, and in others no reply is possible -- such as on the App Store Reviews. </p>
<p>We're currently working on an update for Optiscan which will include the following features:</p>
<ul>
<li>Handle QR codes with invalid version bits -- we found that there is at least one generator out there which produces technically invalid codes. Since it's possible for us to support these, we will.</li>
<li>Add a setting for "When code recognised, just launch the URL with no confirmation query".</li>
<li>Improve handling of partially shadowed images.</li>
<li>Improve handling of blurry images.</li>
<li>Localization into Japanese and several other languages.</li>
<li>Tap-and-hold to save a barcode to the gallery.</li>
</ul>
<p><img alt="primaryscreenshot" src="https://airsource.co.uk/blog/images/2009/02/primaryscreenshot.thumbnail.jpg"></p>
<p>We are, of course, still collecting feedback at <a href="mailto:optiscan-feedback@airsource.co.uk">optiscan-feedback@airsource.co.uk</a></p>What is Optiscan?2009-02-12T16:01:00+00:002009-02-12T16:01:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-12:/blog/2009/02/12/what-is-optiscan/<p>We've added a brief FAQ for Optiscan, which is available <a href="http://blog.airsource.co.uk/index.php/what-is-optiscan/">here</a>.</p>Monetizacommercial ifuggedaboutit2009-02-11T11:10:00+00:002009-02-11T11:10:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-11:/blog/2009/02/11/monetizacommercialifuggedaboutit/<p>An old colleague of mine who used to work at Microsoft told me that he once went to a global summit, to find people standing on tables shouting "Show Me The Money". As a business strategy, it doesn't work too badly. It fell out of favour in the 2000 bubble …</p><p>An old colleague of mine who used to work at Microsoft told me that he once went to a global summit, to find people standing on tables shouting "Show Me The Money". As a business strategy, it doesn't work too badly. It fell out of favour in the 2000 bubble, but we're all older and wiser now. Right?</p>
<p>I went down to London last night along with Nick (CEO) to visit the Mobile Monday event, rescheduled from last week due to England's inability to cope with any snow. It was kindly sponsored by <a href="http://www.ikivo.com">Ikivo</a> and <a href="http://www.omtp.org/">OMTP</a>. Unfortunately we didn't take any pictures as we were all too busy playing with <a href="https://airsource.co.uk/blog/2009/02/06/announcing-optiscan/">Optiscan</a> on our phones, but we did listen to the speakers. </p>
<p>The general theme of the evening was the construction of a widgets platform supporting mobile, but not exclusively so, and incorporating all sorts of fun things. One aspect seemed to be lacking from the discussion though, and come the Q&A, both our hands shot up. Nick got the mike and said "Sounds great. But how do I make money out of it". </p>
<p>We just launched Optiscan on iPhone, and one of the key reasons for choosing that platform was the way we can talk to one person - Apple - and sell around the world. If we want to get operator tie in on just about any other platform we need to go talk to umpteen different operators, and maybe ship in six months time if we're very lucky. Apple takes a week or two. It's just easy, and lets us focus on development and on marketing.</p>
<p>An open widgets platform is fantastic - provided that all the operators and manufacturers get together and implement it in the same way, with exactly the same financial structures, and no subtle differences to APIs such as we see with BREW or MIDP. If it happens, great. But in the meantime, I won't be holding my breath. I'll be following the money.</p>Announcing - Optiscan!2009-02-06T16:11:00+00:002009-02-06T16:11:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-06:/blog/2009/02/06/announcing-optiscan/<p><a href="http://www.airsource.co.uk">Airsource</a> are delighted to announce that <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> is now available for purchase.</p>
<p><a href="http://www.airsource.co.uk">Airsource</a> are delighted to announce that <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">Optiscan</a> is now available for purchase.</p>
<p><img alt="primaryscreenshot" src="https://airsource.co.uk/blog/images/2009/02/primaryscreenshot.thumbnail.jpg"></p>
<p>Optiscan is a professional quality <a href="http://en.wikipedia.org/wiki/QR_Code">QR Code</a> tool, featuring by far the fastest scanner available on the App Store. Optiscan automatically locates and scans QR codes in the camera viewfinder. No more failed scans or blurry barcodes. Scans well from paper, monitors and other device screens.</p>
<ul>
<li>Share contacts, web addresses and text with other devices.</li>
<li>Scan a wide variety of QR code data formats, including locations, email addresses and phone numbers.</li>
<li>Save specific QR codes for quick sharing -- perfect for sharing your business card.</li>
<li>Keeps a history of QR codes created and shared for easy recall.</li>
<li>Want to scan or generate codes in French? Japanese? No problem. Optiscan supports UTF-8, ISO-8859, and Shift-JIS.</li>
<li>Select the contact details you want to send, to ensure the right people get the right information.</li>
<li>Safari lets you save images (tap and hold the image) to the photo gallery - and Optiscan will decode them for you.</li>
</ul>
<p>Optiscan runs without a network connection, and keeps your data private. Why put up with anything less?</p>
<p>Optiscan is available right now from the <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304099767&mt=8">AppStore</a>, priced at $4.99, £2.99, or €3.99 If you'd like to review the application, then promotional iTunes codes are available. Please <a href="mailto:sales@airsource.co.uk">email</a>, mentioning the website where your review will be posted.</p>AppStore Research with Mobclix2009-02-04T11:35:00+00:002009-02-04T11:35:00+00:00Ben Blaukopftag:airsource.co.uk,2009-02-04:/blog/2009/02/04/appstore-ratings/<p>I discovered an incredibly useful resource yesterday - <a href="http://www.mobclix.com">Mobclix</a>. Among other things, they let you see a graph of how your iPhone App Store application - or anyone else's - is getting on. For instance, here's <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=302556126&mt=8">Home Barista</a>.<p>I discovered an incredibly useful resource yesterday - <a href="http://www.mobclix.com">Mobclix</a>. Among other things, they let you see a graph of how your iPhone App Store application - or anyone else's - is getting on. For instance, here's <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=302556126&mt=8">Home Barista</a>.</p>
<p><img alt="Home Barista Stats" src="https://airsource.co.uk/blog/images/2009/02/picture-11.png"></p>
<p>You can also see different views onto lists of applications like this:</p>
<p><img alt="Lifestyle Top Applications" src="https://airsource.co.uk/blog/images/2009/02/picture-12.png"></p>
<p>So I did a bit of playing around. I looked at the following categories: Business, Lifestyle, Navigation, Productivity, and Utility, looking only at applications which listed those categories as their primary I excluded free applications, specified the US region, and noted the highest ranked 5* application, and the range of ratings of the top 10 ranked applications. The test was carried out between 10 and 10.15am GMT 4th Feb 2009. I exercised some arbitrary judgement when I felt that applications fitted better in a different category.</p>
<p>Here are my results.</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>Ranking of top 10 apps</th>
<th>Highest ranked app with 5*</th>
</tr>
</thead>
<tbody>
<tr>
<td>Business</td>
<td>2.0 - 3.5</td>
<td><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=299986558&mt=8">CalcPad Smart Calculator</a> (223, $2.99)*</td>
</tr>
<tr>
<td>Lifestyle</td>
<td>2.5 - 3.5</td>
<td><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=303601203&mt=8">Random Fun Facts</a> (257, $0.99)</td>
</tr>
<tr>
<td>Navigation</td>
<td>2.0 - 5.0</td>
<td><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=301656943&mt=8">GPS Formats</a> (232, $0.99)*</td>
</tr>
<tr>
<td>Productivity</td>
<td>3.0 - 3.5</td>
<td><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=285299118&mt=8">Lunar Calendar</a> (410, $4.99)*</td>
</tr>
<tr>
<td>Utilities</td>
<td>2.0 - 4.0</td>
<td><a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=301676912&mt=8">Kitchen Calculator</a> (567, $1.99)</td>
</tr>
</tbody>
</table>
<p>Notes:</p>
<ul>
<li>Excluded Quad Level (153, $0.99) as it belongs in Utilities</li>
<li>Excluded V-Cockpit GPS (102, $3.99) as it belongs in Entertainment</li>
<li>Excluded Affirmations (407, $0.99) as it belongs in Lifestyle</li>
</ul>
<p>There are some interesting things in these results. It looks like a 2* rated application can make the top 10. Looking at the actual reviews, though, all the recent reviews for high-ranked applications tend to be 4* or 5*, with very few low ratings. Taking <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=297870221&mt=8">Craigsphone</a> as an example (9 in LIfestyle, 2*), the reviews are by and large very good, except for a couple of people who clearly can't work out how to turn the content filter off.</p>
<p>Looking at the 5* rated applications, it's obvious that none of them are selling that well. Why? Well my suspicion is that someone who buys an app ranked 563 has looked for the app, and is going to read the description carefully to make sure it does what they want. Someone who buys from the Top 25/50/100 may well be buying on impulse, and is more likely to be disappointed because the application doesn't meet their expectations - and leave a bad review. In short, if you're successful, it's practically impossible to get a 5* rating. There's always someone who hates a star performer!</p>Androidinous Intentions2009-02-03T12:46:00+00:002009-02-03T12:46:00+00:00Teanlorg Chantag:airsource.co.uk,2009-02-03:/blog/2009/02/03/androidinous-intentions/<p>So you've acquired an <a href="http://code.google.com/android/dev-devices.html">Android Dev Phone 1</a> (the page goes to great lengths to avoid telling you that <em>it's just an unlocked G1</em>). You've downloaded Eclipse, installed the Android plugin, and created a "MyProject" app that says "Hello World, MyProject".</p>
<p>Now what?</p>
<p>Well, if you've been paying attention, you'll have heard of two (or three) new paradigms:</p>
<ul>
<li><a href="http://code.google.com/android/intro/appmodel.html#Tasks">Tasks</a> correspond vaguely to the user's idea of accomplishing something, like "sending an e-mail", and an associated "activity stack" which is hidden from the programmer.</li>
<li><a href="http://code.google.com/android/reference/android/app/Activity.html">Activities</a> correspond loosely to subtasks, like "picking a contact".</li>
<li><a href="http://code.google.com/android/reference/android/content/Intent.html">Intents</a> are issued by activities (like SendEmailActivity) to launch other activities (like ContactPickerActivity).</li>
</ul>
<p><strong>But that's about it.</strong> UI programming is traditionally a pain, which is why I like the iPhone SDK's InterfaceBuilder -- <p>So you've acquired an <a href="http://code.google.com/android/dev-devices.html">Android Dev Phone 1</a> (the page goes to great lengths to avoid telling you that <em>it's just an unlocked G1</em>). You've downloaded Eclipse, installed the Android plugin, and created a "MyProject" app that says "Hello World, MyProject".</p>
<p>Now what?</p>
<p>Well, if you've been paying attention, you'll have heard of two (or three) new paradigms:</p>
<ul>
<li><a href="http://code.google.com/android/intro/appmodel.html#Tasks">Tasks</a> correspond vaguely to the user's idea of accomplishing something, like "sending an e-mail", and an associated "activity stack" which is hidden from the programmer.</li>
<li><a href="http://code.google.com/android/reference/android/app/Activity.html">Activities</a> correspond loosely to subtasks, like "picking a contact".</li>
<li><a href="http://code.google.com/android/reference/android/content/Intent.html">Intents</a> are issued by activities (like SendEmailActivity) to launch other activities (like ContactPickerActivity).</li>
</ul>
<p><strong>But that's about it.</strong> UI programming is traditionally a pain, which is why I like the iPhone SDK's InterfaceBuilder -- unfortunately, Android UI design is essentially Java-style code-based layout (dominated by <a href="http://images.google.com/images?q=gridbaglayout">boring, square layouts</a>) with XML <a href="http://code.google.com/android/reference/available-resources.html#layoutresources">layout resources</a> to save you some of the tediousness. The nearest equivalent of InterfaceBuilder is <a href="http://www.droiddraw.org/">DroidDraw</a>, but it's buggy and confusing, and it's easier to just use LinearLayout and RelativeLayout in XML since you need a scalable UI1 -- unlike WYSIWYG web design, you can't just say "best viewed at 1024×768" and ignore the people stuck at 640×480 when the largest common screen size is 320×480.</p>
<p><strong>Intents don't really <em>look</em> integrated,</strong> even though that's the whole point -- an Intent usually starts a new full-screen Activity, even if you wanted a "recent contacts" popup menu. The only way to reuse third-party controls (which Android calls <a href="http://code.google.com/android/reference/android/view/View.html">views</a>) is to copy-paste code,2 causing code-duplication which they've <a href="http://sites.google.com/site/io/dalvik-vm-internals">tried so hard to avoid</a>. And since the included UI widgets are a bit minimalistic, you often need to reinvent the wheel:</p>
<ul>
<li>Want a text field with a clear button? Tough. The Android way is to click-and-hold, select "select all", and press DEL (backspace). I was hammering DEL for days.</li>
<li>Want to handle tapping, double-tapping,3 or dragging? Many standard widgets will convert touch events into onClick and onLongClick callbacks, but for a custom control, you're on your own.</li>
<li>Unlike on the iPhone,4 very little touch preprocessing occurs, so the touch point is in the wrong place and you have to guess if two taps are close enough to count as a double-click.</li>
<li>You need to set a timer for long-clicks and double-clicks, but it's not clear <em>which</em> timer is suitably thread-safe.5 There's an <a href="http://code.google.com/android/reference/android/view/MotionEvent.html#ACTION_CANCEL">ACTION_CANCEL</a> event which you presumably get on a phone call during a long click, but how about between the first and second click of a double-click? The <a href="http://code.google.com/search/#p=android&q=ACTION_CANCEL">documentation</a> isn't very helpful.</li>
</ul>
<p><strong>Documentation is the usual confusing API reference</strong> (like Javadoc, but less ugly) with occasional explanatory notes but no corresponding programming guide: Most Android devs will be familiar with the <a href="http://code.google.com/android/reference/android/app/Activity.html#ActivityLifecycle">activity lifecycle</a> diagram, but despite having read it, I still found <a href="http://uk.youtube.com/watch?v=TkPiXRNee7A">Dan Morrill's presentation</a> useful, only to realise that it was 7 months old and slightly obsolete (onFreeze() no longer exists, for a start).</p>
<p><strong>Some APIs are still fairly primitive,</strong> like Google's <a href="http://code.google.com/android/reference/com/google/android/maps/MapView.html">MapView</a>, which is in a separate library2 even though its images are bundled with the system resources. This is mostly an indicator that it's not all there yet6 -- it contains just enough to support the Maps application, which is less featureful than the iPhone's Maps app.</p>
<p>So what's good about Android?</p>
<ul>
<li><strong>There some good stuff under-the-hood:</strong> <a href="http://code.google.com/android/reference/android/os/package-summary.html">android.os</a> contains things which would have been nice to have in Java, like <a href="http://code.google.com/android/reference/android/os/CountDownTimer.html">CountDownTimer</a>, <a href="http://code.google.com/android/reference/android/os/ConditionVariable.html">ConditionVariable</a>, and <a href="http://code.google.com/android/reference/android/os/SystemClock.html">SystemClock</a>.7</li>
<li><strong>There are very few singletons.</strong> This lets two Activities share the same Dalvik VM, but also mean you're less likely to end up refactoring global-variable spaghetti.</li>
<li><strong>UI programming is still better than on S60:</strong>8 Custom controls just work (you don't have to worry about CONE or EIKON or UIKON or AVKON) and you don't have to write your own layout code. There are occasional NullPointerExceptions with unhelpful backtraces, but it's still better than <a href="http://www.symbian.com/developer/techlib/v9.2docs/doc_source/reference/SystemPanics/ConePanics.html#panics%2eCONE">CONE 14</a>.</li>
<li><strong>You can actually write background <a href="http://code.google.com/android/reference/android/app/Service.html">services</a></strong> -- the lack of background processing is one of the <a href="http://whydoeseverythingsuck.com/2008/06/iphone-background-processing-not-fixed.html">main gripes</a> about the iPhone SDK. Sadly, there's no easy way to see what's <a href="http://www.google.com/search?q=G1+battery">running down your battery</a>.</li>
<li><strong>There's a <a href="http://www.android.com/market/">marketplace</a>.</strong> When it supports paid apps, allegedly it'll be on par with the iPhone App Store. There's virtually nothing on other platforms: BREW apps effectively need operator approval, and I don't know anyone who's used <a href="http://www.download.nokia.com/">Nokia Download</a>.</li>
</ul>
<p><strong>Ultimately, Android still needs a lot of UI work.</strong> Of the apps I've seen, ShopSavvy has the best (and prettiest) UI -- later I realised that it looks and feels like an iPhone app (with a navigation bar and new views sliding in and everything!). They've obviously put a lot of effort into recreating what the iPhone does for you out of the box. Apple must be doing <em>something</em> right.</p>
<p>I'll finish with the usual deluge of footnotes:</p>
<ol>
<li>People using Android apps expect both portrait and landscape (due to the G1's keyboard) -- and upcoming Android phones will probably have different screen sizes -- so a scalable UI is a must. On the iPhone, you can get away with only supporting 320×480 portrait.</li>
<li>There's a <uses-library> tag for loading libraries which aren't already loaded, but no <provides-library> tag to share your own. The code seems to be loaded from /system/framework which is mounted read-only.</li>
<li>Double-tapping doesn't seem to be a standard UI paradigm on Android; neither is multi-touch, though <a href="http://www.ryebrye.com/blog/2008/11/17/proving-the-g1-screen-can-handle-multi-touch/">the G1 hardware supports it</a>. As a result, most UIs rely heavily on the menu button, which is <em>wonderfully</em> intuitive.</li>
<li>The touch point registered by a touch screen is usually somewhere unhelpfully obscured by your finger, so the iPhone does some preprocessing to move the touch point to where you think you touched. It's essential for anything smaller than about 32×32 and impractical for apps to do manually -- counting double-taps manually is comparatively easy.</li>
<li>Ideally you'd request a dummy touch event in 500 ms, automatically cancelled if something significant happens in the intervening time. All of the edge-cases are automatically handled correctly, and you don't need to worry about cancelling the timer.</li>
<li>Your activity must be a <a href="http://code.google.com/android/reference/com/google/android/maps/MapActivity.html">MapActivity</a> so it can't be one of the other subclasses. "Only one MapActivity is supported per process." The ItemizedOverlay is confusing and doesn't support long-clicks or dragging OverlayItems. The MapView doesn't produce onLongClick events or onClick events from the touch screen. The result is that you need to write a <em>lot</em> of code to even approach the iPhone's Maps app.</li>
<li><a href="http://code.google.com/android/reference/android/os/SystemClock.html#uptimeMillis()">SystemClock.uptimeMillis()</a> is "guaranteed monotonic", but the documentation also notes that "this value may get reset occasionally (before it would otherwise wrap around)". The underlying <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=libs/utils/SystemClock.cpp;h=2bdc0ce278a4708501c97bae835afca8f64acdf4;hb=54b6cfa9a9e5b861a9930af873580d6dc20f773c">code</a> seems to <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=libs/utils/Timers.cpp;h=2abc811a06e92f76761670db7991217b8754a3bb;hb=54b6cfa9a9e5b861a9930af873580d6dc20f773c">take a timespec</a>, convert to nanoseconds, and <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=include/utils/Timers.h;h=96103995bb4b696d15f99f4c22d477e7bc80c6ed;hb=54b6cfa9a9e5b861a9930af873580d6dc20f773c">divide by a million</a>, so it wraps when time_t does (about 68 years, instead of 292 million).</li>
<li>Have you ever tried a custom dialog on Symbian? You need to use a CEikDialog (to get softkeys/menus), add a "dialog line" with a custom control ID, implement a create-custom-control function to convert the ID into a custom control, stick <em>all your controls</em> into that custom control (CEikDialogs only does line-based layout), and override the dialog-sizing to subtract <em>exactly 2 pixels</em> which are inexplicably added to the dialog height.</li>
</ol>Thin versus Thick2009-01-30T17:20:00+00:002009-01-30T17:20:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-30:/blog/2009/01/30/thin-versus-thick/<p>Apparently, web apps may not be quite the cure-all that everyone thought they were. The <a href="http://weblog.infoworld.com/fatalexception/archives/2009/01/the_case_agains.html">linked article</a> gives five reasons why a browser-based app may not be the best idea.</p>
<p>Here's five of my own, related to cell phone web apps.</p>
<ul>
<li><strong>A website doesn't know where you are</strong>. Even the …</li></ul><p>Apparently, web apps may not be quite the cure-all that everyone thought they were. The <a href="http://weblog.infoworld.com/fatalexception/archives/2009/01/the_case_agains.html">linked article</a> gives five reasons why a browser-based app may not be the best idea.</p>
<p>Here's five of my own, related to cell phone web apps.</p>
<ul>
<li><strong>A website doesn't know where you are</strong>. Even the most basic phone can tell what country its in - but the most advanced website can only guess.</li>
<li><strong>Bandwidth is not free</strong>. It is both quicker and cheaper to perform simple actions (like drawing graphs) on device rather than incurring a round-trip to a server.</li>
<li><strong>C is not dead.</strong> Despite what Java aficionados may like to think, C-based languages still have a place in the world of embedded software, particularly where performance critical applications are concerned. On a cell-phone, just about everything is performance critical.</li>
<li><strong>Write Once, Run Anywhere doesn't work.</strong> You will have just as many platform bugs if you use web-based technologies - and they will be harder to find, not easier.</li>
<li><strong>A secret known to two people is no secret.</strong> If my private data is sat on my phone, then you pretty much have to get the phone out of my hands to get at my secrets. If however, the data is all on a website somewhere, then the number of potential vulnerabilities just sky rocketed. </li>
</ul>
<p>And one final thought. Ebay, Facebook, and Google Maps all started as web apps, and now have cell phone clients.</p>AppStore Pricing2009-01-29T17:17:00+00:002009-01-29T17:17:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-29:/blog/2009/01/29/appstore-pricing/<p>I just noticed that number 24 in the UK AppStore is <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=292591480&mt=8">mBoxMail</a> - a Hotmail client for the iPhone. That's impressive in its own right - you don't see many non-entertainment app that high in the AppStore. But the really interesting thing is the price - £5.99 ($9.99). Looks like there's …</p><p>I just noticed that number 24 in the UK AppStore is <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=292591480&mt=8">mBoxMail</a> - a Hotmail client for the iPhone. That's impressive in its own right - you don't see many non-entertainment app that high in the AppStore. But the really interesting thing is the price - £5.99 ($9.99). Looks like there's a good market out there for high quality applications - even at premium (for the AppStore) pricing.</p>Save the Bits - Part II2009-01-29T15:58:00+00:002009-01-29T15:58:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-29:/blog/2009/01/29/save-the-bits-part-ii/<p>Back in my original Save the Bits article, I noted that a foreign currency application on the iPhone, which I'll refer to as AppX, uploaded 16K of data and downloaded 136K just to render a graph. </p>
<p>I said I'd get back when I'd run a packet sniffer. I've just done that, and the results aren't pretty. <p>Back in my original Save the Bits article, I noted that a foreign currency application on the iPhone, which I'll refer to as AppX, uploaded 16K of data and downloaded 136K just to render a graph. </p>
<p>I said I'd get back when I'd run a packet sniffer. I've just done that, and the results aren't pretty. It uses HTTP, to talk to the server, which is as you'd expect. Writing a custom protocol is always painful. But instead of the logical thing to do, which is a short sweet transaction to get the data, and any associated metadata like an ad, like this.</p>
<blockquote>
<ul>
<li>Client: Hi, can I have some data</li>
<li>Server: Here's your data, and an ad to go with it</li>
<li>Client: (Draws graph)</li>
</ul>
</blockquote>
<p>What's actually going on is an extended chat, whereby AppX does something like this</p>
<blockquote>
<ul>
<li>Client: Hi</li>
<li>Server: Hi, have a cookie</li>
<li>Client: Here's my cookie</li>
<li>Server: Here's an ad for you to display</li>
</ul>
</blockquote>
<p>OK, not too many complaints so far, apart from the amount of to-ing and fro-ing which makes the most of all those round trip high latency transaction, but now it gets really fun:</p>
<blockquote>
<ul>
<li>Client: Hi again</li>
<li>Server: Here's a whopping great piece of Javascript code</li>
<li>Client: Can I have some more?</li>
<li>Server: Sure</li>
<li>Client: And an image to represent my graph? I don't fancy drawing all that data myself.</li>
<li>Server: No problem</li>
<li>Client: And maybe some more JavaScript?</li>
<li>Server: You got it</li>
</ul>
</blockquote>
<p>In other words, AppX isn't recently doing much itself. Instead it's fetching an entire JavaScript sub-application which it runs in a WebView and which fetches all the data and renders it. </p>
<p>No wonder it's slow. I've heard of <a href="http://www.dosits.org/animals/effects/img/songlength.gif">Whale Song</a>, but this is ridiculous...</p>Aftermarket Chargers2009-01-26T11:24:00+00:002009-01-26T11:24:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-26:/blog/2009/01/26/aftermarket-chargers/<p>Anyone with a Macbook knows that the MagSafe (TM) tip for the charger is pretty cool - apart from the fact that you can't get any third party accessories for it, because <a href="http://www.macworld.com/article/58064/2007/05/magsafe.html">Apple don't license it</a>. My charger recently started getting rather temperamental due to kink in the cable. The damage …</p><p>Anyone with a Macbook knows that the MagSafe (TM) tip for the charger is pretty cool - apart from the fact that you can't get any third party accessories for it, because <a href="http://www.macworld.com/article/58064/2007/05/magsafe.html">Apple don't license it</a>. My charger recently started getting rather temperamental due to kink in the cable. The damage was right next to the MagSafe (TM) tip, so I couldn't just chop the cable and reconnect. I had a look on ebay, and discovered that there were some cheaper chargers available, and bought one. It turned up pretty quickly, and worked just fine. Once. I paid just over £20 for something that costs £58 from the Apple store (having just checked) so I shouldn't be surprised that it wasn't genuine Apple. I didn't expect it to be made of cheese though. I cracked it open very easily, and desoldered the main lead and the tip with the intention of swapping it over to the original charger. No such luck. The Apple box was considerably better built, and resisted my attempts to open it. I ended up cutting the lead, and connecting the old wire to the new wire and tip (which at least appears to be reasonable quality). That worked perfectly, but next time I'll just go to the Apple Store - particularly because subsequent googling suggests that they <a href="http://support.apple.com/kb/TS1713?viewlocale=en_US">might</a> <a href="http://www.tuaw.com/2008/08/20/apple-replacing-frayed-magsafe-power-cables/">actually have replaced it for free</a>.</p>iPhone Simulator - hidden feature2009-01-22T12:24:00+00:002009-01-22T12:24:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-22:/blog/2009/01/22/iphone-simulator-hidden-feature/<p>One day I'll get round to reading the manual for all the devices I use on a day to day basis. No doubt I'll then discover lots of things I never knew - and from then on, life will be more productive, but more boring. In the meantime, I can continue …</p><p>One day I'll get round to reading the manual for all the devices I use on a day to day basis. No doubt I'll then discover lots of things I never knew - and from then on, life will be more productive, but more boring. In the meantime, I can continue to bump into cute features that surprise me. </p>
<p>I just discovered that if you drag a file to the iPhone Simulator, then it will open (or at least attempt to) the file in Safari. It works for images - very useful for <a href="http://ofcodeandmen.poltras.com/2008/11/04/adding-pictures-to-the-simulator/">adding images to the PhotoLibrary</a>. I've just tried it on a PDF, text, and HTML file, and they all worked as well. A lot easier than typing in the URL to your test site.</p>Announcing - Barista!2009-01-19T13:28:00+00:002009-01-19T13:28:00+00:00Nick Clareytag:airsource.co.uk,2009-01-19:/blog/2009/01/19/announcing-home-barista/<p><a href="http://www.airsource.co.uk">Airsource</a> and <a href="http://baristaapp.com/">Glasshouse Apps</a> are thrilled to announce our first iPhone application - <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=302556126&mt=8">Barista</a>. Very early this morning it became available on the iTunes Application Store and we are obviously very excited by the launch of our first product. </p>
<p><img alt="primaryscreenshot" class="img-centre" src="https://airsource.co.uk/blog/images/2009/01/primaryscreenshot.thumbnail.jpg"></p>
<p>Barista teaches you how to make the perfect espresso.</p>
<p>Buy an espresso …</p><p><a href="http://www.airsource.co.uk">Airsource</a> and <a href="http://baristaapp.com/">Glasshouse Apps</a> are thrilled to announce our first iPhone application - <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=302556126&mt=8">Barista</a>. Very early this morning it became available on the iTunes Application Store and we are obviously very excited by the launch of our first product. </p>
<p><img alt="primaryscreenshot" class="img-centre" src="https://airsource.co.uk/blog/images/2009/01/primaryscreenshot.thumbnail.jpg"></p>
<p>Barista teaches you how to make the perfect espresso.</p>
<p>Buy an espresso machine, and you suddenly expect that you will never have to buy another coffee from Starbucks again! Unfortunately, there are some essential skills that you need to master before that dream becomes a reality. Barista helps you achieve that dream.</p>
<p>Barista gives you:
* step-by-step instructions on making the most popular espresso drinks;
* tips and tricks on making better espresso;
* and a glossary of common terms.</p>
<p>Contact: <a href="mailto:sales@airsource.co.uk">sales@airsource.co.uk</a></p>
<p>Barista is available right now from the <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=302556126&mt=8">AppStore</a>. If you'd like to review the application, then promotional iTunes codes are available. Please <a href="mailto:sales@airsource.co.uk">email</a>, mentioning the website where your review will be posted.</p>
<p>Please note that the "Home Barista" application is now named "Barista".</p>iTunes Connect - failed codesign verification2009-01-16T10:14:00+00:002009-01-16T10:14:00+00:00Nick Clareytag:airsource.co.uk,2009-01-16:/blog/2009/01/16/itunes-connect-failed-codesign-verification/<p>Your iPhone application is finished. The testers are happy. The graphics designers are sure that everything is pixel perfect. Your microsite is done. Your marketing material is ready. Your metadata is in order. All you need to do is to submit your application to the App Store and start raking in the dough.</p>
<p>Your heart sinks, though, because when you submit your application you receive a terrible error message - "failed codesign verification". That's all. No further details. No explanation as to what went wrong. Has all of your stress come to naught?<p>Your iPhone application is finished. The testers are happy. The graphics designers are sure that everything is pixel perfect. Your microsite is done. Your marketing material is ready. Your metadata is in order. All you need to do is to submit your application to the App Store and start raking in the dough.</p>
<p>Your heart sinks, though, because when you submit your application you receive a terrible error message - "failed codesign verification". That's all. No further details. No explanation as to what went wrong. Has all of your stress come to naught?</p>
<p>Well fear not. Many have trodden this path before you and chances are you'll be able to fix it. When I ran into this trying to submit our Very First Product I spent several hours trying to figure out what was preventing Apple from accepting our application.</p>
<h1>1) Have you signed your application correctly?</h1>
<p>It sounds obvious, but you need to check and double check that you have the correct signature being applied to your application. There is adequate documentation on the developer program portal describing what you need to do there. Review this and make sure you have it right.</p>
<p>Many criticisms have been levelled at Apple concerning the hoops you need to jump through to have an application signed. Airsource has experience dealing with most of the common mobile development platforms and although there are elements that are a bit more involved on the iPhone it is not unusual.</p>
<h1>2) How is your .app folder compressed?</h1>
<p>This is what nailed us. Airsource have a fairly strict policy of only shipping products that come out of our automated build process. This ensures that we can repeat builds and that there is a minimal amount of new effort required when we ship second or third products for a platform. Our customers like it because we can be responsive to requests for changes and we don't regress on our packaging.</p>
<p>We've got a lot of experience now doing this for S60, BREW and BlackBerry but setting things up for iPhone builds required some additional scripting. The last stage of the build packages up all of our submission artifacts (screenshots, App Store icon and the binary) into a single archive which we can then use to drive the submission process.</p>
<p>In order to submit your binary to iTunes Connect, you need to compress it. Our automated build process did this:</p>
<div class="highlight"><pre><span></span><code>zip -r OurApp.zip . -x *.svn*
</code></pre></div>
<p>And when this package was submitted to Apple, it was refused with the "failed codesign verification" error.</p>
<p>Much experimentation revealed that the Mac OS X "Compress" Finder command produced an acceptable result, whereas the above command did not. Why?</p>
<p>It turns out that in the app directory there is a softlink:</p>
<div class="highlight"><pre><span></span><code><span class="n">lrwxr</span><span class="o">-</span><span class="n">xr</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">Nick</span><span class="w"> </span><span class="n">Clarey</span><span class="w"> </span><span class="n">staff</span><span class="w"> </span><span class="mi">28</span><span class="w"> </span><span class="mi">15</span><span class="w"> </span><span class="n">Jan</span><span class="w"> </span><span class="mi">17</span><span class="o">:</span><span class="mi">46</span><span class="w"> </span><span class="n">CodeResources</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">_CodeSignature</span><span class="o">/</span><span class="n">CodeResources</span>
</code></pre></div>
<p>and that the way this is archived is important. Softlinks appear to need to be compressed as softlinks, not as copies of the files that they point to. Changing our compression command as follows:</p>
<div class="highlight"><pre><span></span><code>zip -y -r OurApp.zip . -x *.svn*
</code></pre></div>
<p>Produced a result that Apple was happy to accept. It turns out that how you treat softlinks is pretty important.</p>
<h1>3) Make sure your version number is in an acceptable format</h1>
<p>Received a note from Apple after what appeared to be a successful submission:</p>
<blockquote>
<p>Thank you for your recent binary submission to the App Store. Unfortunately we discovered an issue with your binary that you will need to correct in order for your application to proceed to the review stage. The specific issue is outlined below: </p>
<p>Invalid or Non-Increasing CFBundleShortVersionString - The value specified in the bundle's Info.plist file for the key CFBundleShortVersionString must be a string consisting of at most three dot-separated components, where each component is composed only of the digits 0 through 9. For example, any of the following are syntactically valid values for CFBundleShortVersionString: "1.0", "4.2.1", "3.46", "1.112.0"; whereas the following are all syntactically invalid: "1.4.0.0.0.0.5", "GX5", "3.4.2b6", "2.6GM", "1.0 (Gold)", "-3.6". Additionally, each updated version of the same application must have a CFBundleShortVersionString that increases relative to that of the previous version that was actually made available for sale on the iTunes Store. For example, if a previously-available version had a CFBundleShortVersionString of "1.4", then any of the following would be acceptable as the next update: "1.4.1", "1.4.332", "1.5"; but all of the following (though syntactically valid) would be unacceptable: "1.4", "1.3", "1.3.9", "0.9". For more information about the CFBundleShortVersionString key and the Info.plist file, see <a href="http://developer.apple.com/documentation/MacOSX/Conceptual/BPRuntimeConfig/BPRuntimeConfig.html">Apple's Runtime Configuration Guidelines.</a></p>
</blockquote>
<p>Sure enough, our automated builds were producing version numbers in the format major.minor (buildnum) which was not acceptable. Changing to major.minor.buildnum was all that was required to fix this.</p>
<p>Conclusion: be careful with your packaging.</p>Getting blood out of a stone2009-01-16T09:34:00+00:002009-01-16T09:34:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-16:/blog/2009/01/16/getting-blood-out-of-a-stone/<p>I am pretty network agnostic. If I were buying a new phone contract tomorrow, I would not really care which network operator I used. Obviously I'd check out the details of the contract - but the name of the operator is not significant. With one exception - they have to issue a PAC code over the phone. If an operator isn't prepared to let me leave, then I'm not prepared to join them in the first place.<p>I am pretty network agnostic. If I were buying a new phone contract tomorrow, I would not really care which network operator I used. Obviously I'd check out the details of the contract - but the name of the operator is not significant. With one exception - they have to issue a PAC code over the phone. If an operator isn't prepared to let me leave, then I'm not prepared to join them in the first place.</p>
<p>That excludes two major UK operators. In one case, they led one of our employees through a song and dance, repeatedly promising to send the PAC code and failing to do so. In the other, after my wife eventually got past all the obstacles placed in her way (we are busy now, could you call back after midnight?), the operator told her they would mail the code out and she would receive it in 10-14 days, or something equally ludicrous. Only when she asked why they couldn't SMS is did they volunteer that in fact they could, and that it would take about five minutes. </p>
<p>A quick glance at <a href="http://en.wikipedia.org/wiki/Porting_Authorisation_Code">wikipedia</a> tells me that these scenarios are pretty typical for the operators we were dealing with.</p>
<p>Why? It is understandable that network operators don't like losing customers. It is less understandable when a network operator makes it hard for a customer to leave. When a customer calls you up to ask for a PAC code, it is very likely that this is their first ever experience of your customer service. Why not give them a good experience, and encourage them to come back in a year or two when they are looking for a new contract? As things stand, there are now two operators in the UK who I am unlikely to use or to recommend to other companies and friends) for the next few years. </p>
<p>Good customer service may not get talked about as much as it should - but bad customer service certainly does.</p>Chinese Whispers2009-01-13T13:53:00+00:002009-01-13T13:53:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-13:/blog/2009/01/13/chinese-whispers/<p><a href="https://airsource.co.uk/blog/images/2009/01/development1.jpg"><img alt="Software Lifecycle" src="https://airsource.co.uk/blog/images/2009/01/development1.thumbnail.jpg"></a></p>
<p>Just occasionally, I run into the kind of comment on a technical forum that leaves me speechless (or, more correctly, reaching for my keyboard).<p><a href="https://airsource.co.uk/blog/images/2009/01/development1.jpg"><img alt="Software Lifecycle" src="https://airsource.co.uk/blog/images/2009/01/development1.thumbnail.jpg"></a></p>
<p>Just occasionally, I run into the kind of comment on a technical forum that leaves me speechless (or, more correctly, reaching for my keyboard). This is one of them:</p>
<blockquote>
<p>"in BREW, one has to use char pointers rather than large arrays"</p>
</blockquote>
<p>in response to a question someone asked about memory corruption of their applet structure in a BREW program. Digging deep into the mindset of the person who wrote this comment, I speculate that a mentor once told him to use a pointer rather than a large array. That's a perfectly solid piece of technical guidance in context, because BREW has a relatively small stack, and allocating large amounts of memory on the stack is therefore a recipe for disaster. That was what the mentor meant. What the programmer actually heard - and subsequently repeated - was "don't use large arrays", which demonstrates a reasonably poor understanding of the differences - and similarities - between arrays and pointers in C.</p>
<p>I take three lessons out of this:</p>
<ul>
<li>Whatever you say, it will be misunderstood in the worst way possible, so either shut up, or be really really clear about what you are trying to say.</li>
<li>If you're mentoring, take the time to do it properly.</li>
<li>If you're recruiting, make sure you get smart people who will understand you.</li>
</ul>
<p>Now, just to be clear:</p>
<ul>
<li>You can use arrays in BREW programs</li>
<li>The size of your array is limited by the available memory. If your array is on the heap, that's large. If it's on the stack, best keep your array to no more than a few hundred bytes - less if your function is reentrant.</li>
</ul>Save the Bits!2009-01-08T18:05:00+00:002009-01-08T18:05:00+00:00Ben Blaukopftag:airsource.co.uk,2009-01-08:/blog/2009/01/08/save-the-bits/<p>Over here at Airsource, we're not exactly retro, but we do care about computing resources, especially bandwidth. We like small sleek applications that perform well, not applications that use excess bandwidth, and run twenty times slower than necessary. With that in mind, I picked a relatively simple iPhone application that displays a currency exchange rate and a graph of recent historical movement, and measured its bandwidth usage. The results were amazing.<p>Over here at Airsource, we're not exactly retro, but we do care about computing resources, especially bandwidth. We like small sleek applications that perform well, not applications that use excess bandwidth, and run twenty times slower than necessary. With that in mind, I picked a relatively simple iPhone application that displays a currency exchange rate and a graph of recent historical movement, and measured its bandwidth usage. The results were amazing.</p>
<p>It started by uploading 16KB of data, and downloading 136KB. To be fair this is the ad-ware version, which includes an advert image - but that's only about 30KB. That still leaves 106 KB of data to render a graph. In addition, every time it downloads a tick update, it sends 8KB and downloads 34KB of data. </p>
<p>Airsource recently wrote an application that encoded graph data for share price movements in an SMS. That's 140 bytes of data. By that metric, Bandwidth Hog should be providing that is 775 times more accurate. It isn't. Presumably it is downloading an image of the graph instead of fetching the price data and then rendering the graph in code.</p>
<p>Why is this bad? After all, the iPhone contract includes an unlimited bandwidth contract. Well, here are a few reasons:</p>
<ul>
<li>There are plenty of users out there who have unlocked their iPhones, and are on non-standard contracts, some of them with limited bandwidth. Don't you want them to buy your app as well?</li>
<li>Even unlimited bandwidth is not free. It costs the operator something, and that, in turn, costs you something. If iPhone apps prove to use too much bandwidth, then the cost of the iPhone contract will go up.</li>
<li>Design for users in New Hampshire. This is the Airsource version of Joel Spolsky's aphorism "<a href="http://www.joelonsoftware.com/design/index.html">Design for Extremes</a>". When I was up in New Hampshire recently, I noticed that most of the roads, at least in the White Mountains area, run through valleys - and valleys are usually not in the line of sight to a cell base station, because something's in the way, often a very large lump of granite. Consequently the signal strength was often very low. Sometimes you got a decent signal - but if you were driving at 50mph, it disappeared pretty quickly. All this leads us the point that the less data your app needs to download, the faster it will work, and the more likely it is to work in low signal conditions.</li>
<li>Upstream bandwidth is definitely not free. Every ISP I have ever known has charged something for bandwidth used by servers. Sending less data reduces your hosting costs. This may not matter to you if you have only have ten users - but it certainly will if you get to a million users.</li>
</ul>
<p>The only conceivable reason for sending an image instead of the data is that it's cheaper in development time. That is, on the surface of it, a valid business reason; but it is not a good way to endear you to your customers, especially if they wind up paying lots to use an apparently simple application.</p>
<p>And finally... what on earth is this application doing uploading 16KB of data every time I run it? If it uploaded my position, a unique ID, usage statistics for the application and maybe a couple of other things, then that might cover 1KB. But 16KB? and a further 8KB every tick??? <a href="https://airsource.co.uk/blog/2009/01/29/save-the-bits-part-ii/">I'll be back when I've run a packet sniffer...</a></p>Memory Management and NIBs on the iPhone2008-12-23T17:56:00+00:002008-12-23T17:56:00+00:00Ben Blaukopftag:airsource.co.uk,2008-12-23:/blog/2008/12/23/memory-management-and-nibs/<p>There's <a href="http://www.cocoabuilder.com/archive/message/cocoa/2006/5/17/163720">clearly</a> <a href="http://stackoverflow.com/questions/382576/do-i-need-to-release-iboutlets-when-using-loadnibnamed-method">some</a> <a href="http://archives.devshed.com/forums/bsd-93/do-i-need-to-release-iboutlets-in-dealloc-1289958.html">confusion</a> out there among iPhone developers about <a href="http://stackoverflow.com/questions/61838/do-i-need-to-release-xib-resources">whether</a> or <a href="http://www.iphonedevsdk.com/forum/iphone-sdk-development/1227-retaining-iboutlets.html#post36583">not</a> you need to release your IBOutlets. It's actually pretty simple. <p>There's <a href="http://www.cocoabuilder.com/archive/message/cocoa/2006/5/17/163720">clearly</a> <a href="http://stackoverflow.com/questions/382576/do-i-need-to-release-iboutlets-when-using-loadnibnamed-method">some</a> <a href="http://archives.devshed.com/forums/bsd-93/do-i-need-to-release-iboutlets-in-dealloc-1289958.html">confusion</a> out there among iPhone developers about <a href="http://stackoverflow.com/questions/61838/do-i-need-to-release-xib-resources">whether</a> or <a href="http://www.iphonedevsdk.com/forum/iphone-sdk-development/1227-retaining-iboutlets.html#post36583">not</a> you need to release your IBOutlets. It's actually pretty simple. </p>
<p>When UIKit loads a NIB file, it sets up all your IBOutlets for you. That is, after all, then entire point of an IBOutlet! UIKit does this using <a href="https://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/chapter_3_section_4.html#//apple_ref/doc/uid/10000051i-CH4-SW6">setValue:forKey:</a> which is part of the <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html#//apple_ref/occ/instm/NSObject/setValue:forKey:">NSKeyValueCoding Protocol</a>. The important things to know about it are:</p>
<ul>
<li>If you have a setter method (which you will if you have defined it as an @property), UIKit will call that</li>
<li>If you have not defined a setter, then UIKit will directly set the value of the instance variable and then, for anything other than an NSNumber or NSValue date type, UIKit will <strong>retain the value</strong> after autoreleasing the instance variable's old value.</li>
</ul>
<p>The consequence of this is that unless you define your IBOutlets as</p>
<div class="highlight"><pre><span></span><code><span class="nv">@property</span><span class="w"> </span><span class="p">(...,</span><span class="w"> </span><span class="n">assign</span><span class="p">)</span>
</code></pre></div>
<p>then you need to worry about cleaning them up at dealloc time. A good way to do this is to ensure that you always define an IBOutlet as an @property, and then to simply to set their values to nil in your dealloc implementation, like this:</p>
<div class="highlight"><pre><span></span><code><span class="p">-(</span><span class="kt">void</span><span class="p">)</span><span class="nf">dealloc</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">outlet1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">nil</span><span class="p">;</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">outlet2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">nil</span><span class="p">;</span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">dealloc</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>
<p>Setting them to nil will work regardless of how you set up the @property - i.e. whether it is a 'retain'-type or an 'assign'-type property.</p>
<p>You also need to worry about IBOutlets in UIViewController when handling didReceiveMemoryWarning, but that's a story for another post.</p>It's an ill wind that blows no good2008-12-15T15:37:00+00:002008-12-15T15:37:00+00:00Ben Blaukopftag:airsource.co.uk,2008-12-15:/blog/2008/12/15/its-an-ill-wind-that-blows-no-good/<p>I couldn't get to sleep last night, so instead I did a quick pass through the new apps on the AppStore to see what exciting applications (or <a href="https://www.zdnet.com/article/iphone-app-store-why-do-we-need-10-tip-calculators/">tip calculators</a>) were now available. I noticed that "Pull My Finger" is now available, which, errr, makes noises. Whoopee, I thought! Just what …</p><p>I couldn't get to sleep last night, so instead I did a quick pass through the new apps on the AppStore to see what exciting applications (or <a href="https://www.zdnet.com/article/iphone-app-store-why-do-we-need-10-tip-calculators/">tip calculators</a>) were now available. I noticed that "Pull My Finger" is now available, which, errr, makes noises. Whoopee, I thought! Just what I've been waiting for. Funny, though, I'm sure that <a href="http://www.macrumors.com/iphone/2008/09/04/apple-rejecting-applications-based-on-limited-utility/">was rejected by Apple</a> not so long ago. Wonder if they're having a change of fart, I mean heart?</p>
<p>Sure enough, a bit of digging showed that there are now several flatulent applications on the AppStore, all uploaded around 12-14th December. Seems like Apple <a href="http://www.edibleapple.com/pull-my-finger-is-back-in-the-app-store/">may be loosening up</a>...</p>Google and iPhone - part II2008-12-14T03:31:00+00:002008-12-14T03:31:00+00:00Ben Blaukopftag:airsource.co.uk,2008-12-14:/blog/2008/12/14/google-and-iphone-part-ii/<p><a href="https://airsource.co.uk/blog/images/2008/12/app-apple1.png"><img alt="app-apple1" src="https://airsource.co.uk/blog/images/2008/12/app-apple1.png"></a></p>
<p>I just found another interesting article out there in the World Wild Web, over at <a href="http://daringfireball.net/2008/11/google_mobile_uses_private_iphone_apis">Daring Fireball</a>. Apparently, Google started publicizing the voice search feature some time before it actually reached the AppStore. The critical phrase in <a href="http://www.nytimes.com/2008/11/14/technology/internet/14voice.html?partner=rss&emc=rss&pagewanted=all">the NYTimes article</a> is</p>
<blockquote>
<p>"...Users of the free application, which Apple is expected to make available as soon as Friday through its iTunes store..."</p>
</blockquote>
<p>which suggests, in Daring Fireball's analysis, that Google may have have pressured Apple to accept their application even though it violated the SDK agreement.<p><a href="https://airsource.co.uk/blog/images/2008/12/app-apple1.png"><img alt="app-apple1" src="https://airsource.co.uk/blog/images/2008/12/app-apple1.png"></a></p>
<p>I just found another interesting article out there in the World Wild Web, over at <a href="http://daringfireball.net/2008/11/google_mobile_uses_private_iphone_apis">Daring Fireball</a>. Apparently, Google started publicizing the voice search feature some time before it actually reached the AppStore. The critical phrase in <a href="http://www.nytimes.com/2008/11/14/technology/internet/14voice.html?partner=rss&emc=rss&pagewanted=all">the NYTimes article</a> is</p>
<blockquote>
<p>"...Users of the free application, which Apple is expected to make available as soon as Friday through its iTunes store..."</p>
</blockquote>
<p>which suggests, in Daring Fireball's analysis, that Google may have have pressured Apple to accept their application even though it violated the SDK agreement.</p>
<p>I don't buy that. A more realistic summary of the situation is that Google submitted the application in the expectation that:</p>
<p>a) it would be accepted without any problems, because the testing team at Apple were not sufficiently clued up to reject it, and </p>
<p>b) it would not subsequenly be pulled, because it was a really cool application.</p>
<p>With respect to a), there are several applications on the AppStore today that have taken the same approach. That doesn't make it right - indeed it somewhat dubious ethically - though it may be indicative of a certain amount of laissez faire with respect to following the SDK agreement. As for b), quality applications in widespread use absolutely should be left on the AppStore unless there is a compelling reason to remove them. New versions of the iPhone SDK are pre-released to developers, meaning that Google can simply release an updated version of the application which works on the new API. As I pointed out in <a href="http://blog.airsource.co.uk/index.php/2008/12/12/google-and-apple-a-spectator-sport/">my previous article</a>, even if Google do not/cannot release such an update, the application will continue to work, albeit with reduced functionality. Even more importantly, Apple are pretty unlikely to change the API in any case. If there is a widely adopted application out there on the AppStore using an undocumented API, and Apple know about it, then Apple have made an implicit decision to support that API.</p>
<p>The real question we should be asking is not whether Google have acted unfairly in blazing a trail for the rest of us to follow - but whether Apple will act unfairly in preventing us from following that trail.</p>Google using private APIs? Not really...2008-12-12T19:39:00+00:002008-12-12T19:39:00+00:00Ben Blaukopftag:airsource.co.uk,2008-12-12:/blog/2008/12/12/google-and-apple-a-spectator-sport/<p><a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284815942&mt=8"><img alt="app-apple" src="https://airsource.co.uk/blog/images/2008/12/app-apple.png"></a></p>
<p>Google recently <a href="http://news.cnet.com/8301-13579_3-10108348-37.html?part=rss&tag=feed&subj=News-Apple">admitted to breaking the AppStore rules</a> in their <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284815942&mt=8">iPhone application,</a> which fuelled a growing wave of resentment, prompted by the belief that Google were abusing their position as industry leaders to gain a competitive advantage in the market. The critics claim that a similar application submitted by anyone else would be rejected by Apple and never make it to the AppStore. Why should there be one law for Google, and another for the plebian masses? Shouldn't the Google application be pulled from the AppStore until they abide by the rules, as <a href="http://discussions.apple.com/message.jspa?messageID=7616302">others have been</a>?<p><a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284815942&mt=8"><img alt="app-apple" src="https://airsource.co.uk/blog/images/2008/12/app-apple.png"></a></p>
<p>Google recently <a href="http://news.cnet.com/8301-13579_3-10108348-37.html?part=rss&tag=feed&subj=News-Apple">admitted to breaking the AppStore rules</a> in their <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284815942&mt=8">iPhone application,</a> which fuelled a growing wave of resentment, prompted by the belief that Google were abusing their position as industry leaders to gain a competitive advantage in the market. The critics claim that a similar application submitted by anyone else would be rejected by Apple and never make it to the AppStore. Why should there be one law for Google, and another for the plebian masses? Shouldn't the Google application be pulled from the AppStore until they abide by the rules, as <a href="http://discussions.apple.com/message.jspa?messageID=7616302">others have been</a>?</p>
<p>Of course, nothing is ever that clear-cut, particularly where the AppStore is concerned. In the face of it, the SDK agreement is pretty clear. It says, and I quote:</p>
<blockquote>
<p>3.3.1 Applications may only use Published APIs in the manner prescribed by Apple and must not use or call any unpublished or private APIs.</p>
</blockquote>
<p>However, there's a subtlety here. The technique used by Google does not, <a href="http://daringfireball.net/2008/11/google_mobile_uses_private_iphone_apis">as some have claimed</a>, involve calling a private API. It's a public API, but unpublished, meaning that according the SDK, Google should still not be using it. However, by one possible interpretation, Google isn't even using it within. All Google have done is implemented a method like this (based on an article by <a href="http://arstechnica.com/journals/apple.ars/2008/11/19/ars-investigates-does-google-mobile-use-private-apis">Erica Sadun at ars technica</a>)</p>
<div class="highlight"><pre><span></span><code><span class="k">@implementation</span> <span class="nc">GoogleApplication</span>
<span class="c1">// Override private API</span>
<span class="p">-(</span><span class="kt">void</span><span class="p">)</span><span class="nf">proximityStateChanged:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">isOn</span>
<span class="p">{</span>
<span class="w"> </span><span class="c1">// do funky stuff</span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">proximityStateChanged</span><span class="o">:</span><span class="n">isOn</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>
<p>What then happens is that when the proximity state changes - i.e. you bring the phone close to your ear - this method gets called, the Google app detects it, and fun stuff happens. Notice that - <em>this method gets called</em>. It's not Google calling the method, it's the iPhone OS calling it, followed by Google then doing something. That's not just a subtle, inconsequential difference. If Apple decide to remove that method in a future iPhone update, then all that will happen is that the Google app will stop switching into voice search mode when you bring it up to your ear. There will be no other ill effects, and the application will continue to work. In terms of "naughtiness" that's not really any worse than what the bizarrely named <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=292644077&mt=8">NaughtyNuts Camera SelfTimer</a> does. They dive down into the view hierarchy, find the appropriate selector to trigger capturing an image - and then call that directly so that they can trigger the camera through a timer. That's getting very very close to calling an unpublished API - but Apple approved it, and presumably did so as a conscious decision because <a href="http://naughtynuts.wordpress.com/products/camtimer/">they took ten weeks to do it</a>.</p>
<p>Contrast that with a hypothetical application that directly calls a private or unpublished API. If that API disappears, then the application will crash when you try to use that piece of functionality. The verty worst thing you can do is to link against a private framework - if that framework vanishes, your application won't even start up.</p>
<p>My feeling is that while Google are definitely skating on thin ice, their offence is definitely at the minor end of the scale, and that those people calling for their application to be pulled from the AppStore at over-reacting. You'll note that I have linked to <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284815942&mt=8">Google's application</a> in several places in this article. That is because I think it is an excellent, innovative application that is good for users and Apple alike. I approve of Google trying to push the limits - because if they don't, who will? Hopefully we will get some clearer guidance from Apple about exactly what is and is not acceptable, and an outcome that is better for developers, better for users, and better for Apple.</p>Information Addiction2008-12-04T19:35:00+00:002008-12-04T19:35:00+00:00Ben Blaukopftag:airsource.co.uk,2008-12-04:/blog/2008/12/04/information-overload/<p>Donald Knuth, famously, <a href="http://www-cs-faculty.stanford.edu/~knuth/email.html">does not have an email account</a>. Instead, he replies to correspondence (by snail mail) about once every three months. I'm starting to appreciate his motivation. Now obviously, Donald isn't in the business of selling, and his needs are slightly different to mine. But do I need to …</p><p>Donald Knuth, famously, <a href="http://www-cs-faculty.stanford.edu/~knuth/email.html">does not have an email account</a>. Instead, he replies to correspondence (by snail mail) about once every three months. I'm starting to appreciate his motivation. Now obviously, Donald isn't in the business of selling, and his needs are slightly different to mine. But do I need to check my email so often?</p>
<p>One of the first things I do when I get a new phone is to get it hooked up to my IMAP account. On my iPhone, this is a trivial process - and checking my email is even easier. The result is that I end up compulsively checking it whenever I'm walking along. This morning, I suddenly realised that I didn't need to check it, that any email I had received was highly unlikely be so urgent that it required reading Right Now yet not urgent enough to warrant phoning me. So I left my phone in my pocket, carried on walking, and enjoyed the view, with my right hand twitching towards my phone every so often. </p>
<p>Replying to email on an iPhone is a pain anyway. I rarely use it to send or reply - if I get an interesting message, I'll almost certainly read it again later on my desktop, and reply from there. The device, unlike the aptly named Crackberry, is simply not optimised for it. If you never get a chance to sit down at your desk and process your email queue, then sure, you need a good mobile email client - i.e. a Blackberry. The iPhone is a great all-purpose device which meets my needs; that is getting information on the move, and demonstrating smart applications to clients. I don't need to check my email every five seconds when I'm on the move - and from now on I won't.</p>
<p>Seth Godin wrote a similarly minded article today called "<a href="http://sethgodin.typepad.com/seths_blog/2008/12/the-high-cost-o.html">The High Cost of Now</a>" - worth a look.</p>Mobile Linux?2008-11-26T20:27:00+00:002008-11-26T20:27:00+00:00Teanlorg Chantag:airsource.co.uk,2008-11-26:/blog/2008/11/26/mobile-linux/<p>Last week, the BBC <a href="http://news.bbc.co.uk/1/hi/technology/7729978.stm">reported</a> on an upcoming version of Ubuntu for ARM "netbooks". Pity there's no article history; the title (currently <em>Ubuntu set to debut on netbooks</em>) originally said "smartphones" and the meta tags still mention smartphones<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup> even though the article itself mentions nothing about Ubuntu on smartphones, but I could spend all day reporting on inconsistent reporting.</p>
<p>Last week, the BBC <a href="http://news.bbc.co.uk/1/hi/technology/7729978.stm">reported</a> on an upcoming version of Ubuntu for ARM "netbooks". Pity there's no article history; the title (currently <em>Ubuntu set to debut on netbooks</em>) originally said "smartphones" and the meta tags still mention smartphones<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup> even though the article itself mentions nothing about Ubuntu on smartphones, but I could spend all day reporting on inconsistent reporting.</p>
<p>Linux on netbooks is nothing new -- the <a href="http://www.google.co.uk/search?q=msi+wind+suse">MSI Wind</a> launched with SUSE (apparently <a href="http://www.google.co.uk/search?q=msi+wind+sled">SLED</a>, but only on the U90) as an option and the <a href="http://eeepc.asus.com/global/getstarted.html">Eee PC</a> has a Linux option (Xandros, according to Wikipedia). Getting smaller, the <a href="http://maemo.org/intro/maemo_history/">N770/N800/N810</a> use Maemo. Smaller still, plenty of Motorola phones run <a href="http://www.motorola.com/content.jsp?globalObjectId=8411">MOTOMAGX</a>, the G1 runs Android, and the <a href="http://www.openmoko.com/product.html">Freerunner</a> comes with less closed-source software than a typical PC.<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup></p>
<p>No, the real news is an ARM/Ubuntu deal culminating in an ARM laptop.<sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup> The netbook market is currently dominated by the Atom (it's in the newer Eees and Wind), replacing higher-power Intel chips; no surprise, since there's no Vista for ARM.<sup id="fnref:4"><a class="footnote-ref" href="#fn:4">4</a></sup> The main reason to use ARM is for battery life, and power isn't that big a restriction on a laptop -- the new Eees come with more watt-hours than my iBook G4!</p>
<p>What's going to come out of this? It's hard to say.</p>
<ul>
<li>Better power management? With Linux running on so many lower-power things, you'd think the power management code was already pretty decent, but that doesn't stop vendors from <a href="http://mjg59.livejournal.com/100221.html">rolling their own</a>. Still, there are <a href="http://mjg59.livejournal.com/102406.html">watts to be saved</a> everywhere.</li>
<li>A better UI? Something written from scratch is unlikely, but anyone who's used Gnome or KDE knows there's a lot of room for improvement.</li>
<li>Mainstream ARM boards? Currently, the best option for a low-power server<sup id="fnref:5"><a class="footnote-ref" href="#fn:5">5</a></sup> is a desktop Atom board.</li>
<li>Linux on more phones? There are an increasing number of devices filling the phone-computer continuum; this deal moves ARM into the computer end and Linux (more importantly, mainstream Linux apps; the kernel is just a small part of Ubuntu) slightly closer to the phone end.</li>
<li>A completely unlocked, reflashable phone with cutting-edge hardware, Linux kernel, running native Linux apps? I can dream, can't I?</li>
</ul>
<p>Obviously, ARM is trying to establish a bigger presence in the "non-embedded" world. Maybe we'll even get Ubuntu on smartphones; two years is a long time to wait for Symbian to become open-source.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>Right now it says <code><meta name="Description" content="Mobile phone chip firm Arm signs up with Canonical to put Ubuntu software on smartphones and low cost laptops." /></code>. I can't find a cached copy of the original article anywhere. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>100% FLOSS is an interesting ideal (I've yet to see a PC with a GPL BIOS), but OpenMoko seem to have managed it save for firmware running off-CPU, like for GSM/GPS. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>Technology-wise, netbooks are just small laptops; functionality-wise, internet tablets (<a href="http://www.intel.com/products/mid/">MID</a>s) are like small netbooks. <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p>Though there were some ventures with PowerPC and Itanium (more on <a href="http://en.wikipedia.org/wiki/Microsoft_Windows#64-bit_operating_systems">Wikipedia</a>, Windows is only really available for x86-based platforms. Embedded versions don't count. <a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:5">
<p>Or something that can run OpenWRT/DD-WRT. I don't see why people insist on getting wireless routers and sticking everything behind <em>n</em> layers of NAT when your average Linux box is a perfectly capable router, can do lots of other things (like <a href="http://munin.projects.linpro.no/">Munin</a> graphs), and actually gets patched once in a while. But that's a rant for another day. <a class="footnote-backref" href="#fnref:5" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
</ol>
</div>User-friendly (but not developer-friendly)2008-11-25T02:00:00+00:002008-11-25T02:00:00+00:00Ben Blaukopftag:airsource.co.uk,2008-11-25:/blog/2008/11/25/view-all-breakpoints/<p>Imagine you're coding away on an IDE that you haven't used for a while, and becoming reacquainted with it. You get to the point where you want to play with breakpoints, and, of course you have some trouble remembering the keyboard shortcuts. Some IDEs make your life easier than others …</p><p>Imagine you're coding away on an IDE that you haven't used for a while, and becoming reacquainted with it. You get to the point where you want to play with breakpoints, and, of course you have some trouble remembering the keyboard shortcuts. Some IDEs make your life easier than others...</p>
<p>In Visual Studio 2005, you hit Ctrl-B to set a breakpoint. If you want a really quick breakpoint with no conditions, hit F9. And use either Ctrl-Alt-B <em>or</em> Alt-F9 to manage them. That's pretty simple. If I don't know what I'm doing, I can do Right Click->Breakpoint->Insert Breakpoint, and Debug->Windows->Breakpoints (that last one is pretty well hidden!). I cut my teeth on PCs, so I'm probably biased, but I like Visual Studio.</p>
<p>xCode is even slicker - I can either hit Apple-\ to get a breakpoint, or do a single-click in the gutter. Smooth. Viewing all the breakpoints is relatively straightforward with Alt-Cmd-B, and from there I can easily see how to add a condition to an existing breakpoint. I reckon Apple win that one, though I'd have preferred the breakpoint shortcut to have something to do with the letter 'B'.</p>
<p>Over to Carbide.c++ 1.3, an Eclipse-based UI, where I can either set a breakpoint with Right-click->Toggle Breakpoint, or use Ctrl-Shift-B. Now, how do I list them? Window->Show View->Breakpoints tells me that the shortcut is Alt-Shift-Q, then hit B. Yes, you heard it right. I had a play around, and found that the combinations Alt-B, Alt-Shift-B, and Ctrl-Alt-B are all completely unused. For some reason known only to Nokia though (and I thought they were supposed to be good at UI?!), a two part, four key sequence was more logical.</p>
<p>Guess what my least favourite IDE is...</p>Memory usage in UIImagePickerController2008-11-12T14:54:00+00:002008-11-12T14:54:00+00:00Ben Blaukopftag:airsource.co.uk,2008-11-12:/blog/2008/11/12/memory-usage-in-uiimagepickercontroller/<p>UIImagePickerController has plenty of issues. One of the first to be widely discussed was its memory leak which shows itself when you try to access the PhotoLibrary on the simulator. Fortunately, this leak is limited to the Simulator and does not show up on Device. It has also apparently been fixed in iPhone OS 2.2 - though you obviously need to be aware of it if you are coding for older versions.</p>
<p>There is, however, another more serious problem with the image picker on device<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup><p>UIImagePickerController has plenty of issues. One of the first to be widely discussed was its memory leak which shows itself when you try to access the PhotoLibrary on the simulator. Fortunately, this leak is limited to the Simulator and does not show up on Device. It has also apparently been fixed in iPhone OS 2.2 - though you obviously need to be aware of it if you are coding for older versions.</p>
<p>There is, however, another more serious problem with the image picker on device<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>, which shows up when repeatedly capturing images from the camera. If your application uses the camera, you have a choice. You can either hold open an instance of UIImagePickerController all the time, or you can release the controller when you are done using it, and instantiate it next time you want to use it. Surely the latter is better - you should only hold resources open when you need them.</p>
<p><a href="https://airsource.co.uk/blog/images/2008/11/picture-2.png" title="Fig 1. Memory trace of repeated image captures on device"><img alt="picture-2" src="https://airsource.co.uk/blog/images/2008/11/picture-2.thumbnail.png"></a></p>
<p>Figure 1, above, shows the problem with that approach when we moved to the device. We experienced frequent crashes in an application that involved many image captures. We eventually narrowed the problem down to repeatedly capturing images, where the application released the UIImagePickerController after every capture. After 7 or 8 (never more) image captures, the application would either exit, or crash. The trace above shows the memory allocation spiking up during every image capture - and never dropping back down after the 7th image capture. The activity spinner froze, and the device locked up requiring a reset. The freeze occurred inside the UIImagePickerController code, not in application code. We also noted a memory leak from core code that was detected between the 3rd and 4th image capture, which is shown in the trace above</p>
<p>Having identified a means of reliably reproducing the crash<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup>, and noting that it was occurring within Apple code, not our own, we tried holding open a UIImagePickerController for the lifetime of the application. We have not since experienced a crash. Airsource therefore recommends that developers using UIImagePickerController use a singleton instance over the life of the application<sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup> rather than creating on demand and then releasing.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>All tests run on an iPhone 2G 8GB running OS 2.1 (the bug has not been observed in 2.2) <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>We validated the test by running it against a 3rd party application called Phanfare which is also dedicated to image capture. We were able to reliably cause Phanfare to crash, by taking a photo, selecting Use Photo, selecting Cancel, and then repeating. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>Airsource only ran the test in camera capture mode. According to <a href="http://www.iphonedevsdk.com/forum/iphone-sdk-development/3816-uiimagepickercontroller-memory-issues.html#post20911">a post at iPhoneDevSDK.com</a>, the same crash can be experienced in PhotoLibrary mode, albeit with a higher threshold number of image captures. <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>Views of UIImagePickerController2008-11-11T14:45:00+00:002008-11-11T14:45:00+00:00Ben Blaukopftag:airsource.co.uk,2008-11-11:/blog/2008/11/11/views-of-uiimagepickercontroller/<p>The standard image capture in API in the iPhone SDK is the UIImagePickerController. There is much discussion on the web about how this can be customized via subclassing, both from the viewpoint of technical feasibility, and from the viewpoint of being allowed onto the AppStore. It is generally accepted that going direct to private frameworks is unacceptable, even though this arguably can give a better user experience. <a href="http://www.phanfare.com">Phanfare</a> had their app <a href="http://discussions.apple.com/message.jspa?messageID=7616302">pulled</a> from their AppStore for using the PhotoLibrary private framework, and returned with a new version that instead customizes the UIImagePickerController experience.</p>
<p>I took an in-depth look at the view structure that the standard UIImagePickerController creates.<p>The standard image capture in API in the iPhone SDK is the UIImagePickerController. There is much discussion on the web about how this can be customized via subclassing, both from the viewpoint of technical feasibility, and from the viewpoint of being allowed onto the AppStore. It is generally accepted that going direct to private frameworks is unacceptable, even though this arguably can give a better user experience. <a href="http://www.phanfare.com">Phanfare</a> had their app <a href="http://discussions.apple.com/message.jspa?messageID=7616302">pulled</a> from their AppStore for using the PhotoLibrary private framework, and returned with a new version that instead customizes the UIImagePickerController experience.</p>
<p>I took an in-depth look at the view structure that the standard UIImagePickerController creates. The first is obtained by looking at <code>self.view</code> and is not particularly interesting. It consists of a UITransitionController and a UINavigationController and is clearly the meta-system for switching between the capture and preview screens. </p>
<p>The second view hierarchy is created by PLCameraController, and can be obtained by implementing <code>willShowViewController</code> in the UINavigationControllerDelegate. The first callback to this comes when UIImagePickerController wants to display a PLCameraController. The view hierarchy is much more interesting:</p>
<p><a href="https://airsource.co.uk/blog/images/2008/11/uiimagepickercontroller1.png"><img alt="uiimagepickercontroller1" src="https://airsource.co.uk/blog/images/2008/11/uiimagepickercontroller1.thumbnail.png"></a></p>
<p>Several of these classes are undocumented, but that doesn't prevent us from manipulating them with standard APIs. Experimentation showed that the UIImageView highlighted in red is the important one - it's the camera preview pane. You can remove every other view, and just retain this one, and you will get a full-screen camera view. You can also move the TPPushButton (which represents the cancel button) out of the hierarchy and place it elsewhere on the screen. Doing the same with the TPCameraPushButton causes a crash when you activate the button, because the PLCameraController tries to modify the buttons, and fails because the view structure has changed. </p>
<p>You can also add further views (as Phanfare did) to overlay extra details. Clearly there is yet more scope for customizing other aspects of the hierarchy - this requires more experimentation, and we'll report on this as we learn more.</p>Joel Spolsky in Boston2008-10-27T17:18:00+00:002008-10-27T17:18:00+00:00Ben Blaukopftag:airsource.co.uk,2008-10-27:/blog/2008/10/27/joel-spolsky-in-boston/<p>The subtitle for this article should probably be "Why the iPhone is Swallowable". I'm here at the <a href="http://www.sdbestpractices.com/">Software Development Best Practices expo</a> in Boston. <a href="http://www.joelonsoftware.com/">Joel Spolsky</a> introduced us all, just a moment ago, to a new concept in design - how swallowable a device is. Admittedly he applied this to the …</p><p>The subtitle for this article should probably be "Why the iPhone is Swallowable". I'm here at the <a href="http://www.sdbestpractices.com/">Software Development Best Practices expo</a> in Boston. <a href="http://www.joelonsoftware.com/">Joel Spolsky</a> introduced us all, just a moment ago, to a new concept in design - how swallowable a device is. Admittedly he applied this to the notion of an iPod - and the new series of Macbooks - which having no seams look like they should be easily swallowable, but a design principle should be applicable across the board, right?</p>
<p>On a more serious note, Joel discussed a couple of interesting theories about how to make a great product, be it software or anything else. He started by talking about Control/Helplessness with obvious applicability to the Microsoft/Apple products, followed by a long section on Aesthetics. His point was that while there may be no sensible business rationale to creating <a href="http://movies.apple.com/movies/us/apple/mac/macbook/2008/designvideo/apple_new_macbook_video_20081014_r848-9cie.mov">an entire new manufacturing process for the Macbook</a> just to lose a seam or two, we don't let software engineers do design. Engineers think that artists can just paint over all their ugliness, whereas in reality the engineering decisions need to be shaped and guided by the overall design vision.</p>
<p>The second theory was about designing with a <a href="http://www.randomhouse.com/broadway/culturecode/">Culture Code</a> viewpoint. SUVs are perceived as safe, despite the converse beng true, because they are soft (off-road suspension), with big armchairs inside instead of racing Recaro seats, and because the seating position is high up. Finally, and this was the crucial point, they have plenty of cupholders, allowing the driver to get a warm milky drink in a soft comfortable environment - in other words (according to Joel), selling the driver on a breast-feeding type experience. And, of course, breast-feeding equals a safe, comfortable environment.</p>
<p>I am now waiting for the first iPod-clone manufacturer to misunderstand the message and produce an something shaped like a breast.</p>Airsource in Boston2008-10-23T19:26:00+01:002008-10-23T19:26:00+01:00Ben Blaukopftag:airsource.co.uk,2008-10-23:/blog/2008/10/23/airsource-in-boston/<p>Just a quick post to say that I will be dropping in at the <a href="http://www.sdbestpractices.com/">Software Development Best Practice conference</a> in Boston next week (Oct 27-30). If any readers of this blog are there, I'd be happy to get together for a coffee, beer, or lunch, as preferred. Just contact us …</p><p>Just a quick post to say that I will be dropping in at the <a href="http://www.sdbestpractices.com/">Software Development Best Practice conference</a> in Boston next week (Oct 27-30). If any readers of this blog are there, I'd be happy to get together for a coffee, beer, or lunch, as preferred. Just contact us through <a href="http://www.airsource.co.uk/contact-us/">our website</a>.</p>GooglER2008-10-16T12:43:00+01:002008-10-16T12:43:00+01:00Ben Blaukopftag:airsource.co.uk,2008-10-16:/blog/2008/10/16/googler/<p>UK readers will have noted today's <a href="http://www.google.co.uk/">Google Doodle</a> marking the visit of the Queen to Google's London offices. I am personally rather interested to see if Google starts sporting a <a href="http://www.royalwarrant.org/">"By Appointment" Royal Warrant</a>. It would be quite cool to be Her Majesty's search engine of choice, after all.</p>Airsource One2008-10-10T13:06:00+01:002008-10-10T13:06:00+01:00Nick Clareytag:airsource.co.uk,2008-10-10:/blog/2008/10/10/airsource-one/<p>On Wednesday Airsource reached our first major milestone towards our corporate jet. We have our very own in-house pilot in John!</p>
<p><img alt="John (with wings)" class="img-centre" src="https://airsource.co.uk/blog/images/2008/10/dscn0363.thumbnail.jpg"></p>
<p>So after 12 months of hard graft and weekly lessons and flights John passed his <a href="http://en.wikipedia.org/wiki/Private_Pilot_License">PPL</a>. Congratulations from the team - next step is type certification on <a href="http://www.eclipseaviation.com/#/eclipse400/">Airsource One</a>!</p>Mobile Summer2008-10-03T14:25:00+01:002008-10-03T14:25:00+01:00Iestyn Prycetag:airsource.co.uk,2008-10-03:/blog/2008/10/03/mobile-summer/<p>I arrived at Airsource twelve weeks ago to begin my summer internship, and now -- sadly -- it is at an end. It has been an interesting time, where I have learnt a lot and contributed something I feel will be lasting.<p>I arrived at Airsource twelve weeks ago to begin my summer internship, and now -- sadly -- it is at an end. It has been an interesting time, where I have learnt a lot and contributed something I feel will be lasting.</p>
<p>The main area of work with which I've been involved has been quality control; aids to test, debug and auto-build Airsource's projects. This has been an education for me in the process of software development, where tracking down bugs can be tedious and time consuming, so catching problems early is important. Automated testing is a wonderful tool for finding bugs in your code (usually when you write the test!), but more so making sure your code doesn't regress as time passes and new code is added. Now, at the end of my internship, seeing the auto-testing framework I implemented in action and catching bugs on a full Airsource project gives a wonderful sense of achievement. </p>
<p>As Airsource is a mobile software company I got to write mobile applications too; one a debugging aid and another for a client. Not only did this teach me the S60 and BREW platforms, on-device debugging and about memory management, but it also showed me how versatile mobile phones are these days.</p>
<p>The work has been challenging, and there was a very steep learning curve at the beginning; but from my first day my colleagues have been have been great at getting me up to speed with programming languages and concepts. During my time here I've learnt two C++ dialects for mobile phones, and learnt Python from scratch. As a result of this my understanding of topics such as OOP has improved immensely. </p>
<p>One of the things I didn't expect to like when I first heard about it were <em>code reviews</em>. But code reviews have turned out to be one of the best things about this summer (I kid you not). It can be hard having your hours of labour being picked apart, but you learn, the reviewer learns and the product becomes better because of it. Code reviews are one of the main reasons why I have improved as a programmer this summer, and I would recommend all companies do them.</p>
<p>It has been a most enjoyable summer here at Airsource, and yes I would do this again!</p>Yahoo! BluePrint announced2008-09-11T08:48:00+01:002008-09-11T08:48:00+01:00Nick Clareytag:airsource.co.uk,2008-09-11:/blog/2008/09/11/yahoo-blueprint-announced/<p><a href="http://mobile.yahoo.com/developers/roadmap"><img alt="blueprint_logo" src="https://airsource.co.uk/blog/images/2008/09/blueprint_logo.png"></a></p>
<p>Yesterday at <a href="http://www.wirelessit.com/">CTIA Wireless IT and Entertaiment</a> Yahoo! announced their contribution to the mobile platform wars in the form of an XML-based language named BluePrint. BluePrint allows the generation of mobile applications for Windows Mobile and S60, as well as widgets that run inside the Yahoo! Go environment. Yahoo! has provided us an overview <a href="http://mobile.yahoo.com/developers/roadmap">here</a>.</p>
<p>The quest for "write once, run anywhere" continues.<p><a href="http://mobile.yahoo.com/developers/roadmap"><img alt="blueprint_logo" src="https://airsource.co.uk/blog/images/2008/09/blueprint_logo.png"></a></p>
<p>Yesterday at <a href="http://www.wirelessit.com/">CTIA Wireless IT and Entertaiment</a> Yahoo! announced their contribution to the mobile platform wars in the form of an XML-based language named BluePrint. BluePrint allows the generation of mobile applications for Windows Mobile and S60, as well as widgets that run inside the Yahoo! Go environment. Yahoo! has provided us an overview <a href="http://mobile.yahoo.com/developers/roadmap">here</a>.</p>
<p>The quest for "write once, run anywhere" continues. Unfortunately, after many years of bitter experience working in the mobile space, I have come to realise that the goal of a single environment that will give you a good quality application experience on all platforms is doomed to failure. Every mobile device has its fair share of idiosyncracies and unique advantages and features. A "cross-platform" toolkit like BluePrint is unfortunately doomed to deliver you a mobile experience that, although workable, is going to be too slow, too bloated, too limiting and too disconnected from the device it is running on. If you really want to produce a great mobile application you need to develop it with an appreciation for the underlying platform and not just opt for the lowest common denominator.</p>
<p>My current favourite example is the iPhone/iTouch development environment. Apple has put the brakes on Flash content and Java content, not because they want to spoil Adobe and Sun's party, but because they know that the only way they will encourage the development of great quality applications is by forcing developers to appreciate the API and the platform they have assembled. Allowing cross-platform content on would result in a very bland and dysfunctional collection of applications. Pushing developers into Objective-C and the Touch environment forces them to rethink their application approaches rather than just cranking the handle on the last Java app they wrote.</p>
<p>We will run BluePrint through the wringer in due course; before we even embark on that particular expedition, though, I feel I know what is coming.</p>Wordpress for iPod/iPhone2008-08-29T16:10:00+01:002008-08-29T16:10:00+01:00Nick Clareytag:airsource.co.uk,2008-08-29:/blog/2008/08/29/wordpress-for-ipodiphone/<p>I've recently been playing with <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=285073074&mt=8">Wordpress</a> for the iPod Touch and iPhone. It's a very simple application which allows you to write posts while you're on the go, and post them to your blog over whichever network connection is most convenient.</p>
<p>My impressions so far are that it seems to …</p><p>I've recently been playing with <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=285073074&mt=8">Wordpress</a> for the iPod Touch and iPhone. It's a very simple application which allows you to write posts while you're on the go, and post them to your blog over whichever network connection is most convenient.</p>
<p>My impressions so far are that it seems to work pretty much as advertised. You can review blog posts written by others, change their status or edit them, and add new posts. You can even attach photos that may be on your device - although there isn't really any way to take photos from the touch, so iPhone users will be the only ones to really benefit.</p>
<p>Overall - very simple and does what it says on the tin. It doesn't solve your bad iPod typing (if you have fat fingers like me you might be struggling to write longer posts without them reading like Klingon) but it does give you another way of getting your musings up on a blog from your cafe of choice.</p>
<p>Overall, I'd give it 3/5.</p>Spill chucking2008-08-26T08:41:00+01:002008-08-26T08:41:00+01:00Ben Blaukopftag:airsource.co.uk,2008-08-26:/blog/2008/08/26/spill-chucking/<p>Just got a letter, from a source that will remain nameless... It was addressed to "Ben BLAUNITEDKINGDOMOPF". Sounds like someone needs to rethink exactly what parts of the address field get auto-completion...</p>SSH on the iPhone and iPod Touch2008-08-20T19:26:00+01:002008-08-20T19:26:00+01:00Iestyn Prycetag:airsource.co.uk,2008-08-20:/blog/2008/08/20/ssh-on-the-iphone-and-ipod-touch/<p>SSH is undoubtedly a useful tool and the iPhone and iPod Touch are great portable ways of connecting to networks; put both together you can be a sysadmin on the move! So what are the options for this? The Apple AppStore has a few SSH clients, I decided to take three - <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=287887578&mt=8">SSH</a>, <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=287765826&mt=8">iSSH</a> and <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=286623227&mt=8">TouchTerm</a> - out for a spin.<p>SSH is undoubtedly a useful tool and the iPhone and iPod Touch are great portable ways of connecting to networks; put both together you can be a sysadmin on the move! So what are the options for this? The Apple AppStore has a few SSH clients, I decided to take three - <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=287887578&mt=8">SSH</a>, <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=287765826&mt=8">iSSH</a> and <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=286623227&mt=8">TouchTerm</a> - out for a spin.</p>
<h1>SSH</h1>
<p>First up is <strong>SSH</strong>, marketing at $3.99 (£2.39). Opening the program greets you with a screen where you can type in an address to connect to in the form <code>user@hostname:port</code> (the address is added to a list on the first screen so you don't have to type it next time). Once this has been entered you're presented with a password entry screen, although you have to press the password entry box to get a keyboard.</p>
<p>If this is successful you get a retro green monospace on black terminal, with a white command line bar at its bottom. This white command line is the only way you can send commands over ssh, you press it to bring up a keyboard with which you type your command and press 'Send'. This command is then echoed to the terminal and executed. Basic shell commands work well, but once you want to use
some basic programs such as <code>less</code>, <code>more</code> or <code>man</code> problems start to show.</p>
<p><img alt="SSH command line" src="https://airsource.co.uk/blog/images/2008/08/ssh_ls_color.png"></p>
<p>Navigation is the main problem with these programs through this client, I'm used to using the arrow and return keys to navigate them (I know there are other ways, but these are the ones I remember). The client has no emulation of arrow keys, so we lose that form of navigation and there is no return key on the keyboard. After looking up some of the other navigation commands available for these programs I was able to navigate them quite successfully, but the process of touch command bar->type command->send for every bit of navigation felt very tedious.</p>
<p>Another basic command I tried and expected to work was <code>top</code>, but alas <strong>SSH</strong> fails again and produced this output:</p>
<p><img alt="SSH and top" src="https://airsource.co.uk/blog/images/2008/08/ssh_top.png"></p>
<p>which is hardly usable. You can try and scroll the output, using screen swipes, but each time top refreshes your scrolling is lost.</p>
<p>As arrow keys are not implemented command line history is harder, meaning you have to type in commands to access your history, which is annoying and slow if you are using <strong>SSH</strong>'s input method. </p>
<p>The client allows you to use control commands using the ^ key, so Ctrl-C becomes ^c. This works and is functional, but to reach the ^ key you have to go through two keyboards, which is annoying. Why can't there be a Ctrl button on the screen ready for me to use whichever keyboard I'm in?</p>
<h1>iSSH</h1>
<p>Disappointed with <strong>SSH</strong> I went on to try <strong>iSSH</strong> (marketing at $4.99 or £2.99). When you start it up for the first time you must add a connection configuration. This is a form where you fill in a name for the configuration, the hostname and at your choice a username and command to execute on connection.</p>
<p><img alt="iSSH configuration form" src="https://airsource.co.uk/blog/images/2008/08/issh_add_configurations.png"></p>
<p>With a configuration now saved I could choose to connect to it. This dropped me into a terminal where <code>ssh</code> asked me for a password, the bottom half of the screen is filled with a keyboard which types directly into the console. The terminal used is much better than the one <strong>SSH</strong> used, it has a gray monospace font on a black background, and it supports colours which makes using such a small screen more bearable.</p>
<p>There is a bar above the terminal which contains buttons for the Ctrl, Alt, Esc, Tab, F# and Shift keys (which can be used in combination), along with a keyboard toggle and exit. This allows you to use niceties such as tab complete on the command line, a great time saver when using the iPhone's user input. Being able to toggle the keyboard is rather useful if you wish to read a file using <code>less</code>; you can invoke <code>less</code> then hide the keyboard giving the whole screen to the file which you can navigate using the emulated arrow keys.</p>
<p>Seeing that <strong>SSH</strong> had been so abysmal when I tried to run <code>top</code> I tried to run <code>top</code> on <strong>iSSH</strong>, which produced this result: </p>
<p><img alt="iSSH with top" src="https://airsource.co.uk/blog/images/2008/08/issh_top.png"></p>
<p>Hurrah! <code>top</code> is usable using iSSH. Running <code>more</code>, <code>less</code> and <code>man</code> was also successful using <strong>iSSH</strong>, which gives access to arrow keys through swiping the left 2/3 of the screen, and the keyboard has a return key (the right 1/3 of the screen is reserved for scrolling). This means you can also use the arrow key
emulation to scroll the command line history.</p>
<p>Tilting the device the appropriate way will change the display from portrait to landscape, which is a neat feature, especially useful if you're reading files as you have width in the screen.</p>
<p><a href="https://airsource.co.uk/blog/images/2008/09/new_issh_landscape.png"><img alt="new_issh_landscape" src="https://airsource.co.uk/blog/images/2008/09/new_issh_landscape.png"></a></p>
<p>Multiple sessions are supported by <strong>iSSH</strong>, after you've hit the exit button your session doesn't disconnect, you're dropped into a list of open sessions, from which you can choose which session to see on the screen or you can go back to the main opening screen to connect to a new session. Within your sessions you can swipe horizontally to move to different sessions, like one does on the iPhone's main menu screen; this usually works well but sometimes it gets tangled up with scrolling swipes which ends up with you in the wrong session and scrolled up too far.</p>
<h1>TouchTerm</h1>
<p><strong>TouchTerm</strong> is the cheapest of this bunch at $2.99 (£1.79), and is based OpenSSH and OpenSSL. The opening screen allows you to connect to servers and manage your connections. You can connect as a 'One-Time Connection' or you can save a connection profile for future use (these profiles can also be edited in the future if you want).</p>
<p>There is a settings page which allows you to configure how text is entered, the display and security. One of the choices under text entry is whether or not to enter code directly into the terminal, if you choose not to all the text you enter goes into a text box before it's echoed to the terminal. There are numerous display settings, among which are colours for the cursor, foreground and background, as well as the font size. For security you can set options for how keys are kept and whether or not to save passwords. </p>
<p>Once you've chosen a connection to make and you've connected with the server you are presented with a white text entry box where you enter your password, below which is the standard terminal <code>foo@bar.com's password:</code>prompt. The password letters are echoed for a few seconds in the textbox before turning into a *, helping you know if you've mistyped on the onscreen keyboard.</p>
<p>Above the terminal is a bar with some extra buttons, 'Clear', 'Ctrl', 'Tab' 'Esc', a button for starting arrow key emulation and one to go to the settings page. The arrow key emulation is rather neat, it overlays four translucent arrow keys above the terminal, which makes things such as accessing command line history navigation rather nice.</p>
<p><img alt="TouchTerm Arrows" src="https://airsource.co.uk/blog/images/2008/09/touchtermarrows.png"></p>
<p>The terminal emulator used doesn't seem to support terminal colours, so running e.g. <code>ls --color</code> doesn't work, even though you can set colours for the foreground and background, rather annoying.</p>
<p>Running standard programs using <strong>TouchTerm</strong> is successful although it does feel a bit sluggish. <code>top</code> for instance displays quite well, but when interacting with <strong>TouchTerm</strong>'s controls there is a delay between pressing/swiping/whatever and the action taking place. Running <code>less</code> works, however there's a second or two delay for the cursor to jump to the bottom of the screen after you've navigated, which makes the experience feel clunky.</p>
<p><strong>TouchTerm</strong> can be used in both portrait and landscape modes and the keyboard can be hidden/unhidden by just tapping a couple of times on the emulated terminal. Scrolling is done with swipes of the screen, but this allows you to scroll off the edge of the terminal emulator giving you an ugly gray background, however once you've released your swipe the terminal will reposition so that it's edges and the screen's edges match. </p>
<h1>Conclusion</h1>
<p>Comparison chart of the clients:</p>
<p><a href="https://airsource.co.uk/blog/images/2008/09/ssh-table2.png"><img alt="comparison table" src="https://airsource.co.uk/blog/images/2008/09/ssh-table2.png"></a></p>
<p>In terms of functionality <strong>iSSH</strong> and <strong>TouchTerm</strong> beat <strong>SSH</strong> hands down. Depending on what the user is looking for in their <code>ssh</code> client will decide on if they prefer <strong>iSSH</strong> or <strong>TouchTerm</strong>. The former has the feature of multiple sessions and supports terminal colours properly, while the latter seems slightly more user friendly with the font size configuration and profile editing. Both make <code>ssh</code> usable on the iPhone.</p>
<p><strong>UPDATE</strong>
Added review of <strong>TouchTerm</strong> and updated the review of
<strong>iSSH</strong> to cover version 1.1 (from 1.0). </p>'It Just (doesn't) Work'2008-08-07T16:14:00+01:002008-08-07T16:14:00+01:00Ben Blaukopftag:airsource.co.uk,2008-08-07:/blog/2008/08/07/it-just-doesnt-work/<p>The new iPhone SDK requires that developers upgrade to OS X Leopard, which is a nice excuse for most of us to drop 100 bucks on a new operating system that does, err, exactly what the old one did. I am sure I'll come across some amazing new feature that …</p><p>The new iPhone SDK requires that developers upgrade to OS X Leopard, which is a nice excuse for most of us to drop 100 bucks on a new operating system that does, err, exactly what the old one did. I am sure I'll come across some amazing new feature that I couldn't live without soon enough... Obviously there is the addition of TimeMachine, but funnily enough I was already doing backups.</p>
<p><a href="https://airsource.co.uk/blog/images/2008/08/leopard.bmp"><img alt="leopard.bmp" src="https://airsource.co.uk/blog/images/2008/08/leopard.bmp"></a></p>
<p>One thing I was expecting was a seamless upgrade. I was sorely disappointed. The first complaint from the Leopard Installer DVD was that my disk had the wrong partition map. I upgraded my macbook's hard drive a year or so ago, and apparently had selected APM instead of GUID when I installed. To fix that, I followed the instructions at <a href="http://www.macosxhints.com/article.php?story=2007102511133285">http://www.macosxhints.com/article.php?story=2007102511133285</a> which took several hours (LOTS of copying data to/from external drives). This involved the use of an excellent program called <a href="http://www.shirt-pocket.com/SuperDuper/SuperDuperDescription.html">Super Duper</a> which I am happy to report does Just Work.</p>
<p>The next complaint was harder to resolve. The verification process failed half-way through installing. Skipping the verification resulted in a failed install (not on my main drive, on my backup!). Googling revealed that apparently this problem was not uncommon, and was related to after-market RAM upgrades (that's me) - though doing a full RAM soak test revealed no problems at all. I eventually managed to install the thing by restoring the DVD onto a partition on my external drive, and then booting from that. </p>
<p>The iPhone SDK was, fortunately, easier to install!</p>Homogenous Hardware?2008-07-30T19:24:00+01:002008-07-30T19:24:00+01:00Ben Blaukopftag:airsource.co.uk,2008-07-30:/blog/2008/07/30/homogenous-hardware/<p>I have just been playing around with <a href="http://http://www.skybound.ca/">Stylizer</a>, a Windows CSS editor. Why, you may ask, is the CTO of a mobile software company messing around with CSS editors? A very good question. Someone was extolling the virtues of this program on the <a href="http://discuss.joelonsoftware.com/?biz">Business of Software forum</a>, and how everyone …</p><p>I have just been playing around with <a href="http://http://www.skybound.ca/">Stylizer</a>, a Windows CSS editor. Why, you may ask, is the CTO of a mobile software company messing around with CSS editors? A very good question. Someone was extolling the virtues of this program on the <a href="http://discuss.joelonsoftware.com/?biz">Business of Software forum</a>, and how everyone should take a look at the first-run experience. I went to the website, which is simple, clean, and attractive, and downloaded the program, which instantly launches into the tutorial.</p>
<p>Stylizer's tutorial is well implemented, and compelling. I am no expert on CSS, and not much more knowledgable about CSS than I was when I started the tutorial, but I know a bit more about how to use Stylizer, and I agree that it is easy to use. Right up to the point where it asked me to hold down my left mouse button and then click with the right <em>while the left button was still down</em>. </p>
<p>Now, not only did I have to read that twice to work out what I needed to do, I could not actually do it. I use an Apple Mighty Mouse, and it is simply not possible to press <em>any</em> two buttons simultaneously, unless you press very hard enough (i.e. hard enough to break it).</p>
<p><a href="https://airsource.co.uk/blog/images/2008/07/apple_mighty_mouse-copy.png"><img alt="All mice are not equal" src="https://airsource.co.uk/blog/images/2008/07/apple_mighty_mouse-copy.png"></a></p>
<p>Up to this point, all the shortcuts had been with the keyboard, so I didn't quite see why we were suddenly using the mouse, particularly in a click combination that is hardly standard. In the case of Stylizer, it is simple enough just to achieve the desired action (insert a new rule) using the Insert key, but I am left asking myself <em>why</em> someone found it necessary to come up wth a completely non-standard action to implement a common action. In the same thought, I realise that Apple disabled simultaneous button clicks on the Mighty Mouse precisely to prevent people coming up with complex, non-standard UIs. More power to them.</p>Symbian OS goes OS2008-06-24T21:33:00+01:002008-06-24T21:33:00+01:00Ben Blaukopftag:airsource.co.uk,2008-06-24:/blog/2008/06/24/symbian-os-goes-os/<p>Airsource woke up this morning to Nokia's announcement to make Symbian an Open Source platform, and with it all the concrete platforms like S60, UIQ, and so forth. While Motorola, Sony Ericsson, and NTT DoCoMo are all mentioned in the press release, it seems to me that they had little …</p><p>Airsource woke up this morning to Nokia's announcement to make Symbian an Open Source platform, and with it all the concrete platforms like S60, UIQ, and so forth. While Motorola, Sony Ericsson, and NTT DoCoMo are all mentioned in the press release, it seems to me that they had little choice but to jump on board. From an Airsource perspective, S60 pretty much meant Nokia - and now Nokia will mean S60. At the very least, that might make our sales presentations easier.</p>
<p>This announcement is good news in two ways for Airsource's business. Firstly, from a commercial perspective, it removes some of the cloud of doubt about what Android means for the market. Android did not exactly threaten the future of S60, the established leader in the convergent devices market, but it did cast some doubt on the subject. How would an open source competitor affect the market? Businesses, of course, hate doubt, and while S60 was ahead of Android on pretty much everything apart from source access, the playing field there has now been made more level. Obviously a $1500 charge (annual) for source access is not free, but it is vastly cheaper than the old fee to become a Symbian Platinum Partner. </p>
<p>Secondly, from a technical perspective, access to the underlying platform code helps the developer produce more stable products, faster. Some areas of code are always technically more complex and more poorly documented than others, such as MTMs. Access to source code will significantly ease development on the really cool stuff.</p>
<p>We at Airsource look forward to seeing more developments.</p>Cambridge CAMRA Beer Festival2008-05-21T11:42:00+01:002008-05-21T11:42:00+01:00Nick Clareytag:airsource.co.uk,2008-05-21:/blog/2008/05/21/cambridge-camra-beer-festival/<p><a href="https://airsource.co.uk/blog/images/2008/05/dscn0201.JPG"><img alt="A banner declaring the event" src="https://airsource.co.uk/blog/images/2008/05/dscn0201.thumbnail.JPG"></a></p>
<p>The Cambridge CAMRA Beer Festival has arrived in town again.</p>
<p>An annual event in Cambridge, the Beer Festival features hundreds of brews from all over the UK and Europe. The key message is that the best beer is "Real Ale"; not stuffed with preservatives and artificially carbonated but the genuine, hand-crafted article.<p><a href="https://airsource.co.uk/blog/images/2008/05/dscn0201.JPG"><img alt="A banner declaring the event" src="https://airsource.co.uk/blog/images/2008/05/dscn0201.thumbnail.JPG"></a></p>
<p>The Cambridge CAMRA Beer Festival has arrived in town again.</p>
<p>An annual event in Cambridge, the Beer Festival features hundreds of brews from all over the UK and Europe. The key message is that the best beer is "Real Ale"; not stuffed with preservatives and artificially carbonated but the genuine, hand-crafted article.</p>
<p>As a <strike>team bonding</strike> training exercise, the Airsource team went along to learn more about Real Ale and to sample a few ounces of the amber brew. The work was strictly investigative and rigorously scientific.</p>
<p><a href="https://airsource.co.uk/blog/images/2008/05/dscn0212.JPG"><img alt="Inside the main hall" src="https://airsource.co.uk/blog/images/2008/05/dscn0212.thumbnail.JPG"></a></p>
<p>The main hall features food and, obviously, large quantities of beer.</p>
<p>A few hints for those unaccustomed to the beer festival;</p>
<ul>
<li>Work a half-pint at a time</li>
<li>The cheese stall is brilliant</li>
<li>Leave the more potent brews for later in the evening</li>
</ul>
<p><a href="https://airsource.co.uk/blog/images/2008/05/dscn0206.JPG" title="Inside the Cambridge Beer Festival compound."><img alt="Much Merriment Outdoors" src="https://airsource.co.uk/blog/images/2008/05/dscn0206.thumbnail.JPG"></a></p>
<p>My vote for the best beer experienced this year is <a href="http://www.bartramsbrewery.co.uk/comrade-bill.html">"Comrade Bill Bartrams Egalitarian Anti Imperialist Soviet Stout"</a> which is very much worth your time and energy.</p>
<p><a href="https://airsource.co.uk/blog/images/2008/05/dscn0205.JPG" title="As part of our scientific study, obviously some beer needs to be consumed."><img alt="The team" src="https://airsource.co.uk/blog/images/2008/05/dscn0205.thumbnail.JPG"></a></p>Yotel just another Hotel2008-05-20T14:28:00+01:002008-05-20T14:28:00+01:00Ben Blaukopftag:airsource.co.uk,2008-05-20:/blog/2008/05/20/yotel-just-another-hotel/<p><img alt="Yotel Galley" src="https://airsource.co.uk/blog/images/2008/05/image001.thumbnail.jpg" title="Yotel Galley"></p>
<p>I stayed at the <a href="http://www.yotel.com">Yotel</a> in Heathrow Terminal 4 the other day. I had an 11am flight to the US, and decided that instead of a 6am start from Cambridge, it made much more sense to stay literally 50m away from the British Airways checkin desk. This made me an accidental, but much appreciated, beneficiary of the T5 shambles - BA are keeping a lot of their long-haul flights in T4 until the new terminal has properly bedded down.<p><img alt="Yotel Galley" src="https://airsource.co.uk/blog/images/2008/05/image001.thumbnail.jpg" title="Yotel Galley"></p>
<p>I stayed at the <a href="http://www.yotel.com">Yotel</a> in Heathrow Terminal 4 the other day. I had an 11am flight to the US, and decided that instead of a 6am start from Cambridge, it made much more sense to stay literally 50m away from the British Airways checkin desk. This made me an accidental, but much appreciated, beneficiary of the T5 shambles - BA are keeping a lot of their long-haul flights in T4 until the new terminal has properly bedded down.</p>
<p>I arrived at the Yotel's reception, aka "galley" at about 10pm. After a slight modification to my booking, I headed off to my "cabin" (there's a bit of a shipping theme), gained access with the ubiquitous card lock, and then headed straight for the shower. Two seconds later I realised that there was a window in my room, facing directly onto the corridor! I promptly closed the blind, but I really don't see the point of this window. Why would I want to be able to see onto the corridor - and vice versa?</p>
<p>The Yotel concept is that you get a small room - about seven square metres - comprising an electrically folding bed (think business class seats) which is actually pretty comfy, a bathroom, a tv, and so forth. There's even a desk and chair, both of which fold away. Except the desk didn't. It got stuck half-way down. </p>
<p><img alt="Yotel Broken Desk" src="https://airsource.co.uk/blog/images/2008/05/image005.thumbnail.jpg" title="Broken desk"></p>
<p>Fortunately it didn't obstruct the bed, but it did mean once the bed was extended, the only path from bathroom to door was to climb over the bed.
Careful readers will be wondering where your baggage goes. Yotel have thought of that - the bed is pretty high off the floor, and there's storage space underneath it. There's also somewhere to hang a suit up.</p>
<p><img alt="Yotel luggage storage" src="https://airsource.co.uk/blog/images/2008/05/image000.thumbnail.jpg" title="Luggage storage"></p>
<p><img alt="Yotel Console" src="https://airsource.co.uk/blog/images/2008/05/image003.thumbnail.jpg" title="Console"></p>
<p>The desk has power points and a network connection just above it, though I have to report that the network connection didn't work. For the technically minded, it assigned an IP address, but failed to pass any packets. Rather than try and fix Yotel's network for them, I just went outside (too much metal inside to get a signal) and made a phone call instead, over a pleasant pint at Weatherspoons.</p>
<p>Apart from the desk and the network connection, I was pretty pleased with the room. There was some building work inside Heathrow that night, but earplugs (available from reception) completely blotted that out - and if you sleep in a standard airport hotel you just get to hear planes and traffic in any case. I slept well (almost too well, I missed my watch alarm, but had set up a backup wakeup call on the TV, which I recommend, because it turned the lights on as well), the shower was good, and it was a 1 minute walk to check-in in the morning. </p>
<p>Does it have some issues? Yes. Would I stay again? Yes.</p>Mobile Device Databases2008-05-02T17:27:00+01:002008-05-02T17:27:00+01:00Ben Blaukopftag:airsource.co.uk,2008-05-02:/blog/2008/05/02/mobile-device-databases/<p>I've just been taking a look at <a href="http://www.deviceatlas.com">Device Atlas</a>, which in its own words is the "world's most comprehensive database of mobile device information". I had high hopes, but unfortunately it appears to be rehash of the kind of information which is readily available, without anything more useful. I appreciate …</p><p>I've just been taking a look at <a href="http://www.deviceatlas.com">Device Atlas</a>, which in its own words is the "world's most comprehensive database of mobile device information". I had high hopes, but unfortunately it appears to be rehash of the kind of information which is readily available, without anything more useful. I appreciate that this database is not designed for my sole delectation, but I surely can't be alone in wanting to get a list of devices running S60 FP1 which have a camera? Not only does the Device Atlas apparently not allow any sort of search, it doesn't tell me what operating system a phone runs, what JSRs it has, or allow any kind of user commenting (e.g. "HTTPS is broken on this device"). </p>
<p>My favourite part of the site is in the FAQ, where it says "Can I have a job? Yes, we're hiring world class staff for the DeviceAtlas project. Call us." Errr, call who? There's no phone number. Click on "Contact" on the nav bar at the bottom of the screen. Still no phone number. I presume you're supposed to call dotMobi (one more click away) rather than Device Atlas, but that's far from clear. Also in the FAQ, we read that "We intend this to be the single largest, most comprehensive, and accurate device database on the planet". A worthy goal - but Device Atlas has a long way to go to reach it.</p>What platform should I write my app for?2008-04-28T14:20:00+01:002008-04-28T14:20:00+01:00Ben Blaukopftag:airsource.co.uk,2008-04-28:/blog/2008/04/28/series-60-versus-brew/<p>When we set up Airsource, we set it up as a BREW consultancy. We rapidly sold a number of BREW projects, and built on the expertise we had acquired while at QUALCOMM. In the process, however, we inevitably found ourselves working on other software platforms, particularly on Series 60, which now accounts for about half of Airsource's work. Series 60 and BREW are often held up as competitors, though in practice I would argue quite strongly that they target very different markets. <p>When we set up Airsource, we set it up as a BREW consultancy. We rapidly sold a number of BREW projects, and built on the expertise we had acquired while at QUALCOMM. In the process, however, we inevitably found ourselves working on other software platforms, particularly on Series 60, which now accounts for about half of Airsource's work. Series 60 and BREW are often held up as competitors, though in practice I would argue quite strongly that they target very different markets. </p>
<p>A BREW phone, such as the Motorola V3M has a primary display is 176x220, it has 23MB of memory, and a processor clocked at perhaps 40MHz. A Nokia E65 costs nearly twice as much, has twice as many pixels, can store five times as much, and runs fives times faster. Pundits will immediately point out that the V3M is a pretty slow example of a BREW phone. That's certainly true, but the V3 series accounts for a very significant fraction of the US market for BREW phones. The Nokia E65 is similarly chosen for comparison as an example of a popular Series 60 v3 phone.</p>
<p>Comparing the two is pointless. The BREW phone is clearly a much lighter-weight platform, targetted primarily at games. The Symbian phone has considerably more memory, more storage, faster CPU, and generally faster network data transfer. It is also significantly harder to program for - in the same way that coding for a Windows XP PC requires rather more knowledge than writing a program in BBC BASIC did. A Symbian phone is simply more capable than a BREW one, and consequently the APIs are richer, and the learning curve steeper.</p>
<p>That's not to say that that Airsource's clients do not want to target their application at both BREW and at Symbian. They absolutely do. But the Symbian application will always have more functionality and tighter device integration than the BREW application, in the same way that a dedicated BREW application will have more functionality and tighter device integration than a web-based application. Clients come to us because they want to get the most out of the phone, and to do things that are non-trivial, whether that be linking into the messaging menu of a Symbian phone, porting a multi-process application to BREW, or simply writing an application that Just Works. </p>
<p>What this means is that while the mobile market is certainly split into markets that may not directly compete with <strong>every</strong> other platform, they form an overlapping whole that represents the mobile space. Companies want to get their application "on mobile", and by that, they mean on the maximum number of phones, with users who will spend money, for as little cost as possible. Choice of platform, like Symbian, or BREW, is therefore just a business decision, not a technical one.</p>En route to the Mobile World Congress2008-02-12T06:15:00+00:002008-02-12T06:15:00+00:00Nick Clareytag:airsource.co.uk,2008-02-12:/blog/2008/02/12/en-route-to-the-mobile-world-congress/<p>It's 5:30am and the annual wireless industry hoe-down (now in it's second day) is taking place in Barcelona. In order to avoid the rush, crowds and expensive Easyjet flights, I'm flying out a day late and looking forward to see what's in store for the industry during this coming year.<p>It's 5:30am and the annual wireless industry hoe-down (now in it's second day) is taking place in Barcelona. In order to avoid the rush, crowds and expensive Easyjet flights, I'm flying out a day late and looking forward to see what's in store for the industry during this coming year.</p>
<p><a href="https://airsource.co.uk/blog/images/2008/02/picture.jpg" title="Stansted is gradually becoming a nice airport..."><img alt="Stansted at 6" src="https://airsource.co.uk/blog/images/2008/02/picture.thumbnail.jpg"></a></p>
<p>The wireless industry seems to have a "pulse" - there are a flurry of handset releases around Easter and a blizzard of handsets released around Christmas. This release cycle feeds back into the software development industry, so if you work back from handset release dates you can get a picture of what your year will look like. Subtract 4-8 weeks for handset field testing and another few weeks for certification and you can see that you have certain dead spots, at least during the months of January and August.</p>
<p>Certainly what we discovered last year was that we experienced a real "dead spot" during the time of 3GSM (the precursor to MWC). The reason for this seems to be that everyone is either getting ready for or coming down from the conference. Someone pointed out to me, though, that rather than dreading the dead spots you need to enjoy them because in this industry they don't last for very long.</p>
<p>What are we looking forward to this year? What is coming down the pipeline in the industry that may change the way we all do business? The big ticket items;</p>
<ul>
<li>No more Motorola</li>
<li>Good flat rate data tariffs</li>
<li>The iPhone SDK</li>
<li>An Android handset</li>
</ul>
<p>Motorola has received a good kicking of late, and despite defining the early years of mobile and having the smallest and grooviest handsets a few years ago, they seem to have lost their way. An ex-Motorolan myself, I can say that they have some truly awesome engineers on staff but there is a distinct lack of leadership and vision in the middle and upper ranks. So what's to happen to them? The most likely scenario I can see is that they are bought up by a far-Eastern manufaturer and quietly sink into oblivion. Maybe Google can buy them and use their handset wisdom to seed the market with Android. Yeah, not likely. So I think Motorola's handset business is history, and judging by their dribble of handset releases at MWC it's looking like it won't be long before the mobile phone pioneer has to make a quiet exit.</p>
<p>Good flat rate data tariffs may be on the way as well. The <a href="http://www.forbes.com/markets/feeds/afx/2008/02/11/afx4638622.html">EU Commissioner Viviane Reading</a> has cracked the whip on data roaming charges and maybe this will spur the lumbering operators into action to set up flat rate tariffs that are affordable and reasonable for all. Cheap data access will be a spur to the small, hungry handset software development community and will transform the landscape. All it takes is one...</p>
<p>It's unlikely we'll hear much about the iPhone SDK before the <a href="http://www.macrumors.com/2008/02/07/apple-event-in-last-week-of-february/">end of Feb</a>. As a newcomer to the industry, Apple forges it's own path. I'm not, for example, expecting to see much activity from them at MWC. They want to control their own coverage very carefully and orchestrate their own events. Maybe they will have a sideline event of some kind. But I'm not expecting much activity.</p>
<p>My colleague Teanlorg has already given us some <a href="https://airsource.co.uk/blog/2008/02/07/android-code-day/">analysis of Android</a> and the challenges that await Google on the device. Reports suggest some early device prototypes are creeping out at MWC so I will keep my eyes peeled for them. If a device is in prototype form now, though, this suggests that an end-of-year launch is still a possibility. But if we haven't seen anything more solid by mid-year we could be waiting until well into 2009 before anything emerges.</p>
<p>There's a lot to look forward to this year in the mobile world. New platforms, old players fading out and hopefully cheaper network access to come. I'll be bringing you more from the show and surrounding events over the coming days.</p>Android Code Day2008-02-07T13:39:00+00:002008-02-07T13:39:00+00:00Teanlorg Chantag:airsource.co.uk,2008-02-07:/blog/2008/02/07/android-code-day/<p>After a much-awaited <a href="http://www.fchouse.com/archives/release-day">Release Day</a> and the obligatory pub afterwards, last Thursday marked the end of January and <a href="http://android-developers.blogspot.com/2008/01/posted-by-dan-morrill-developer.html">Android Code Day</a> in London and Tel Aviv. <strike>It was my first time in Israel, which made it more exciting</strike> As much as I'd like to visit Israel, I wouldn't be able to justify the expense (it might happen in the future with the current trend in ticket prices). The good news is that the train journey into London is relatively short and painless.<p>After a much-awaited <a href="http://www.fchouse.com/archives/release-day">Release Day</a> and the obligatory pub afterwards, last Thursday marked the end of January and <a href="http://android-developers.blogspot.com/2008/01/posted-by-dan-morrill-developer.html">Android Code Day</a> in London and Tel Aviv. <strike>It was my first time in Israel, which made it more exciting</strike> As much as I'd like to visit Israel, I wouldn't be able to justify the expense (it might happen in the future with the current trend in ticket prices). The good news is that the train journey into London is relatively short and painless.</p>
<p>After waiting around in the lobby, we were eventually escorted through brief NDA-signing (is it legally binding if there's no expectation for you to read it at all?) and then upstairs, into a meeting room just big enough to hold 40 people and their laptops without feeling crowded.</p>
<p>The first half of the day was taken up by a brief presentation by Jason Chen, interleaved with a not-so-brief and highly interactive question-and-answer session. Then there was lunch, with the usual perks (who wouldn't work for Google for a freezer full of Ben and Jerry's?) and some insight into Google's business strategy.</p>
<p><img alt="Top-secret plans" src="https://airsource.co.uk/blog/images/2008/02/dscn0100-dscn0102-copy.thumbnail.jpg" title="Top-secret plans"></p>
<p>We started the afternoon with a brief demo of an Android app from an audience member. It was pretty decent for only two days' worth of code and some borrowed sprites -- maybe Android phones will compete with the PSP (in keeping with tradition, they've tested Quake on Android prototypes, with reasonable performance).</p>
<p><img alt="A familiar game…" src="https://airsource.co.uk/blog/images/2008/02/dscn00952.thumbnail.png" title="A familiar game…"></p>
<p><img alt="... stretched by OpenGL in landscape mode" src="https://airsource.co.uk/blog/images/2008/02/dscn0098.thumbnail.png" title="…stretched by OpenGL in landscape mode"> </p>
<p>The "hackathon" commenced, but as the average level of experience was "has compiled and run the hello world app" (and the documentation is written in the traditional Javadoc style that never manages to tell you how to do what you want to do), I don't think much coding actually happened -- that wasn't really the point of the day. Jason Chen is a Developer Advocate; so's Dan Morrill (the first link in this article). He writes "I get to travel the world to meet developers and talk about cool technologies. I can't believe I get paid for this!" though Google is keen on hiring more Developer Advocates.</p>
<p>It's not hard to see why: the room seemed to consist of people from handset manufacturers, mobile OS companies, people wanting to see if Android is worth investing time into, and people there just to keep track of what Google was up to -- in fact, the last category seems to cover everyone.<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup> One of the most promising things about Android is an abundance of cool, novel, well-written, <em>currently nonexistent</em> apps -- it's a chicken-and-egg, and Google can only hope that apps are easier to produce than users (what do <em>you</em> think the <a href="http://code.google.com/android/adc.html">Android Developer Challenge</a> hopes to accomplish?).</p>
<p>Of course, it's really <em>future users</em> that give decision-makers the incentive to spend money on a particular platform. Until we invent time machines, we'll have to settle for <em>a significantly high probability of a significant number of future users</em>. The greatest impediment is actually having phones for sale, but the problems there are largely political, and if I started on politics I'd be here all day. Once phones are on the market, there's another problem:</p>
<p><strong>Android has to be good.</strong></p>
<p>And by "good", I mean better than the next phone on the shelf. Of course, customers define what's "better", but what do you see when you go phone shopping?</p>
<ul>
<li>The brand is difficult to change.</li>
<li>The design is difficult to do well, but at least the OHA has Motorola.</li>
<li>The "specs" that the shop decides to show are rather arbitrary.</li>
<li>The price matters, but in the UK, it's largely "free on contract" (at least Android is free, so manufacturers can save on licensing fees).</li>
<li>What your friends say is even harder to change.</li>
</ul>
<p>Word-of-mouth is often mentioned as the most important form of advertising, and also the hardest to get right (should I be worried that nobody seems to know what Android is?). But personally, if there's anything that'll make me recommend one phone over another, it's the UI, probably because it's the thing which irritates me every time I use a phone -- it's also where Android has a chance to shine, since (in theory) you can rewrite everything, even if you end up with a bloated home screen app that does everything under the sun.</p>
<p>Without going into technical details (there's <a href="http://www.youtube.com/AndroidDevelopers">plenty on YouTube</a>), Android has a few distinguishing features; the most notable is that it tries to be designed around the user experience. <a href="http://code.google.com/android/reference/android/content/Intent.html">Intents</a> are a key idea -- they encapsulate what the user <em>wants</em> to do. The system passes the request to whichever app says it can fulfil the intent -- apps aren't replaced, intents are.</p>
<p>The problem arises when you have many apps which want to provide some basic enhancement. If I have Skype on my Android phone, here's what I'd like to see:</p>
<ul>
<li>Skype status shows up in the contact list.</li>
<li>Calling a Skype-online contact makes a Skype call over Wi-Fi.</li>
<li>Calling a Skype-online contact asks me before using HSDPA, in case I don't want to end up with a big phone bill and violate the no-VoIP clause of the ToS.</li>
</ul>
<p>It's conceptually easy, with one catch: you don't want to <em>replace</em> the contacts and dialler, because apps can't access another app's data. It's difficult to do the right thing when you change contacts apps between adding, removing, editing, and renaming contacts, and it doesn't work with Google-talk or <strike><a href="http://sourceforge.net/projects/gaim/">Gaim</a></strike> <a href="http://www.pidgin.im/about/">Pidgin</a>, let alone a psychic dialler which reads my brainwaves and calls the right person without me asking.</p>
<p>What users expect is the concept of a global address book, with each contact (an ID) optionally linked to names, phone numbers, e-mail addresses, snail-mail addresses, <a href="http://en.wikipedia.org/wiki/WGS84">WGS84</a> coordinates, IM usernames, and anything you might associate with a person (or group of people).<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup> Similarly, apps which handle intents ("send a text message") can also advertise a "quality factor" (RFC 2616) that changes dynamically, so that Jabber's "quality" drastically decreases when I move out of range of Starbucks and my phone starts using roaming HSDPA, hopefully going below a threshold which prompts the phone to ask me first. Suddenly, my phone has a better IM client than my computer: it'll switch to SMS when Wi-Fi isn't available, connect to the office's SIP server, and give me the directions to tonight's party.</p>
<p>There's been little work on any platform to address these; as a result, there always tends to be one app that ends up on top with everything else struggling to catch up -- IM clients throughout the decades have followed this trend. Phones are a different matter, and unless something reaches critical mass, we'll just end up with a lot of fragmentation. While compatibility issues aren't an aspect of fragmentation that the audience seemed to be worrying about (people seemed more worried about OEMs taking Android and making it proprietary), they're important to the user experience: seamlessly integrating apps together is an important part of a good UI.</p>
<p>Another thing which doesn't exist in Android is input methods -- the current emulator only supports primitive touch screen and a keyboard. How will multitap work? What about graffiti, handwriting recognition, or an on-screen keyboard? These tend to require extra screen space, which means it has to be coded into each app or provided by the system; there's currently no easy way to give another app a certain portion of your screen. Besides, the keyboard should only appear when I've selected a text field (and it should let me write/graffiti directly into a text field). What about gestures? Or force-input?<sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup></p>
<p>When you control nearly everything about a device, it's much easier to make a good UI instead of just a decent one -- the iPhone kept coming up as an example of a good UI throughout the day.<sup id="fnref:4"><a class="footnote-ref" href="#fn:4">4</a></sup> Android will stand between everybody's apps and the underlying hardware, whether there are 30 keys or none, and it needs to provide enough abstraction so that developers can rely on the system to do the Right Thing and still give a good user experience. It's what Android is designed around.</p>
<p>At last, the day came to an end, and I left for King's Cross. Though the room was teeming with skepticism, Android is something that I'd like to succeed, and I'll be watching the OHA's <a href="http://www.openhandsetalliance.com/oha_members.html">four handset manufacturers</a> closely over the next few months (maybe Motorola will finally make a phone that looks pretty but doesn't irritate me every time I try to use it).</p>
<p>There's going to be another Android Code Day in Boston on the 23rd. It's worth going to, especially if the <a href="http://android-developers.blogspot.com/2008/01/deadline-extension-for-android.html">impending SDK update</a>, happens first. Besides, you can always enjoy the Ben & Jerry's on the mission to uncover Google's master plan.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>Working with mobile software imparts a strong urge to know what everyone else is doing, possibly to learn from those who have done it better than you, or in search of the next great platform which is reasonably profitable and both a joy to use and code for (my recent experiences with a certain OS/IDE/build system can be summarised as <em>it should not be this difficult!</em>). <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>One can only hope that it won't be implemented using XML. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>Want to turn the alarm off? Hit it. This is probably patented. The <a href="http://www.sonyericsson.com/cws/products/mobilephones/overview/w380a?cc=us&lc=en">W380a</a> lets you turn off the alarm by waving at the camera. <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p>The iPhone and the BlackBerry the two phones which I hear described as good, not just better-than-something-else. <a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
</ol>
</div>Why should I use static_cast?2008-01-30T19:25:00+00:002008-01-30T19:25:00+00:00Ben Blaukopftag:airsource.co.uk,2008-01-30:/blog/2008/01/30/why-should-i-use-static_cast/<p>I recently had the dubious pleasure of debugging a User 42 Panic on a piece of Symbian code that was given to me by another company. You always need to make sure you understand what the system is telling you, so I went straight to the documentation:</p>
<blockquote>
<p>User 42: This panic is raised by a number of RHeap member functions, AllocLen(), Free(), FreeZ(), ReAlloc(), ReAllocL(), Adjust() and AdjustL() when a pointer passed to these functions does not point to a valid cell. </p>
</blockquote>
<p>I recently had the dubious pleasure of debugging a User 42 Panic on a piece of Symbian code that was given to me by another company. You always need to make sure you understand what the system is telling you, so I went straight to the documentation:</p>
<blockquote>
<p>User 42: This panic is raised by a number of RHeap member functions, AllocLen(), Free(), FreeZ(), ReAlloc(), ReAllocL(), Adjust() and AdjustL() when a pointer passed to these functions does not point to a valid cell. </p>
</blockquote>
<p>Hmmm. That didn't really give me many clues. Well, I needed to figure out how to reproduce the bug anyway. That was relatively easy, I could reproduce it with a User::Leave(). The leave was being trapped, and the debugger showed the panic was being issued between the Leave and the TRAP, so that pointed to a problem on the CleanupStack (which agreed with what the documentation said). Isolating the problem even further showed that I could reproduce the problem by doing this:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nt">CItemBase</span><span class="o">*</span><span class="w"> </span><span class="nt">item</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">(</span><span class="nt">CItemBase</span><span class="o">*)</span><span class="nt">decoder-</span><span class="o">></span><span class="nt">ReadItemLC</span><span class="o">();</span><span class="w"> </span>
<span class="w"> </span><span class="nt">CleanupStack</span><span class="p">::</span><span class="nd">PopAndDestroy</span><span class="o">(</span><span class="nt">item</span><span class="o">);</span>
</code></pre></div>
<p>For reference, ReadItemLC() constructed an MDecodable, and the inheritance hierarchy looked like:</p>
<p><a href="https://airsource.co.uk/blog/images/2008/01/drawing1.png"><img alt="MultipleInheritance" src="https://airsource.co.uk/blog/images/2008/01/drawing1.png"></a></p>
<p>Ignoring the fact that a function called <code>ReadItemLC()</code> should return a <code>CItemBase*</code>, not an <code>MDecodable*</code>, the problem here is that in that inheritance hierarchy, both <code>CItemBase</code> and <code>MDecodable</code> have their own data (<code>MDecodable</code> has a vtable pointer, even though it's an abstract class). Consequently when you cast a pointer from an <code>MDecodable*</code> to a <code>CItemBase*</code> you get a different pointer value. Try <a href="https://airsource.co.uk/blog/images/2008/01/examplecode.cpp">the attached code</a> on any system for a demonstration of the problem. </p>
<p>However, in the code sample above, the <code>CItemBase</code> class had not been fully defined; we had simply forwarded-declared <code>CItemBase</code>. That meant the compiler couldn't do the necessary arithmetic (subtract 4) to convert an <code>MDecodable*</code> to a <code>CItemBase*</code> - because it didn't know whether they were actually part of the same inheritance tree, or completely dissociated types. So it did the equivalent of a <code>reinterpret_cast</code>. </p>
<p>When I changed the code to do the correct C++ style cast:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nt">CItemBase</span><span class="o">*</span><span class="w"> </span><span class="nt">item</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">static_cast</span><span class="o"><</span><span class="nt">CItemBase</span><span class="o">*>(</span><span class="nt">decoder-</span><span class="o">></span><span class="nt">ReadItemLC</span><span class="o">());</span><span class="w"> </span>
<span class="w"> </span><span class="nt">CleanupStack</span><span class="p">::</span><span class="nd">PopAndDestroy</span><span class="o">(</span><span class="nt">item</span><span class="o">);</span>
</code></pre></div>
<p>It promptly failed to compile, because <code>CItemBase</code> hadn't been fully defined. Adding</p>
<div class="highlight"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf">"ItemBase.h"</span>
</code></pre></div>
<p>at the top of the file fixed the compile - and fixed my User 42 Panic as well.</p>
<p>Lesson: next time I get code from another source, check the casting is done properly.</p>On starting a software company2007-12-06T12:49:00+00:002007-12-06T12:49:00+00:00Ben Blaukopftag:airsource.co.uk,2007-12-06:/blog/2007/12/06/on-starting-a-software-company/<p>This blog has been going for nearly a year now - our first post was on January 29th. Airsource has been around for a little longer than that - I quit my job at QUALCOMM to work full-time at Airsource in June 2006. We're approaching the end of the year, so what better time for a bit of a review. I'm going to stick to the technical side of things - I'm sure Nick will have something to say, and maybe one of our new employees will want to give their view of things too. <p>This blog has been going for nearly a year now - our first post was on January 29th. Airsource has been around for a little longer than that - I quit my job at QUALCOMM to work full-time at Airsource in June 2006. We're approaching the end of the year, so what better time for a bit of a review. I'm going to stick to the technical side of things - I'm sure Nick will have something to say, and maybe one of our new employees will want to give their view of things too. </p>
<p>There are several key things I've learned in my time at Airsource. The most important one is that when it's your own company, it's not enough to be good, or even just better than the next guy. You need to be great. Every time I write code, at the back of my head is the feeling that some day this code may be run by a customer. And it had better work, because if it doesn't, one way or another it will be my problem. No matter how principled you are, that ethos simply doesn't apply when you're working for a large company. You write the code, you test it, the QA department passes it, and you walk away. You don't even have to do any sales!</p>
<p>The corollary of this is that it's not just enough to write good software. It has to be the right software. By that, I mean that you need to make absolutely sure that you know what the customer is asking for - and that what the customer is asking for is really what they want. And then, you need to deliver that, deliver it well, and ideally deliver just a little bit more. You don't want to give the farm away, when consultancy is what keeps a roof over your head, but you do want to give the customer a warm fuzzy feeling.</p>
<p>If you are writing a product, you don't develop the thing in a clean room and then unleash it on an unsuspecting public. Or if you do, it will probably flop. You do some market research first. You do usability testing. You go out there and find out what people want. In the same way, when working on a client project, it's your responsibility to make sure you are doing what the customer wants. And remember, even if what the customer is asking for sounds stupid, there's a reason behind it. At least when you're a small software company, your customers are almost guaranteed to be making a lot more money than you are. Which means they're doing something right.</p>
<p>When Airsource finishes a customer project, we send someone along who didn't write any of the code, and had as little involvement as possible with the project. They sit down with the customer, and do a post mortem. The customer gets the chance to voice any and all complaints that they have. They are surprisingly frank. I've had some feedback about me that people would never give to my face. And then, when we've got the feedback, we sit down together at the office, and figure how to make the next project an even better experience for the customer. </p>Cambridge Fun Run2007-11-16T14:03:00+00:002007-11-16T14:03:00+00:00Ben Blaukopftag:airsource.co.uk,2007-11-16:/blog/2007/11/16/cambridge-fun-run/<p>The Airsource team have just been out for their first team-building exercise. One of the benefits of running your own company is that you get to choose what the activities are - and since my sport is running, and Airsource is a four man company, I decided we'd do the <a href="http://www.camfunrun.org">Cambridge …</a></p><p>The Airsource team have just been out for their first team-building exercise. One of the benefits of running your own company is that you get to choose what the activities are - and since my sport is running, and Airsource is a four man company, I decided we'd do the <a href="http://www.camfunrun.org">Cambridge Fun Run</a> - a four by 1.1 mile relay. I did check that everyone was up for a bit of running first though! We've just got back from the race - via the pub, so that's an average of 7 minutes running each followed by an hour eating and drinking. Seems a good ratio. Not sure how we placed yet, but those who know me will know I'm not exactly uncompetitive, so I'll be checking the results with interest!</p>"Who's your boss?"2007-11-14T13:17:00+00:002007-11-14T13:17:00+00:00Nick Clareytag:airsource.co.uk,2007-11-14:/blog/2007/11/14/whos-your-boss/<p>Approaching recruitment for the first time is always a bit of a tricky one. You dread the thought of getting someone onboard only to throw them overboard again after a few weeks because you totally misjudged their capacity to work, think or be part of the team. You advertise your position and after a few weeks of interviews you start to wonder if you aren't just doing it all wrong. It's in these moments of sheer desperation that that the Recruitment Agency beckons.<p>Approaching recruitment for the first time is always a bit of a tricky one. You dread the thought of getting someone onboard only to throw them overboard again after a few weeks because you totally misjudged their capacity to work, think or be part of the team. You advertise your position and after a few weeks of interviews you start to wonder if you aren't just doing it all wrong. It's in these moments of sheer desperation that that the Recruitment Agency beckons.</p>
<p>Well, actually, the Recruitment Agency isn't just beckoning, it's phoning you up every other day from Glasgow, West London and various other locations, telling you all about the FANTASTIC engineer that they've got just sitting on their books BEGGING to be hired by you if only you'll sign their paperwork and agree to let them inundate you with CVs. They only charge an eye-watering percentage of the recruit's first year salary.</p>
<p>Now, before I go all nuts here - there are <a href="http://www.ecmselection.co.uk/">exceptions</a> - companies who build their reputation on their relationship with you and the candidate, who are used over and over again over decades by technical people and who do a great job. But frankly these are rare and when you find them, you hang on to them for dear life. Yes, they cost good money. But they also send you great people and they do so politely and respectfully. They also don't cold call.</p>
<p>So I want to offer you some helpful advice on dealing with those who can't read or understand English.</p>
<p>Cold emailing is pretty simple. Every mail server has an option you can set which will cause emails from a specific domain to be dropped on the floor and ignored forever. When Airsource is emailed by a recruitment company, they get a polite email explaining why they are being blacklisted and then - into the blacklist they go. I know this isn't very nice, but frankly, emailing me when I've asked not to be emailed by you isn't very nice either, so my conscience is clear.</p>
<p>This goes for those who claim not to be recruitment agencies;</p>
<blockquote>
<p>When you are next looking to recruit, wouldn't you like to:
- Reduce you(sic) cost per hire by thousands
- Reduce the time it takes you to recruit
- Increase the exposure of your vacancy and have it potential(sic) viewed by millions of active job seekers, 24 hours a day, 7 days a week, 356(sic) days per year.
FooDeBlah is not a recruitment agency - in fact we're very different. The biggest difference is that we do not charge an end placement fee. </p>
</blockquote>
<p>Yes, 356 days a year. After all, even agencies need to have some time off. But different? Different you say? You, sir, look like a duck. You talk like a duck. You walk like a duck. You email as if you were a duck. You are on my blacklist.</p>
<p>Of course, you then get the agencies who think that getting on the phone to you is somehow special - it's actually ok to call me up and waste even more of my time instead of emailing me. That somehow I won't be nearly as annoyed.</p>
<p>Two particularly cute stories - I had a nice chap from Glasgow who just "happened to ring me up" and "hadn't seen any ads online" so couldn't possibly have noticed anything whatsoever about my request to not be contacted by an agency. When challenged that in fact this was a load of old rubbish, the poor bloke got all teary on me - "if we never called up people who asked not to be contacted, we'd never get any business." Deary me.</p>
<p>The second one was a more aggressive gentleman who rung me up and insisted on trying to pry as many possible details as he could from me. I resisted, explained that frankly we weren't interested in his services and that he really shouldn't ever call us again. He got a little shirty at this point and said "Who's your boss?" My reply was "I'm the CEO of Airsource."</p>MOTODEV Summit, London2007-11-14T12:02:00+00:002007-11-14T12:02:00+00:00Nick Clareytag:airsource.co.uk,2007-11-14:/blog/2007/11/14/motodev-summit-london/<p>Last Friday I had the opportunity to head down to London to see the Motorola <a href="http://developer.motorola.com/eventstraining/summit/">MOTODEV Summit</a> world tour come to town. MOTODEV is Motorola's attempt to woo the developers of the world to their platforms and devices - attempt to capture some mind-share and give us some insight into what is around the corner for their technology.<p>Last Friday I had the opportunity to head down to London to see the Motorola <a href="http://developer.motorola.com/eventstraining/summit/">MOTODEV Summit</a> world tour come to town. MOTODEV is Motorola's attempt to woo the developers of the world to their platforms and devices - attempt to capture some mind-share and give us some insight into what is around the corner for their technology.</p>
<p><a href="https://airsource.co.uk/blog/images/2007/11/dscn0047.png" title="The main lobby of \"The Brewery\"."><img alt="Lobby" src="https://airsource.co.uk/blog/images/2007/11/dscn0047.thumbnail.png"></a></p>
<p>It was a pretty small gathering, at <a href="http://www.thebrewery.co.uk/">"The Brewery"</a> near Liverpool Street Station in the heart of "The City", an area of London frequented by blokes in suits carrying Blackberries and shouting "Sell! Sell!" in increasingly fervent tones. Members of Motorola's "ecosystem" were there, and about 24 low-key stands were put up so that various parties could hawk their wares.</p>
<p><img alt="Main Hall" src="https://airsource.co.uk/blog/images/2007/11/dscn0046.thumbnail.png" title="The main hall, first thing. The exhibition, "breakfast" and lunch were all served in here."></p>
<p>About half of the people there seemed to be Motorolans (yes, that's what they call themselves. I know this 'cause I used to be one...), and there were quite a few suits and ties. I recognised a few faces from the <a href="http://mobilemonday.org.uk/">MoMo London</a> events, and a few folks from overseas had even come in for the occasion - I spotted some Israelis, a few of the nice folk from <a href="http://www.funambol.com/">Funambol</a> in Italy and I spoke to someone who had come in from Barcelona. It's fair to say things even felt a little European.</p>
<p>Obviously, there's a lot of buzz about Motorola. For all of the horrendous history they have on the user interface front, <a href="http://www.motorola.com/mediacenter/bios.jsp?globalObjectId=354">Captain Zander</a> and the gang have been doing some hard work whipping their phone platform into shape for the 21st Century. They have a great bunch of engineers and their "feature phone" devices are some of the most popular in the world - the bestselling device of 2006 I believe was the RAZR - but these "big hits" are few and far between, and Motorola just hasn't managed to build the sheer volume of models that companies like Nokia have. Motorola has become pretty strong on style, but their software has unfortunately been weak - it's fine in the guts of the thing but the bits the user sees have been very poorly thought out. They were a bit late to the UI party. So I was certainly interested to hear what they had planned.</p>
<p>Despite the advertised breakfast, a woefully small stack of pastries was on offer (note to event organisers - please - a proper feed in future?) but free WiFi and a lounge-like atmosphere made for a nice escape from the biting cold outside. The keynote kicked off at about 9:30 and Christy Wyatt greeted us with Motorola's perspective on where the big challenges were in the mobile industry and how Motorola was going to address them.</p>
<p><a href="https://airsource.co.uk/blog/images/2007/11/dscn0048.png"><img alt="Keynote" src="https://airsource.co.uk/blog/images/2007/11/dscn0048.thumbnail.png"></a></p>
<p>The main theatre where the keynote was given.</p>
<p>(Christy was actually pretty refreshing. I'm becoming slightly tired of the woeful presentation skills shown at these events and she was great. No notes, spoke well to the audience and didn't stumble or start, despite some technical outages. Bravo.)</p>
<p><img alt="Christy Wyatt" src="https://airsource.co.uk/blog/images/2007/11/dscn0052.thumbnail.png" title="Christy Wyatt, VP for ecosystem and market development @ Motorola, delivers the keynote."></p>
<p>Here's where it got interesting. The single biggest issue facing the mobile market from a developer's perspective is - fragmentation. But instead of addressing the issue by driving their platform strategies to minimise this (like Nokia does), Motorola are determined to go bananas and back pretty much <strong>every</strong> platform under the sun. So Motorola has;</p>
<ul>
<li>AJAR (The "low-end" platform MIDP 2.0, migrating to MIDP 3.0 in the coming year or so)</li>
<li>MOTOMAGX (The "feature" platform - Linux, MIDP and WebUI)</li>
<li>UIQ ("High-end multimedia" platform)</li>
<li>Windows Mobile ("Enterprise Mobility")</li>
</ul>
<p>So Motorola is exhibiting the broader malaise of the mobile industry. They are fragmenting their platform strategy. Frankly, this isn't a help to developers, but a hindrance. They offer a confusing array of development choices and no way of addressing their handset base with a single effort. Perhaps it's unfair to think that this is even possible, but it looks to me like they are backing every horse - a bet which is going to be expensive and inefficient.</p>
<p>The question on everyone's minds, of course, was - what about the <a href="http://www.openhandsetalliance.com/">Open Handset Alliance</a>? Motorola are part of the gang there, so what are they doing about it? Can they tell us anything about the platform and when are they going to launch something with it? Predictably, the nice people at Motorola looked a bit like rabbits caught in headlights - because although they were part of the OHA, they had no idea of the shape of Android was actually going to take. So the OHA doesn't look like a club of collaborators so much as a group of companies interested in the rabbit that Google is going to pull out from their hat.</p>
<p>So the sparsely populated hall emptied out - Motorola had put on a wide spread of talks in 5 different tracks that people could attend. The one that had really caught my eye was the presentation on the MOTOMAGX platform. I knew from the past that Motorola had been trying to do something with Linux but had thus far been unsuccessful in their attempts to commercialise it. How far had they come? Had they done it yet?</p>
<p><img alt="MOTOMAGX presenters" src="https://airsource.co.uk/blog/images/2007/11/dscn0054.thumbnail.png" title="The MOTOMAGX presenters prepare to start."></p>
<p>I was pleased to discover that they had. <a href="http://www.motorola.com/content.jsp?globalObjectId=8411">MOTOMAGX</a> really is Linux and it really is running on their new feature phone devices - from the RAZR 2 onwards. There is the plan to offer a Linux SDK from H2 2008 onwards, and QT 2.3 is currently being used for the UI. So there really is an "open source phone", although of course you can't rebuild the kernel for this particular device...</p>
<p><img alt="MOTOMAGX Block Diagram" src="https://airsource.co.uk/blog/images/2007/11/dscn0055.thumbnail.png" title="A block diagram of the MOTOMAGX solution. 3 different development options on the one device. Ouch."></p>
<p>The most surprising thing for me was Motorola's efforts to offer the "widget" model of application development to the community. The newer devices sport <a href="http://webkit.org/">WebKit</a> and Motorola are adding a broad JavaScript extension model in order to allow proper off-line applications. I'm pretty skeptical about how successful this will be for mobile applications (Widgets aren't really useful for desktop applications; why does anyone want to use them for mobile applications?) but it is interesting to see how broadly WebKit is now being used in the mobile market. Nokia, Motorola, Apple and now Google are all behind it and shipping it as their on-device browser of choice.</p>
<p>Oh, and all of these SDKs will be in a single "unified" development environment (<a href="http://www.eclipse.org/">Eclipse</a>, of course). They should all be available in H2 2008, and we are certainly looking forward to the possibilities - particularly in the native application space.</p>
<p>So - even more fragmentation. There are 3 different application frameworks to choose from - on the one device platform!</p>
<p><img alt="MOTOMAGX Mob" src="https://airsource.co.uk/blog/images/2007/11/dscn0056.thumbnail.png" title="The presenters are mobbed. The offer of early access to the MOTOMAGX SDK proves too much for the huddled masses."></p>
<p>The MOTODEV road show heads off to Beijing next in order to spread the news. But a fragmented mobile world we live in today looks set to get even more so in the coming years. It's going to get a lot worse before it starts to get better.</p>Using ILogger2007-11-02T11:28:00+00:002007-11-02T11:28:00+00:00Teanlorg Chantag:airsource.co.uk,2007-11-02:/blog/2007/11/02/using-ilogger/<p>It's strange how many BREW specialists you can talk to who have never used <code>ILogger</code>. It's not hard to see why, though: the API reference only sometimes tells you the details of how to use it, and while the <code>ILogger</code> overview notionally tells you what it <em>does</em>, you have to read it very carefully to figure out how to use it.</p>
<p>And when you find out what it does, it doesn't seem like the most appropriate thing to use.<p>It's strange how many BREW specialists you can talk to who have never used <code>ILogger</code>. It's not hard to see why, though: the API reference only sometimes tells you the details of how to use it, and while the <code>ILogger</code> overview notionally tells you what it <em>does</em>, you have to read it very carefully to figure out how to use it.</p>
<p>And when you find out what it does, it doesn't seem like the most appropriate thing to use.</p>
<p>Because you don't want to be bored with the details (otherwise you'd be reading the API reference, hoping to find out what you're missing), I'll give you a code example:</p>
<div class="highlight"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf">"AEELogger.h"</span>
<span class="kt">void</span><span class="w"> </span><span class="nf">log_something</span><span class="p">(</span><span class="n">AEEApplet</span><span class="o">*</span><span class="w"> </span><span class="n">pMe</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">bucket</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w"> </span><span class="n">ILogger</span><span class="w"> </span><span class="o">*</span><span class="n">pILogger</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Error checking has been omitted in this example for clarity</span>
<span class="w"> </span><span class="n">ISHELL_CreateInstance</span><span class="p">(</span><span class="n">pMe</span><span class="o">-></span><span class="n">m_pIShell</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="n">AEECLSID_LOGGER_WIN</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&</span><span class="n">pILogger</span><span class="p">);</span>
<span class="w"> </span><span class="n">ILOGGER_SetParam</span><span class="p">(</span><span class="n">pILogger</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="n">AEE_LOG_PARAM_FILTER_ONE</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="n">bucket</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">TRUE</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Do the actual logging</span>
<span class="w"> </span><span class="n">LOG_TEXT</span><span class="p">(</span><span class="n">pILogger</span><span class="p">,</span><span class="w"> </span><span class="n">bucket</span><span class="p">,</span><span class="w"> </span><span class="s">"Hello world"</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Cleanup</span>
<span class="w"> </span><span class="n">ILOGGER_Release</span><span class="p">(</span><span class="w"> </span><span class="n">pILogger</span><span class="w"> </span><span class="p">);</span>
<span class="w"> </span><span class="n">pILogger</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>As mentioned in the comments, error handling has been omitted: if <code>ISHELL_CreateInstance()</code> fails, your app will crash (<code>ISHELL_CreateInstance()</code>, <code>ILOGGER_SetParam()</code>, and <code>LOG_TEXT()</code> return error codes; <code>ILOGGER_Release()</code> returns the ramining reference count). In English, what it does is pretty simple:</p>
<ol>
<li>
<p>Create an ILogger instance, which logs to the BREW output window.</p>
</li>
<li>
<p>Enable log bucket 0.</p>
</li>
<li>
<p>Log a test message.</p>
</li>
<li>
<p>Release the logger, and set the pointer to NULL (generally good practice).</p>
</li>
</ol>
<p>The log output appears a bit cryptic at first: the log message is prefixed with the log bucket, the record type, your app's class ID, and the ILogger's instance ID.</p>
<p>There are three classes implementing ILogger mentioned in the API references: </p>
<ul>
<li>
<p><code>AEECLSID_LOGGER_FILE</code> logs to a file</p>
</li>
<li>
<p><code>AEECLSID_LOGGER_SERIAL</code> logs to the serial port, and</p>
</li>
<li>
<p><code>AEECLSID_LOGGER_WIN</code> logs to the BREW Simulator output window.</p>
</li>
</ul>
<p>Already, you've probably realised something: It doesn't log to the serial port or BREW output window as appropriate, unlike <code>DBGPRINTF()</code>. Of course, you can probably do something with an #ifdef. [1]</p>
<p>You set parameters with a call to <code>ILOGGER_SetParam()</code> (parameters are defined in AEELogParamType). While <code>param</code> is usually a <code>uint32</code>, <code>pParam</code> is sometimes a <code>uint32</code>pretending to be a <code>void*</code>, and the documentation isn't that clear on this.</p>
<p>There are 256 <em>log buckets</em>, which can be filtered by the app (call <code>ILOGGER_SetParam</code> and set <code>AEE_LOG_PARAM_FILTER_ONE</code> with the bucket in <code>param</code> and <code>(void*)TRUE</code> or <code>(void*)FALSE</code> in pParam), and by the log parser, so it's good for tagging different sections of code so you can switch logging for, say, your network code on at runtime. There are also 256 instance IDs, and the default is 0; you set it by setting <code>AEE_LOG_PARAM_INSTANCE_ID</code>. If you do it at runtime with some sort of thread ID, you can distinguish between threads.</p>
<p>There are 65536 record types, defined in AEELogItemType: </p>
<ul>
<li>
<p>0 is text,</p>
</li>
<li>
<p>1 is a "binary message",</p>
</li>
<li>
<p>2 is a "binary block" (a BLOB), and</p>
</li>
<li>
<p>anything 0x100 or over is in a "user-defined format".</p>
</li>
</ul>
<p>A <em>binary message</em> is typically produced from <code>ILOGGER_PutMsg()</code>. The format is defined in AEELogBinMsgType, but to summarise, they're fixed at 112 bytes (supposedly this makes logging faster), and store four <code>uint32</code>s and two strings totalling 80 bytes (including null terminators); the first <code>uint32</code> is meant to be <code>__LINE__</code> and the first string is meant to be <code>__FILE__</code> (and standard log parsers probably assume this). The current implementation (3.1.5 simulator) also outputs some contents of your stack if you don't fill 80 bytes with the two strings, <strong>this is a security risk</strong> (it ought to zero them anyway to make log entries cleaner).</p>
<p>A <em>binary block</em> is just a byte array, and you'll have to find a way of distinguishing between them (like user-defined formats, or a prefix string, or XML).</p>
<p>If you're logging to a file, you'll have to call <code>ILOGGER_SetParam()</code> with <code>AEE_LOG_PARAM_FILE_NEW</code> and a file name (drive letters don't work; for testing, just use something like "LogFile"). You need to <code>#include "AEEFile.h"</code> for the file modes.</p>
<p>You'll notice that logs are actually in a binary format. It's defined in the API (AEELogRecord, AEELogRcdHdrType), but I haven't found something that will parse the log files, let alone let you filter for specific buckets and class IDs, let you have pluggable pretty printing for your user-defined log format, or save a BLOB to a file. <code>ILogger</code> could prove to be a useful general logging tool (all apps spam the serial port, and the receiver filters), but for most purposes, it's a bit overkill.</p>
<p>[1] The BREW Tools Suite's BREW Logger is meant to be able to connect to the BREW Simulator, but I haven't managed this. Anyone?</p>Persistent Storage Performance on BlackBerry Curve 83002007-10-30T14:57:00+00:002007-10-30T14:57:00+00:00John Earltag:airsource.co.uk,2007-10-30:/blog/2007/10/30/persistent-storage-on-blackberry-curve-8300/<p>There are three persistent storage methods available directly for Java CLDC applications on BlackBerry devices:</p>
<ol>
<li>The MIDP record store</li>
<li>RIM's persistent object API</li>
<li>The file system, which is newly available in version 4.2 of the OS, and permits storage of multimedia files.</li>
</ol>
<p>There are three persistent storage methods available directly for Java CLDC applications on BlackBerry devices:</p>
<ol>
<li>The MIDP record store</li>
<li>RIM's persistent object API</li>
<li>The file system, which is newly available in version 4.2 of the OS, and permits storage of multimedia files.</li>
</ol>
<p>The record store and persistent object API are typically used by applications for storing configuration and other data so that it is hidden from other applications. We thought it would be interesting to compare the MIDP record store and the persistent object API, since they probably share at least some of the same underlying implementation, but code using the record store should be easier to port to other devices.</p>
<p>We tested reading and writing a 10-byte buffer to/from persistent storage, using each of the MIDP record store interface (javax.microedition.rms) and the RIM persistent object interface (net.rim.device.api), on a BlackBerry Curve 8300 running BlackBerry OS v4.2.2.114 (platform 2.4.0.53):</p>
<table>
<thead>
<tr>
<th></th>
<th>Writes</th>
<th>Reads</th>
</tr>
</thead>
<tbody>
<tr>
<td>Persistent Object Store</td>
<td>18.5 ± 0.4 ms/write</td>
<td>13.6 ± 0.2 us /read</td>
</tr>
<tr>
<td>MIDP Record Store</td>
<td>16.7 ± 0.3 ms/write</td>
<td>57.7 ± 4.6 us/read</td>
</tr>
</tbody>
</table>
<p>These were estimated using 10 runs of 1000 writes and 10 runs of 10000 reads; the ± figures denote standard deviation. Note how read speed is an order of magnitude faster than write.</p>
<p>The persistent object store read takes about a quarter of the time of the MIDP record store read, presumably because no copy is required (the persistent object store just returns a reference to an already instantiated object) and there is no indexing overhead, just a hash table lookup to find the reference.</p>
<p>So, use the Persistent Object Store unless you really value the additional portability or the 2 ms time difference on writes.</p>In pursuit of space2007-10-23T11:33:00+01:002007-10-23T11:33:00+01:00Nick Clareytag:airsource.co.uk,2007-10-23:/blog/2007/10/23/in-pursuit-of-space/<p>After a few months of intensive effort from myself and Ben (not to mention the efforts of many potential candidates), we managed to find two engineers who fit the bill. Taking on two people at once is a bit risky when you're as little as we are, but when you find great people you just need to grab them. There's more equipment to buy and there are more salaries to pay, but once you've got the people on board you only have to go through things once with both of them, so overall it seems to save time. We're feeling pretty chuffed.<p>After a few months of intensive effort from myself and Ben (not to mention the efforts of many potential candidates), we managed to find two engineers who fit the bill. Taking on two people at once is a bit risky when you're as little as we are, but when you find great people you just need to grab them. There's more equipment to buy and there are more salaries to pay, but once you've got the people on board you only have to go through things once with both of them, so overall it seems to save time. We're feeling pretty chuffed.</p>
<p>But the obvious side-effect of taking on more people is - where in the heck do you fit them? Ben and I started off in his front bedroom, which was tough going but kind of fun. I'm one of these blokes who likes a fair bit of waggle room, though, so as soon as we possibly could (14 months in!) we moved across the road to a lovely little pseudo-serviced office outfit with wonky floors and bundles of character. And two months later we're already trying to work out where we are all going to fit.</p>
<p>When push comes to shove, you need to make sacrifices. In many places I've worked, there was a pretty strict hierarchy when it came to allocating seating spaces. It went a little something like this:</p>
<ul>
<li>Senior executives (CEO/CTO etc.)</li>
<li>Managers (People with Director in their title)</li>
<li>Sales staff</li>
<li>Support staff (IT people, Accountants, Office staff)</li>
<li>Engineers, Testers, Content developers</li>
</ul>
<p>Now this is a bit topsy-turvy, and here's why. The only guys who actually MAKE money in the company are the guys at the bottom. Yes, that's right. Everyone else is basically an overhead on a software business. The executive team spend a lot of time meeting people and forming relationships, opening the doors for software to be sold. The managers motivate teams, resolve differences and bond with people. The sales guys spend a lot of time on the road, seeking opportunites in new markets. The support staff keep the ship running smoothly. But the folks at the bottom are the ones that actually build things that can be sold. So why don't you put them first when you're planning this out?</p>
<p>So if I'm going to be consistent on all this it means that I'm the first on the chopping block. My new digs are substantially smaller but frankly since I should be out building those all-important corporate relationships do I really need a huge desk? Nah. Besides, it acts as a huge motivator to get me out and tracking down our long-term office.</p>
<p><img alt="Chez moi!" src="https://airsource.co.uk/blog/images/2007/10/chez-moi.png" title="Chez moi!"></p>
<p>So we had a lot of rearranging of furniture to do. Squeezing in an additional two people proved to be kind of a challenge but with me moved back to a small desk where a bookshelf used to be it opens up the possibilities to fit everyone in. Ben and Teanlorg have one side:</p>
<p><img alt="Ben and Teanlorg at work" src="https://airsource.co.uk/blog/images/2007/10/tean-and-ben.png" title="The guys hard at work"></p>
<p>And John has the other:</p>
<p><img alt="John at work" src="https://airsource.co.uk/blog/images/2007/10/john.png" title="John, pounding on keyboard"></p>
<p>Ah, the heart-warming clickety-clack of fingers on keyboards. And yes, those are Aerons. If you're going to sit in a chair for as long as we do, you might as well do it in comfort. :-)</p>All jobs filled...2007-10-08T12:47:00+01:002007-10-08T12:47:00+01:00Ben Blaukopftag:airsource.co.uk,2007-10-08:/blog/2007/10/08/all-jobs-filled/<p>One pile of CVs later, after 53 days and a whole bunch of interviewing, we have finally filled our roles. The Airsource team is about to double! This means we're not currently recruiting. I would say that speculative applications from good candidates are always welcome, but until we get our …</p><p>One pile of CVs later, after 53 days and a whole bunch of interviewing, we have finally filled our roles. The Airsource team is about to double! This means we're not currently recruiting. I would say that speculative applications from good candidates are always welcome, but until we get our new office, that's just not true. Unless you are a bat or a sloth, and can code whilst hanging upside-down from the ceiling.</p>
<p>Looking for a new office is another task that's been occupying our time. We've found one, over four times bigger than our current pad, in the centre of town. We're just going through all the legals, which apparently could take quite a while. In the meantime, the next couple of months are going to be pretty cosy.</p>Airsource are recruiting2007-08-15T08:24:00+01:002007-08-15T08:24:00+01:00Ben Blaukopftag:airsource.co.uk,2007-08-15:/blog/2007/08/15/airsource-are-recruiting/<p>If you've been following our blog, you'll notice things have been a bit quiet recently. Business is good - and we need another software engineer. If you're good, and willing to work in Cambridge (that would be Cambridge UK, for our US readers), then we'd love to hear from you. Experience …</p><p>If you've been following our blog, you'll notice things have been a bit quiet recently. Business is good - and we need another software engineer. If you're good, and willing to work in Cambridge (that would be Cambridge UK, for our US readers), then we'd love to hear from you. Experience counts, but so does passion and talent. </p>
<p>Degrees? We've met PhDs who can't, and 15 year-olds who can. If you're looking for your first full-time software job, then you will learn more in a month with us than in a year at a big corporate. If you've been watching your company fail to learn from history for, well, what seems like long enough to <em>be</em> history, then come talk to us. If you want to be in early on a successful startup, whether in a junior or senior role, send your CV to <a href="mailto:jobs@airsource.co.uk">jobs@airsource.co.uk</a>. More details at <a href="http://www.airsource.co.uk/jobs">http://www.airsource.co.uk/jobs</a>.</p>
<p>Agency emails will go straight on our spam filter. If we want an agency, we'll call you.</p>DUMA Release2007-07-17T11:26:00+01:002007-07-17T11:26:00+01:00Ben Blaukopftag:airsource.co.uk,2007-07-17:/blog/2007/07/17/duma-release/<p>As promised, here's the release of DUMA for BREW, announced at BREW 2007. It's a library that helps debug memory problems, and Airsource have ported it over to to BREW. Download it <a href="https://airsource.co.uk/blog/images/2007/07/testduma.zip">here</a> - it includes a test program, and full documentation of how to use it.</p>DUMA Release Date2007-07-13T09:53:00+01:002007-07-13T09:53:00+01:00Ben Blaukopftag:airsource.co.uk,2007-07-13:/blog/2007/07/13/duma-release-date/<p>I know that some of you out there have been waiting for our release of DUMA for BREW, which we announced in our talk at BREW 2007. We're still finalising the documentation for this - but it will be out on Tuesday 17th July. Check back here then, and in the …</p><p>I know that some of you out there have been waiting for our release of DUMA for BREW, which we announced in our talk at BREW 2007. We're still finalising the documentation for this - but it will be out on Tuesday 17th July. Check back here then, and in the meantime, sorry for the delay!</p>Configuring the Visual C++ 6.0 stack fill2007-07-02T09:02:00+01:002007-07-02T09:02:00+01:00Ben Blaukopftag:airsource.co.uk,2007-07-02:/blog/2007/07/02/configuring-the-visual-c-60-stack-fill/<p>When you build a project in Visual C++ debug mode, it prefills the stack for you on entry to every function. Every stack variable that you might (or might not) use will be pre-initialised to 0xCCCCCCCC. <p>When you build a project in Visual C++ debug mode, it prefills the stack for you on entry to every function. Every stack variable that you might (or might not) use will be pre-initialised to 0xCCCCCCCC. </p>
<p>This is, of course, pretty useful. It makes it easier to spot uninitialised stack variables, because 0xCCCCCCCC is unlikely to be a valid value. There is one disadvantage though - that value happens to evaluate to TRUE. If you have an uninitalised boolean, that can hide one class of bugs. Unfortunately Visual C++ doesn't offer any ability to control the prefill value. Airsource have created a simple utility that modifies a DLL, changing the prefill value to 0 - you can easily edit the script to allow further customisation if you want; for example you might want to optionally fill with 0xDEADBEEF.</p>
<p>The script is written in Perl, so you'll need to install that - we recommend <a href="http://www.activeperl.com/">ActivePerl</a>. The script is also only tested in a Cygwin environment. </p>
<p>Download the script from <a href="https://airsource.co.uk/blog/images/2007/07/mod_dll.zip">here</a>. Unzip the archive, which contains a single file mod_dll.pl. Run this using 'perl mod_dll.pl <mydllfile>' which will change the prefill value to 0.</p>
<p>However, as an alternative to using this script, you might want to consider upgrading to Visual Studio 2005. If you read from an uninitialised variable, you will get a breakpoint. Now <em>that</em> makes it a <em>lot</em> easier to spot uninitialised stack variables!</p>BREW Conference 20072007-07-02T08:43:00+01:002007-07-02T08:43:00+01:00Ben Blaukopftag:airsource.co.uk,2007-07-02:/blog/2007/07/02/brew-conference-2007/<p><img alt="Setting up the Airsource stand" src="https://airsource.co.uk/blog/images/2007/07/brewconf.jpg" title="Setting up the Airsource stand"></p>
<p>The Airsource team are now back in the UK after BREW 2007. We've had a good post-conference debrief, which was particularly interesting for me, the CTO of Airsource, as it was my first BREW conference. I have a few thoughts I'd like to share.<p><img alt="Setting up the Airsource stand" src="https://airsource.co.uk/blog/images/2007/07/brewconf.jpg" title="Setting up the Airsource stand"></p>
<p>The Airsource team are now back in the UK after BREW 2007. We've had a good post-conference debrief, which was particularly interesting for me, the CTO of Airsource, as it was my first BREW conference. I have a few thoughts I'd like to share.</p>
<p>We found the conference invaluable for meeting new contacts, both leads and possible partners, and our stand definitely enabled us to meet leads who we wouldn't otherwise have spoken to. As a relative newcomer, we met a lot of people who I suspect we would not have encountered had we not had a stand - and if we can convert any of these into sales, then it will definitely have been worth it.</p>
<p>What I'm less certain about is the technical talk we gave. We're still eagerly awaiting the speaker evaluations, though the initial feedback we've had is good, and we're hopeful that we gave a good impression of Airsource. However, there are two doubts in my mind about the talk. </p>
<p>Firstly, I'm far from convinced that a single 50 minute talk is the best way to educate an audience of people on a technical level. Presumably our audience were primarily embedded software engineers, and my experience of that community is that they like to read, and are perhaps not so adept at listening. I read (and write) a lot of technical articles, and in retrospect I think that the time we spent constructing, practising, and delivery our talk, might have been better spent on writing a set of good articles to be digested at leisure by the technical community, to be followed up by an in-person Q&A session. I had some quality one-to-one discussions with QUALCOMM engineers while at the conference, with output which was far more valuable and interesting than any talk could possible be. </p>
<p>Secondly, and more seriously from a business perspective, the talk we gave took at least two man-weeks to prepare (I'll have to check our timesheets for precise figures when I get back to the office), and that's a considerable investment for a small company. We need to be certain, if we are to repeat this exercise next year, that it will reap benefits. I'm not convinced. It may improve our standing in the developer community. It may, just possibly, improve our Google ranking. It certainly helped my personal development in a number of ways. But will it lead to cash on the table? I'm dubious. If we can help the BREW community while growing our business, then we will certainly do so, but to spend two man-weeks a year on essentially pro-bono activity is simply not going to be sustainable for us.</p>
<p>One approach might be for us to give a more business-related, less technical talk next year. However, feedback has been that there isn't really much truly advanced programming matter in the technical tracks. The BREW community seems to want more developer information, not less. We certainly do... Part of the problem, perhaps, is that the compensation for speaking is a free conference ticket - which is not exactly compensation for the time and investment in creating a worthwhile talk.</p>
<p>I'm aware that a number of BREW developers who weren't previously aware of our blog will now be reading it, and I'd very much appreciate any feedback about what you felt about the talks this year. Do you feel that the talks need to be more technical? Did you speak, and if so, do you think it will help your business? Either email us, or post a comment here on the blog. We look forward to hearing from you!</p>RVCT 3.0 Released2007-06-20T14:58:00+01:002007-06-20T14:58:00+01:00Nick Clareytag:airsource.co.uk,2007-06-20:/blog/2007/06/20/rvct-30-released/<p>If you've been paying close attention to the grapevine, you will have noticed that today ARM has announced the release of an upgrade to the RVCT toolchain for BREW. For those who aren't aware, RVCT gives BREW developers access to a version of their ARM compiler, linker and associated tools …</p><p>If you've been paying close attention to the grapevine, you will have noticed that today ARM has announced the release of an upgrade to the RVCT toolchain for BREW. For those who aren't aware, RVCT gives BREW developers access to a version of their ARM compiler, linker and associated tools which can be used for creating applications optimised for running on device.</p>
<p>If you looked closely at the <a href="http://www.arm.com/news/17813.html">announcement</a> you would have seen that we got a mention. Airsource has had an early opportunity to test out RVCT 3.0 and evaluate the performance and features being offered. It's fair to say we were pretty impressed - it's a much-needed upgrade and ARM has definitely got the edge over GCC when it comes to performance and size.</p>
<p>We'll be offering more details on some of the results we discovered and some analysis of the new features in RVCT 3.0 in our talk at BREW 2007 on Friday in Randle A at 13:45. After our presentation we'll try to get some more details up here.</p>Airsource @ BREW 20072007-06-19T13:01:00+01:002007-06-19T13:01:00+01:00Nick Clareytag:airsource.co.uk,2007-06-19:/blog/2007/06/19/airsource-brew-2007/<p>Winging our way across the Atlantic, en route to San Diego, I realise that it's been a year since Airsource started trading. It's not been easy, giving up great jobs and great people in order to go it alone, but we've made it to a year and are managing to stretch the company marketing budget far enough to get to BREW 2007. <p>Winging our way across the Atlantic, en route to San Diego, I realise that it's been a year since Airsource started trading. It's not been easy, giving up great jobs and great people in order to go it alone, but we've made it to a year and are managing to stretch the company marketing budget far enough to get to BREW 2007. </p>
<p>It's my fourth BREW conference, and I look forward to it every year. For us, it presents a unique opportunity to find out what has been happening in all of the major BREW markets around the world - South Korea, Japan, South America and of course the USA. The technical talks have been improving every year and Ben in particular is looking forward to getting some insight into BREW 4, the latest incarnation of Qualcomm's mobile application platform.</p>
<p>QUALCOMM do their best to show everyone a great time by putting on fantastic evening entertainment and great talks. I'm particularly looking forward to what the keynote has to offer this year, to see if somehow Paul Jacobs is going to improve on his abseiling (rapelling for US readers) that bought him onstage a few years back! There will be loud music, cool lighting and, inevitably, a bunch of models or actors walking around with phones. It will be the marketing machine at its best, and everyone needs a bit of cheese every now and then!</p>
<p>One of our experiments this year is to discover whether or not a stand is really worth it for the little guy. We thought long and hard about the money we had to spend on a stand and decided to go for it, if for nothing other than to find out if the additional presence helps grow our business. Having seen 3GSM up close on the cheap, I'm definitely glad we didn't spend anything on a stand for that - far too big a stage for the new starter and frankly I think it's now well past its prime. It's time to start thinking about a replacement for 3GSM, but that's for another article.</p>
<p>Unlike 3GSM (or Mobile World Congress as it's now known), the BREW conference offers a stage that's just right for us. Being BREW specialists, we can rely on reaching exactly the people we need to and we won't find ourselves in endless conversations only to discover that the folks we're talking to make antennas and base stations and haven't the slightest interest in our skills. Of course, the final proof will be in the aftermath and the analysis which I'm determined we will do after every significant spend on our marketing - what has the money we've laid out actually bought us in terms of business? If the answer is (as I fear it will be for many organisations who spend big on events) "not much", then we'll turn a much more critical eye to this next year.</p>
<p>Another experiment we're running is in what we give to people when they come to our stand. I was so impressed when I visited the conference last year and spent some time talking with <a href="http://www.punchcut.com/">PunchCut</a> (a bunch of people who I have a lot of time for) and getting some nifty logo badges from them - they made the most of their limited marketing budget through creativity. They didn't bother with stupid pens, breath mints and USB sticks. They put some thought into it. It spoke volumes about their capacity to innovate and for a company focussed on creativity such as they are this was inspirational. So we've done our best to raise the bar this year. Come to our stand and check out what we've managed to do and you might just walk away with something cool. That is, if we haven't run out. Which we might. I never order enough...</p>
<p>So come and visit us if you're at BREW 2007 - we're on Stand 80. If you can't make it, we'll be posting updates every day from the show to give you our take on the latest and greatest developments in BREW. Oh, and there will be a few surprises from us in the next few days as we lift the curtain on one or two of our clients and some of the work we've done for them. There's cool stuff on the way!</p>BREW interfaces - implementing a new interface2007-06-05T15:30:00+01:002007-06-05T15:30:00+01:00Ben Blaukopftag:airsource.co.uk,2007-06-05:/blog/2007/06/05/brew-interfaces-implementing-a-new-interface/<p>BREW makes heavy use of an interface framework derived from COM. It's very useful to understand the internals of how it works, especially if you want to extend any of the built in classes. In this series of articles, I will discuss:</p>
<ul>
<li><a href="https://airsource.co.uk/blog/2007/05/01/brew-interfaces-how-they-work-and-how-to-use-them/">Part 1. The internals of a BREW interface …</a></li></ul><p>BREW makes heavy use of an interface framework derived from COM. It's very useful to understand the internals of how it works, especially if you want to extend any of the built in classes. In this series of articles, I will discuss:</p>
<ul>
<li><a href="https://airsource.co.uk/blog/2007/05/01/brew-interfaces-how-they-work-and-how-to-use-them/">Part 1. The internals of a BREW interface.</a></li>
<li>Part 2. How to implement a simple new interface.</li>
<li>Part 3 (not published yet). How to publish your new interface as an extension.</li>
</ul>
<h1>Part 2. How to implement a simple interface.</h1>
<p>As with any task, the simplest way to do something is to copy an existing solution and adapt it to your own needs. If you want to get up and running quickly, then a very good approach is to download the uiOneSDK, which includes Widgets, and checkout widgets/src/BitmapWidget.c, which includes a simple class exposing the IImage interface.</p>
<p>However, in this article, we'll go through the steps of implementing an interface all the way from scratch. We're going to implement a very simple interface, that extends IAStream, treats the stream data as ASCII, and capitalizes it. Obviously this is pretty useless as it stands, but it provides a good basis for more useful streams, such as encryption classes. You should not that we take some shortcuts which mean that the implementation as it stands is not suitable for using in an extension.</p>
<h2>First, decide on the interface</h2>
<p>We'll call it IUCStream, for Upper Class Stream. It looks exactly like an IAStream, with one additional interface - IUCSTREAM_EnableTranslation which enables or disables the upper type translation.</p>
<h2>Write the public header (AEEUCStream.h)</h2>
<p>The public header needs to declare the layout of the interface, so that other pieces of code can use it. It does not have to - and should not - say anything about the private implementation of the class.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// Include our base interface definition. AStream is defined in AEE.h</span>
<span class="w"> </span><span class="cp">#include</span><span class="w"> </span><span class="cpf"><AEE.h></span>
<span class="w"> </span><span class="c1">// Provide a cast method</span>
<span class="w"> </span><span class="cp">#define IUCSTREAM_TO_IASTREAM(po) (IAStream*)(po)</span>
<span class="w"> </span><span class="c1">// Define a struct that lets people inherit from the new interface</span>
<span class="w"> </span><span class="cp">#define INHERIT_VTBL(IUCStream) \</span>
<span class="cp"> INHERIT_VTBL(IAStream) \</span>
<span class="cp"> void (*EnableTranslation)(iname * pIUCStream, boolean enable)</span>
<span class="w"> </span><span class="c1">// Define the new interface</span>
<span class="w"> </span><span class="n">DEFINE_VTBL</span><span class="p">(</span><span class="n">IUCStream</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">INHERIT_VTBL</span><span class="p">(</span><span class="n">IUCStream</span><span class="p">);</span>
<span class="w"> </span><span class="p">};</span><span class="n">_</span>
<span class="w"> </span><span class="c1">// Set up standard macros</span>
<span class="w"> </span><span class="c1">// We only define one extra function. We do not define the base </span>
<span class="w"> </span><span class="c1">// functions. Since this class is expected to be used like an </span>
<span class="w"> </span><span class="c1">// IAStream, we will override a couple of functions by hooking into </span>
<span class="w"> </span><span class="c1">// the IAStream implementations. See later</span>
<span class="w"> </span><span class="cp">#define IUCSTREAM_EnableTranslation \</span>
<span class="cp"> AEEGETPVTBL(IUCStream, po)->EnableTranslation()</span>
</code></pre></div>
<h2>Write the private header (UCStream.hpp)</h2>
<p>The private header declares the general implementation of the class, storage of the vtables, and declares functions that will implement interface methods,</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">#include <AEEUCStream.h></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">This</span><span class="w"> </span><span class="k">class</span><span class="w"> </span><span class="n">MUST</span><span class="w"> </span><span class="n">NOT</span><span class="w"> </span><span class="n">use</span><span class="w"> </span><span class="n">any</span><span class="w"> </span><span class="n">C</span><span class="o">++</span><span class="w"> </span><span class="n">virtual</span><span class="w"> </span><span class="n">functions</span><span class="p">,</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">we</span><span class="w"> </span><span class="n">are</span><span class="w"> </span><span class="n">reliant</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">on</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">first</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">bytes</span><span class="w"> </span><span class="n">being</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">interface</span><span class="w"> </span><span class="n">vtable</span><span class="o">.</span><span class="w"> </span><span class="n">If</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">we</span><span class="w"> </span><span class="n">use</span><span class="w"> </span><span class="n">virtuals</span><span class="p">,</span><span class="w"> </span><span class="n">then</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">first</span><span class="w"> </span><span class="n">four</span><span class="w"> </span><span class="n">bytes</span><span class="w"> </span><span class="n">will</span><span class="w"> </span><span class="n">instead</span><span class="w"> </span><span class="n">be</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">C</span><span class="o">++</span><span class="w"> </span><span class="n">vtable</span><span class="o">.</span><span class="w"> </span><span class="n">This</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">useful</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">want</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="n">BREW</span><span class="w"> </span><span class="n">interfaces</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">C</span><span class="o">++</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">but</span><span class="w"> </span><span class="n">only</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">are</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="n">using</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">ADS</span><span class="w"> </span><span class="ow">or</span><span class="w"> </span><span class="n">RVCT</span><span class="w"> </span><span class="mf">1.2</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">compilers</span><span class="p">,</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">these</span><span class="w"> </span><span class="n">have</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">non</span><span class="o">-</span><span class="n">compliant</span><span class="w"> </span><span class="n">ABI</span><span class="w"> </span>
<span class="w"> </span><span class="k">class</span><span class="w"> </span><span class="n">UCStream</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">public</span><span class="p">:</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Factory</span><span class="w"> </span><span class="n">method</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">create</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">IUCStream</span><span class="w"> </span><span class="n">wrapped</span><span class="w"> </span><span class="n">around</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">IAStream</span>
<span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="n">IUCStream</span><span class="o">*</span><span class="w"> </span><span class="n">create</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="p">);</span>
<span class="w"> </span><span class="n">private</span><span class="p">:</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">The</span><span class="w"> </span><span class="n">VTable</span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">AEEVTBL</span><span class="p">(</span><span class="n">IUCStream</span><span class="p">)</span><span class="o">*</span><span class="w"> </span><span class="n">pvt</span><span class="p">;</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Store</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">reference</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">stream</span><span class="w"> </span><span class="n">we</span><span class="w"> </span><span class="n">are</span><span class="w"> </span><span class="n">wrapping</span>
<span class="w"> </span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">stream</span><span class="p">;</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Is</span><span class="w"> </span><span class="n">translation</span><span class="w"> </span><span class="n">on</span><span class="err">?</span>
<span class="w"> </span><span class="n">boolean</span><span class="w"> </span><span class="n">translationEnabled</span><span class="p">;</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Static</span><span class="w"> </span><span class="n">member</span><span class="w"> </span><span class="n">implemnetations</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">interface</span><span class="w"> </span><span class="n">functions</span>
<span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="n">uint32</span><span class="w"> </span><span class="n">Release</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pStream</span><span class="p">);</span>
<span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="n">int32</span><span class="w"> </span><span class="n">Read</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pStream</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="nb nb-Type">void</span><span class="o">*</span><span class="w"> </span><span class="n">pBuffer</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="n">uint32</span><span class="w"> </span><span class="n">dwCount</span><span class="p">);</span>
<span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">EnableTranslation</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pStream</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="n">boolean</span><span class="w"> </span><span class="n">enable</span><span class="p">);</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Other</span><span class="w"> </span><span class="n">interface</span><span class="w"> </span><span class="n">implementations</span><span class="w"> </span><span class="n">hook</span><span class="w"> </span><span class="n">onto</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">IAStream</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">implementation</span>
<span class="w"> </span><span class="n">UCStream</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pStream</span><span class="p">);</span>
<span class="w"> </span><span class="o">~</span><span class="n">UCStream</span><span class="p">(</span><span class="nb nb-Type">void</span><span class="p">)</span><span class="w"> </span><span class="p">{}</span>
<span class="w"> </span><span class="p">};</span>
</code></pre></div>
<h2>Write the factory method implementation</h2>
<p>The first thing we need to do is construct one of these new classes. Since we are not writing an extension, but merely implementing a new interface to be used within our own code, we do not need to hook into CreateInstance, and we can set up the IAStream private member in construction, which is normally impossible. We use a single heap allocation for both the class memory space and the vtable. In order to do this in a slightly safer manner, and save ourselves from arithmetic, we use MALLOCREC_EX from AEEStdLib.h.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="cp">#include</span><span class="w"> </span><span class="cpf">"AEEStdLib.h"</span><span class="c1"> // For MALLOCREC_EX_</span>
<span class="w"> </span><span class="cp">#include</span><span class="w"> </span><span class="cpf"><new></span><span class="c1"> // For placement new</span>
<span class="w"> </span><span class="n">IUCStream</span><span class="o">*</span><span class="w"> </span><span class="nf">UCStream::create</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pStream</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Allocate space for both the vtable and the class itself</span>
<span class="w"> </span><span class="n">UCStream</span><span class="o">*</span><span class="w"> </span><span class="n">ucs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">MALLOCREC_EX</span><span class="p">(</span><span class="n">UCStream</span><span class="p">,</span><span class="w"> </span><span class="kr">sizeof</span><span class="p">(</span><span class="n">AEEVTBL</span><span class="p">(</span><span class="n">IUCStream</span><span class="p">));</span>
<span class="w"> </span><span class="c1">// Placement new</span>
<span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="p">(</span><span class="n">ucs</span><span class="p">)</span><span class="w"> </span><span class="n">UCStream</span><span class="p">(</span><span class="n">pStream</span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">IUCStream</span><span class="o">*</span><span class="p">)</span><span class="n">ucs</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="n">UCStream</span><span class="o">::</span><span class="n">UCStream</span><span class="p">(</span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pStream</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Initialise the vtable</span>
<span class="w"> </span><span class="n">AEEVTBL</span><span class="p">(</span><span class="n">IUCStream</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="n">vt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">AEEVTBL</span><span class="p">(</span><span class="n">IUCStream</span><span class="p">)</span><span class="o">*</span><span class="p">)(</span><span class="n">this</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span>
<span class="w"> </span><span class="n">pvt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">vt</span><span class="p">;</span>
</code></pre></div>
<p>Now we come to initialising the vtable. As we mentioned, we'll be using IASTREAM default methods in several cases. We could write a UCStream::AddRef method that simply called through to IASTREAM_AddRef - but it's much easier to use our knowledge of the vtable layout, and point directly at the correct method. We would need to be a bit more rigorous in our implementation if writing an extension with its own class id - this will be discussed in Part 3.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// Use the base AStream implemnetations for some functions</span>
<span class="w"> </span><span class="n">AEEVTBL</span><span class="p">(</span><span class="n">IAStream</span><span class="p">)</span><span class="o">*</span><span class="w"> </span><span class="n">astreamVT</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">AEEGETPVTBL</span><span class="p">(</span><span class="n">pStream</span><span class="p">,</span><span class="w"> </span><span class="n">IAStream</span><span class="p">);</span>
<span class="w"> </span><span class="n">vt</span><span class="o">-></span><span class="n">AddRef</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">astreamVT</span><span class="o">-></span><span class="n">AddRef</span><span class="p">;</span>
<span class="w"> </span><span class="n">vt</span><span class="o">-></span><span class="n">Readable</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">astreamVT</span><span class="o">-></span><span class="n">Readable</span><span class="p">;</span>
<span class="w"> </span><span class="n">vt</span><span class="o">-></span><span class="n">Cancel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">astreamVT</span><span class="o">-></span><span class="n">Cancel</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Point the rest at our own implementations</span>
<span class="w"> </span><span class="n">vt</span><span class="o">-></span><span class="n">Release</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UCStream</span><span class="p">::</span><span class="n">Release</span><span class="p">;</span>
<span class="w"> </span><span class="n">vt</span><span class="o">-></span><span class="n">Read</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UCStream</span><span class="p">::</span><span class="n">Read</span><span class="p">;</span>
<span class="w"> </span><span class="n">vt</span><span class="o">-></span><span class="n">EnableTranslation</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UCStream</span><span class="p">::</span><span class="n">EnableTranslation</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
</code></pre></div>
<h2>Implement the interface</h2>
<p>This is the easiest bit. We want to take data read from the IAStream, and do some post-processing on it.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="p">#</span><span class="nn">define</span><span class="w"> </span><span class="nt">ME_FROM_IUCSTREAM</span><span class="o">(</span><span class="nt">pStream</span><span class="o">)</span><span class="w"> </span><span class="nt">UCStream</span><span class="o">*</span><span class="w"> </span><span class="nt">me</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">(</span><span class="nt">UCStream</span><span class="o">*)</span><span class="nt">pStream</span><span class="o">;</span>
<span class="w"> </span><span class="nt">int32</span><span class="w"> </span><span class="nt">UCStream</span><span class="p">::</span><span class="nd">Read</span><span class="o">(</span><span class="nt">IUCStream</span><span class="o">*</span><span class="w"> </span><span class="nt">pStream</span><span class="o">,</span><span class="w"> </span><span class="nt">void</span><span class="o">*</span><span class="w"> </span><span class="nt">pBuffer</span><span class="o">,</span><span class="w"> </span><span class="nt">uint32</span><span class="w"> </span><span class="nt">dwCount</span><span class="o">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="err">ME_FROM_IUCSTREAM(pStream)</span><span class="p">;</span>
<span class="w"> </span><span class="err">int32</span><span class="w"> </span><span class="err">bytes_read</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">IASTREAM_Read(me->stream,</span><span class="w"> </span><span class="err">pBuffer,</span><span class="w"> </span><span class="err">dwCount)</span><span class="p">;</span>
<span class="w"> </span><span class="err">if(bytes_read</span><span class="w"> </span><span class="err">></span><span class="w"> </span><span class="err">0</span><span class="w"> </span><span class="err">&&</span><span class="w"> </span><span class="err">me->translationEnabled)</span>
<span class="w"> </span><span class="err">{</span>
<span class="w"> </span><span class="err">char*</span><span class="w"> </span><span class="err">buf</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">(char*)pBuffer</span><span class="p">;</span>
<span class="w"> </span><span class="err">char*</span><span class="w"> </span><span class="err">end</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">buf</span><span class="w"> </span><span class="err">+</span><span class="w"> </span><span class="err">bytes_read</span><span class="p">;</span>
<span class="w"> </span><span class="err">while(buf</span><span class="w"> </span><span class="err"><</span><span class="w"> </span><span class="err">end)</span>
<span class="w"> </span><span class="err">{</span>
<span class="w"> </span><span class="err">if(*buf</span><span class="w"> </span><span class="err">>=</span><span class="w"> </span><span class="err">'a'</span><span class="w"> </span><span class="err">&&</span><span class="w"> </span><span class="err">*buf</span><span class="w"> </span><span class="err"><=</span><span class="w"> </span><span class="err">'z')</span>
<span class="w"> </span><span class="err">{</span>
<span class="w"> </span><span class="err">*buf</span><span class="w"> </span><span class="err">+=</span><span class="w"> </span><span class="err">'A'</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">'a'</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="nt">buf</span><span class="o">++;</span>
<span class="w"> </span><span class="err">}</span>
<span class="w"> </span><span class="err">}</span>
<span class="w"> </span><span class="nt">return</span><span class="w"> </span><span class="nt">bytes_read</span><span class="o">;</span>
<span class="w"> </span><span class="err">}</span>
</code></pre></div>
<p>Our Release method uses the reference counting of IAStream to avoid the need to store a reference count ourselves. When the ref count hits 0, we'll cleanup this object as well.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nt">uint32</span><span class="w"> </span><span class="nt">UCStream</span><span class="p">::</span><span class="nd">Release</span><span class="o">(</span><span class="nt">IUCStream</span><span class="o">*</span><span class="w"> </span><span class="nt">pStream</span><span class="o">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="err">ME_FROM_IUCSTREAM(pStream)</span><span class="p">;</span>
<span class="w"> </span><span class="err">int32</span><span class="w"> </span><span class="err">nRefs</span><span class="p">;</span>
<span class="w"> </span><span class="err">nRefs</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">IASTREAM_Release(me->stream)</span><span class="p">;</span>
<span class="w"> </span><span class="err">if(0</span><span class="w"> </span><span class="err">==</span><span class="w"> </span><span class="err">nRefs)</span>
<span class="w"> </span><span class="err">{</span>
<span class="w"> </span><span class="err">delete</span><span class="w"> </span><span class="err">me</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="nt">return</span><span class="w"> </span><span class="nt">nRefs</span><span class="o">;</span>
<span class="w"> </span><span class="err">}</span>
</code></pre></div>
<p>Finally, and simplest of all, we implement the new method</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nt">void</span><span class="w"> </span><span class="nt">UCStream</span><span class="p">::</span><span class="nd">EnableTranslation</span><span class="o">(</span><span class="nt">IUCStream</span><span class="o">*</span><span class="w"> </span><span class="nt">pStream</span><span class="o">,</span><span class="w"> </span><span class="nt">boolean</span><span class="w"> </span><span class="nt">enable</span><span class="o">)</span>
<span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w"> </span><span class="err">ME_FROM_IUCSTREAM(pStream)</span><span class="p">;</span>
<span class="w"> </span><span class="err">me->translationEnabled</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">enable</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
</code></pre></div>
<h2>Use the interface in your code</h2>
<p>You can use an IUCStream anywhere that you'd use an IAStream. For example:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">IAStream</span><span class="o">*</span><span class="w"> </span><span class="n">pAStream</span><span class="p">;</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Get</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">ASTREAM</span><span class="w"> </span><span class="n">from</span><span class="w"> </span><span class="n">somewhere</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">simplest</span><span class="w"> </span><span class="n">way</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">construct</span><span class="w"> </span><span class="n">a</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">MEMSTREAM</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">use</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">ASTREAM</span><span class="p">,</span><span class="w"> </span><span class="ow">or</span><span class="w"> </span><span class="n">build</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">IPEEK</span><span class="w"> </span><span class="n">from</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">ISOURCEUTIL</span>
<span class="w"> </span><span class="o">...</span>
<span class="w"> </span><span class="n">IUCSStream</span><span class="w"> </span><span class="n">pUCStream</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UCSStream</span><span class="p">::</span><span class="n">create</span><span class="p">(</span><span class="n">pAStream</span><span class="p">);</span>
<span class="w"> </span><span class="n">IUCSTREAM_EnableTranslation</span><span class="p">(</span><span class="n">pUCStream</span><span class="p">,</span><span class="w"> </span><span class="n">TRUE</span><span class="p">);</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Now</span><span class="w"> </span><span class="n">use</span><span class="w"> </span><span class="n">it</span><span class="w"> </span><span class="n">like</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">IAStream</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">any</span><span class="w"> </span><span class="n">method</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="n">requires</span><span class="w"> </span><span class="n">an</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">IAStream</span><span class="o">*.</span><span class="w"> </span><span class="n">You</span><span class="w"> </span><span class="n">can</span><span class="w"> </span><span class="n">directly</span><span class="w"> </span><span class="n">cast</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="ow">or</span><span class="w"> </span><span class="n">better</span><span class="w"> </span><span class="n">use</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">defined</span><span class="w"> </span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">cast</span><span class="w"> </span><span class="n">macro</span>
<span class="w"> </span><span class="n">int32</span><span class="w"> </span><span class="n">bytes_read</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">IASTREAM_Read</span><span class="p">(</span>
<span class="w"> </span><span class="n">IUCSTREAM_TO_IASTREAM</span><span class="p">(</span><span class="n">pUCStream</span><span class="p">),</span><span class="w"> </span>
<span class="w"> </span><span class="n">pBuffer</span><span class="p">,</span><span class="w"> </span>
<span class="w"> </span><span class="n">dwCount</span><span class="p">);</span>
</code></pre></div>BREW interfaces - how they work, and how to use them2007-05-01T16:35:00+01:002007-05-01T16:35:00+01:00Ben Blaukopftag:airsource.co.uk,2007-05-01:/blog/2007/05/01/brew-interfaces-how-they-work-and-how-to-use-them/<p>BREW makes heavy use of an interface framework derived from COM. It's very useful to understand the internals of how it works, especially if you want to extend any of the built in classes. In this series of articles, I will discuss:</p>
<ul>
<li>Part 1. The internals of a BREW interface …</li></ul><p>BREW makes heavy use of an interface framework derived from COM. It's very useful to understand the internals of how it works, especially if you want to extend any of the built in classes. In this series of articles, I will discuss:</p>
<ul>
<li>Part 1. The internals of a BREW interface.</li>
<li><a href="https://airsource.co.uk/blog/2007/06/05/brew-interfaces-implementing-a-new-interface/">Part 2. How to implement a simple new interface.</a></li>
<li>Part 3 (not published yet). How to publish your new interface as an extension.</li>
</ul>
<h1>Part 1. The internals of a BREW interface.</h1>
<p>In object-oriented terms, ISHELL_CreateInstance can be considered a factory method, that instantiates classes on request, and returns an interface pointer.
As arguments to ISHELL_CreateInstance, you pass in a classid - e.g. AEECLSID_WEB, and as a return you get an interface pointer - e.g. an IWeb<em>. If you happened to know what the class instance - i.e. the AEEWeb instance - looked like, you could cast that IWeb</em> directly to an AEEWeb*, and it would work. That's why the following snippet of code works:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">ISHELL</span><span class="o">*</span><span class="w"> </span><span class="n">getShell</span><span class="p">(</span><span class="n">void</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// GETAPPINSTANCE() returns an IApplet</span>
<span class="w"> </span><span class="c1">// We can cast this directly to an </span>
<span class="w"> </span><span class="c1">// AEEApplet (defined in AEEAppGen.h)</span>
<span class="w"> </span><span class="n">AEEApplet</span><span class="o">*</span><span class="w"> </span><span class="n">applet</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">AEEApplet</span><span class="o">*</span><span class="p">)</span><span class="n">GETAPPINSTANCE</span><span class="p">();</span>
<span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="n">applet</span><span class="o">-></span><span class="n">m_pIShell</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
</code></pre></div>
<p>Let's assume, for the moment, that you want to actually use an interface though, for example IWeb. If you write the following code</p>
<div class="highlight"><pre><span></span><code> IWEB_Release(pIWeb);
</code></pre></div>
<p>You are not calling a function IWEB_Release. IWEB_Release is actually a macro, and I'll expand it below:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// IWEB_Release(pIWeb);</span>
<span class="w"> </span><span class="n">AEEGETPVTBL</span><span class="p">((</span><span class="n">pIWeb</span><span class="p">),</span><span class="n">IWeb</span><span class="p">)</span><span class="o">-></span><span class="n">Release</span><span class="p">();</span>
</code></pre></div>
<p>which expands to:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// IWEB_Release(pIWeb);</span>
<span class="w"> </span><span class="c1">// AEEGETPVTBL((pIWeb),IWeb)->Release();</span>
<span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="p">((</span><span class="n">AEEVTBL</span><span class="p">(</span><span class="n">IWeb</span><span class="p">)</span><span class="o">**</span><span class="p">)((</span><span class="n">void</span><span class="o">*</span><span class="p">)(</span><span class="n">pIWeb</span><span class="p">))))</span><span class="o">-></span><span class="n">Release</span><span class="p">();</span>
</code></pre></div>
<p>keep expanding...</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// IWEB_Release(pIWeb);</span>
<span class="w"> </span><span class="c1">// AEEGETPVTBL((pIWeb),IWeb)->Release();</span>
<span class="w"> </span><span class="c1">//(*((AEEVTBL(IWeb)**)((void*)pIWeb)))->Release();</span>
<span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">IWebVtbl</span><span class="o">**</span><span class="p">)((</span><span class="n">void</span><span class="o">*</span><span class="p">)</span><span class="n">pIWeb</span><span class="p">))</span><span class="o">-></span><span class="n">Release</span><span class="p">();</span>
</code></pre></div>
<p>The situation here is illustrated by the diagram below. When we write IWEB_Release(pIWeb) we start with pIWeb, which happens to point an AEEWeb instance. However, we don't really care what the object is. The only contract we have with is that the first word <strong>must</strong> point to an instance of an IWebVtbl. The IWebVtbl is a struct, which contains pointers to functions, thus defining the interface that IWeb presents.
<img alt="IWeb in memory" src="https://airsource.co.uk/blog/images/2007/05/vtbl.png"></p>
<p>AEEWeb.h contains a long and detailed description of the IWeb interface. This could be condensed down to just the following few lines:</p>
<div class="highlight"><pre><span></span><code> typedef struct _IWeb
{
int (*addref)(struct _IWeb*);
int (*release)(struct _IWeb*);
....
} IWeb;
</code></pre></div>
<p>and you would write code like this</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">ISHELL_CreateInstance</span><span class="p">(</span><span class="n">pIShell</span><span class="p">,</span><span class="w"> </span><span class="n">AEECLSID_WEB</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">void</span><span class="o">**</span><span class="p">)</span><span class="o">&</span><span class="n">pIWeb</span><span class="p">;);</span>
<span class="w"> </span><span class="n">pIWeb</span><span class="o">-></span><span class="n">release</span><span class="p">(</span><span class="n">pIWeb</span><span class="p">);</span>
</code></pre></div>
<p>The only reason you don't write code like that, is because the BREW coders defined a lot of helpful macros. They are helpful, most of the time, but they do shield you from what's going on under the scenes.</p>
<p>It should now be clear that the structures underlying a BREW interface are relatively simple. In part 2, I'll go into more depth, and show how to create a simple BREW class from scratch.</p>Your application drained my battery2007-04-17T10:36:00+01:002007-04-17T10:36:00+01:00Ben Blaukopftag:airsource.co.uk,2007-04-17:/blog/2007/04/17/your-application-drained-my-battery/<p>A client rings up and says 'your application killed my battery. I ran it, and within 30 minutes my battery was flat'. How do you prove that it's not your application at fault? Now, bearing in mind most mobile phone batteries last for a week or more on standy, even if you some long and very tedious tests, it's going to take you forever to prove, well, nothing really. Certainly nothing that will convince the client.<p>A client rings up and says 'your application killed my battery. I ran it, and within 30 minutes my battery was flat'. How do you prove that it's not your application at fault? Now, bearing in mind most mobile phone batteries last for a week or more on standy, even if you some long and very tedious tests, it's going to take you forever to prove, well, nothing really. Certainly nothing that will convince the client.</p>
<p>This was a real situation for me a couple of years ago. Product management were breathing down my neck with all kinds of 'helpful' suggestions. After two hours of doing nothing but responding to their emails I shut my office door (well, we had an open-plan office, so I told my manager to handle the emails and not let anyone talk to me), and sat down to think about it. Five minutes later I had a solution.</p>
<p>For some reason we had a brand spanking new power supply hanging around the lab. I used to work at <a href="http://www.camcon.co.uk/">Cambridge Consultants</a>, where besides writing software I did a lot of playing with scopes and soldering irons. I like software, but I really like software when I have complete control over the device. So anything hardware-ish caught my eye.</p>
<p>I got a couple of microprobes, figures out which lines were power and ground on the battery, and then hooked up the PSU in place of the battery, set to constant voltage and variable current. Then I measured the normal standby current on the phone which gave me a baseline to test against. Then I measured the current drawn by my application going at full-burn sending network traffic. The current was much higher, obviously, but still nowhere near that needed to drain the battery in half an hour. After all, that's an order of magnitude less than the talk time! Then I watched the current drawn in standby mode, seeing the occasional spike as the app woke up to do some housekeeping and contact the server, using perfectly reasonable amounts of current. This was quickly followed by a return to normal standby levels.</p>
<p>These results were enough to convince me, and more importantly the client, that the bug report was completely bogus. We're guessing that someone charged a phone, saw the battery die quickly, but didn't notice that the cleaner has unplugged the charger in order to do the vacuum cleaning.</p>So who or what is Airsource?2007-04-04T15:57:00+01:002007-04-04T15:57:00+01:00Ben Blaukopftag:airsource.co.uk,2007-04-04:/blog/2007/04/04/so-who-or-what-is-airsource/<p>There's a strong Airsource theme in this Blog. But what is <a href="http://www.airsource.co.uk/">Airsource</a>? <p>There's a strong Airsource theme in this Blog. But what is <a href="http://www.airsource.co.uk/">Airsource</a>? </p>
<p><a href="http://www.airsource.co.uk"><img alt="airsource_process310" src="https://airsource.co.uk/blog/images/2009/02/airsource_process310.jpg"></a></p>
<p>Our <a href="http://www.airsource.co.uk/">website</a> will tell you a bit more about the company and the <a href="http://www.airsource.co.uk/about">people</a>, but in short, we're a mobile software company, based in Cambridge, UK. We've got experience on a bunch of mobile platforms - all the usual suspects like Pocket PC, Symbian - and can code just about anything you want on any phone. Our specialist area, though, is BREW. We reckon we know more about BREW than any other consultancy in Europe. And if it's uiOne you need help with, then we know more about that than anyone. Why? Well before we formed Airsource, we used to work at a small company called Trigenix, which wrote some software called Trigplayer. Trigplayer is the renderer used in uiOne, and we wrote versions 1,2,3 and, yes, you guessed, version 4, which just happens to be the BREW version used in uiOne.</p>
<p><strong>Update</strong> - Like all blog posts, this one is only valid when written. It is worth pointing out that since this article was written we have launched products on Symbian S60, and on iPhone, not to mention having gained development experience on Windows Mobile, Android, and Blackberry. We still know lots about BREW - and we know lots about other stuff too.</p>Floating Point on ARM2007-03-22T23:27:00+00:002007-03-22T23:27:00+00:00Ben Blaukopftag:airsource.co.uk,2007-03-22:/blog/2007/03/22/floating-point-on-arm/<p>This should really be read before my previous post (<a href="https://airsource.co.uk/blog/2007/03/20/floating-point-on-brew/">Floating Point on BREW</a>). It's a brief description of how floating point arithmetic works, and why the BREW environment is limited.<p>This should really be read before my previous post (<a href="https://airsource.co.uk/blog/2007/03/20/floating-point-on-brew/">Floating Point on BREW</a>). It's a brief description of how floating point arithmetic works, and why the BREW environment is limited.</p>
<p>When you write some code like this:</p>
<div class="highlight"><pre><span></span><code> int a = x + y;
</code></pre></div>
<p>the compiler, be that GNUDE, ADS, RVCT, or WinARM, compiles it into something like</p>
<div class="highlight"><pre><span></span><code> ADD r0, r1, r2
</code></pre></div>
<p>i.e. take the contents of the registers R1, and R2, which have hopefully been preloaded with the variables x and y, add the two together, and stick the result in R0, which we may later store in the memory location relating to the variable a.</p>
<p>If you now want to do the same in floating point, that's fine:</p>
<div class="highlight"><pre><span></span><code> double a = x + y;
</code></pre></div>
<p>compiles to</p>
<div class="highlight"><pre><span></span><code> ADF F0,F1,F2
</code></pre></div>
<p>We just take the contents of the floating point registers F1, and F2, execute the instruction ADF, which funnily enough adds two floats together, and there you are.</p>
<p>It's not quite that simple, as you might have guessed. While the above is valid assembly (and hopefully the compiler is capable of turning it into valid machine code), no ARM7 or ARM9 core has any hardware support for floating point. Just because the ARM instruction set defines floating point instructions doesn't mean that a particular ARM core has to implement them. They'll take one look at ADF F0,F1,F2 and promptly throw an illegal instruction trap. And that means that you aren't going to get hardware floating point working on <strong>any</strong> BREW handset.</p>
<h1>So how can I use floats in C?</h1>
<p>Historically, there have been a couple of approaches. The first is to write a piece of code that handles the illegal instruction trap, figures out what the instruction was, calls an appropriate function, and then jumps back into the original code just after the instruction that triggered the trap. If this sounds complicated, it is. It's effectively implementing a coprocessor in software, and is known as a Software FPU. It's not unique to ARM; there was a Software FPU available for the 680x0 series processor back in the days of the Mac IIsi and so forth.</p>
<p>The second approach requires compiler support. Instead of generating floating point instructions, the example above is treated as though we actually wrote:</p>
<div class="highlight"><pre><span></span><code> double a = _dadd(x,y);
</code></pre></div>
<p>_dadd being a function that is present in a library thoughtfully provided by the suppliers of the compiler. The compiler handles the marshalling of the parameters when calling the function, no floating point instruction is ever generated, and the processor remains blissfully unaware that floating point was ever used. </p>
<p>The only point left to make is that GCC, ADS, and so forth do all this for you. But the library provided is compiled without the /norwpi and /ropi flags needed for BREW development, and consequently can't be used. If you are using elf2mod, of course, and don't need those flags, then there's every possibility that it may work, but there's no need to risk it; we can use floats.o, supplied by QUALCOMM, instead. See <a href="https://airsource.co.uk/blog/2007/03/20/floating-point-on-brew/">Floating Point on BREW</a> for a full explanation.</p>Floating Point on BREW2007-03-20T22:48:00+00:002007-03-20T22:48:00+00:00Ben Blaukopftag:airsource.co.uk,2007-03-20:/blog/2007/03/20/floating-point-on-brew/<p>Pretty much any BREW developer knows that you can't use floating point. Or, to be more precise, you can't use floating point without jumping through a few hoops. You essentially have three options<p>Pretty much any BREW developer knows that you can't use floating point. Or, to be more precise, you can't use floating point without jumping through a few hoops. You essentially have three options</p>
<p>1) Don't use floating point </p>
<p>If you can manage it, this is definitely the best option. ARM processors, almost without exception, have no floating point unit. Any floating point operations need to be done in software, and needless to say, that's SLOW.</p>
<p>2) Use the BREW APIs </p>
<p>A bit of a middleground, this one. If you don't need floating point much, but you do need it, then you can just write everything using the BREW floating point API. This uses constructs like:</p>
<div class="highlight"><pre><span></span><code> double myResult = FADD(1.0,2.0);
</code></pre></div>
<p>which isn't the most readable code on the planet. It's not portable, you can only uses doubles, not floats, and it's not pretty. But it does work.</p>
<p>3) Use a software floating point library </p>
<p>This is the point where you say 'hey, I thought the compiler came with one of those'. Well, if truth be told, it does. And if you compile up a standard application with something like</p>
<div class="highlight"><pre><span></span><code> double myResult = 1.0 + 2.0;
</code></pre></div>
<p>then it will compile just fine, right up to the point where you try to link, and you discover that the floating point library shipped with the compiler wasn't compiled with the correct flags for relocatable code. Whoops.</p>
<p>Actually, if you try and compile the code above, and just the code above, it will work fine - because the standard makefile will compile with -O2, which means dead code is optimised straight out. Use the following code in your event loop instead:</p>
<div class="highlight"><pre><span></span><code> double d = 0.0;
d += myIntVariable;
myIntVariable = (int)d;
</code></pre></div>
<p>This <strong>will</strong> generate the following errors:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nl">Error:</span><span class="w"> </span><span class="nl">L6265E:</span><span class="w"> </span><span class="n">Non</span><span class="o">-</span><span class="n">rwpi</span><span class="w"> </span><span class="n">Section</span><span class="w"> </span><span class="n">libspace</span><span class="p">.</span><span class="n">o</span><span class="p">(.</span><span class="n">bss</span><span class="p">)</span><span class="w"> </span><span class="n">cannot</span><span class="w"> </span><span class="n">be</span>
<span class="w"> </span><span class="n">assigned</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="n">Exec</span><span class="w"> </span><span class="n">region</span><span class="w"> </span><span class="p">'</span><span class="n">ER_ZI</span><span class="p">'.</span><span class="w"> </span><span class="nl">Error:</span><span class="w"> </span><span class="nl">L6248E:</span>
<span class="w"> </span><span class="n">libspace</span><span class="p">.</span><span class="n">o</span><span class="p">(.</span><span class="n">text</span><span class="p">)</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="n">region</span><span class="w"> </span><span class="p">'</span><span class="n">ER_RO</span><span class="p">'</span><span class="w"> </span><span class="n">cannot</span><span class="w"> </span><span class="n">have</span><span class="w"> </span><span class="n">address</span><span class="w"> </span><span class="k">type</span>
<span class="w"> </span><span class="n">relocation</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">__libspace_start</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="n">region</span><span class="w"> </span><span class="p">'</span><span class="n">ER_ZI</span><span class="p">'.</span>
</code></pre></div>
<p>As an aside, these error messages aren't terribly helpful. If you go to c:\Apps\ARM\RVCT_BREWv1_2 - or wherever you installed it - and rename Lib to NoLib, then rebuild, you will get <strong>much</strong> more helpful error messages:</p>
<div class="highlight"><pre><span></span><code> Error: L6218E: Undefined symbol _dflt (referred from mytest.o)
Error: L6218E: Undefined symbol _dadd (referred from mytest.o)
Error: L6218E: Undefined symbol _dfix (referred from mytest.o)
</code></pre></div>
<p>Telling us that we're using floating point operations in mytest.o, which is a lot more helpful than moaning about libspace.o. Don't forget to rename NoLib back to Lib once you've fixed everything...</p>
<p>The obvious solution is to go implement all the relevant symbols that the linker is failing on in a relocatable manner, and then link against those instead of the standard library. This is the approach that <a href="http://www.s-cradle.com/english/index.html">Sophia Framework</a> takes. Fortunately, QUALCOMM have released a <a href="https://brewx.qualcomm.com/bws/content/gi/common/appseng/en/knowledgebase/docs/floats.zip">floating point library (AKA floats.o)</a> that does much the same thing for free (though to be fair, Sophia Framework does a whole lot more besides). Link against this library, and you can do pretty much whatever you want with floating point, without any hacking around with arcanely named macros.</p>
<p>There are, as always, a couple of caveats:</p>
<ul>
<li>It doesn't work with ADS 2.2, because the ABI (Application Binary Interface) changed. Use ADS 2.1 or RVCT</li>
<li>It doesn't work with GCC (allegedly, not tried myself)</li>
</ul>
<p>The inherent danger with this solution is that the freedom to use floats wherever you like means that you are tempted to use them in inappropriate places. Just remember that it's all done in software - and it's SLOW.</p>Windows Mobile Insanity2007-03-19T12:59:00+00:002007-03-19T12:59:00+00:00Nick Clareytag:airsource.co.uk,2007-03-19:/blog/2007/03/19/windows-mobile-insanity/<p>It's been a few years since I got stuck into Windows Mobile programming - back in the days of the Orange SPV when Windows Mobile was known as Smartphone 2002 - and the time has arrived to get back into it again. It used to be that Microsoft helpfully supplied you with a cut-down version of the Visual Studio toolkit called "Embedded Visual Studio" which you could download for free, but now you're handcuffed to Visual Studio .NET 2005 if you want to do anything with Windows Mobile 5.0 or later. No worries, it was time for an upgrade anyway.<p>It's been a few years since I got stuck into Windows Mobile programming - back in the days of the Orange SPV when Windows Mobile was known as Smartphone 2002 - and the time has arrived to get back into it again. It used to be that Microsoft helpfully supplied you with a cut-down version of the Visual Studio toolkit called "Embedded Visual Studio" which you could download for free, but now you're handcuffed to Visual Studio .NET 2005 if you want to do anything with Windows Mobile 5.0 or later. No worries, it was time for an upgrade anyway.</p>
<p>But of course, that's only part of the story - you need to get hold of the various SDKs, emulator images and associated bumpf which goes with it. Microsoft now helpfully offer to <a href="http://msdn.microsoft.com/windowsmobile/downloads/resourcekit/default.aspx">ship</a> you a couple of DVDs rather than letting you download it all (BitTorrent anyone?) so I thought, well, why not?</p>
<p>Trip over to the <a href="http://www.microsoft.com/windowsmobile/developers/default.mspx">Microsoft Windows Mobile</a> developer page - cool, link off to the "Developer Resource Kit", order a couple of DVDs...uh oh:</p>
<p><img alt="Mozilla - Bad formatting" src="https://airsource.co.uk/blog/images/2007/03/mozilla.png"></p>
<p>Yup, made the mistake of trying to order something from Microsoft using a non-Microsoft browser. Fine, use the "Send To IE" plugin for Firefox and off we go...uh oh:</p>
<p><img alt="IE7 - Broken" src="https://airsource.co.uk/blog/images/2007/03/ie7.png"></p>
<p>You just get "stuck" on this page, no matter how many times you hammer the "Buy Now" button. My goodness - could it be that Microsoft haven't updated their own website to use IE7? Surely not. Fortunately I have a VMWare image "locked" to IE6 for testing purposes - and look what happens when I use it:</p>
<p><img alt="IE6 - In VMWare" src="https://airsource.co.uk/blog/images/2007/03/ie6-vmware.png"></p>
<p>Yup, some of Microsoft's own internal pages don't work with IE7. Says it all, really, doesn't it?</p>The Art of the Demo2007-03-13T17:12:00+00:002007-03-13T17:12:00+00:00Ben Blaukopftag:airsource.co.uk,2007-03-13:/blog/2007/03/13/the-art-of-the-demo/<p><a href="http://www.mobilemonday.net">Mobile Monday</a> is a great networking event for companies in the mobile space, and Airsource regularly attends. Last night I went along to their <a href="http://mobilemonday.org.uk/2007/03/mobile-monday-london-march-12th-event.html">Demo Night</a>, where instead of the usual format of panel debate and a couple of demos, the entire evening was given over to ten demos.<p><a href="http://www.mobilemonday.net">Mobile Monday</a> is a great networking event for companies in the mobile space, and Airsource regularly attends. Last night I went along to their <a href="http://mobilemonday.org.uk/2007/03/mobile-monday-london-march-12th-event.html">Demo Night</a>, where instead of the usual format of panel debate and a couple of demos, the entire evening was given over to ten demos.</p>
<p>Airsource isn't quite ready to demo anything yet, but when we are, we'll certainly be taking it to Mobile Monday. You get a pretty much captive audience of up to 200 senior people, all in your industry. What's not to like? Well, for some people, quite a lot. If you're going to hold the attention of 200 busy people for seven minutes, there are a few rules to follow.</p>
<p>1) Explain your product</p>
<p>First off, explain what your product is! If you spend your whole presentation showing what your product does - but without explaining what it actually is - then your audience are simply going to sit there trying to figure out what the big deal is.</p>
<p>2) Explain why we would want to use it</p>
<p>Then, explain why we might want to use it. Again, not what it can do, but why we want to do those things.</p>
<p>3) Explain what unique features make it special</p>
<p>Don't explain why everyone else's products are bad. Explain why yours is good.</p>
<p>4) Explain it audibly and clearly</p>
<p>And finally, and most importantly, make sure we can hear those points.</p>
<p>The best presentations from last night more or less made those points, and we heard every word. <a href="http://www.mobyko.com">Mobyko</a> gave a funny, simple and compelling demo, using a stooge planted in the audience. Personally, it's very unlikely that I'll run into a business need for that product - but I'll certainly remember them. The middle-of-the-road demos got the point across, but perhaps not as clearly or as memorably as they could have done.</p>
<p>As for the rest, in one case I had no idea why I, or anyone else, would ever want to use their product; in another neither my neighbour nor I could figure out what the company name was. The product looked like it might have been quite interesting, but the presenter made far too many points too quickly without stopping to explain the essentials. It looked like it had been practiced in front of engineers who knew the product inside out.</p>
<p>I'm not saying that when Airsource demos, it will be the world's best demo - but we'll make sure we understand our audience.</p>Don't Surprise The User2007-03-07T23:18:00+00:002007-03-07T23:18:00+00:00Ben Blaukopftag:airsource.co.uk,2007-03-07:/blog/2007/03/07/dont-surprise-the-user/<p>Back when I worked at Qualcomm, and previously Trigenix, I spent most of my time working on mobile phone UIs, or more specifically on uiOne and its earlier incarnations, a tool making it much easier to implement a mobile UI from scratch. As a matter of necessity, we spent a lot of time looking at UIs on embedded systems. I could go on, at length, about some appalling examples of usability, but that's a topic for another day. The question is, what makes a good UI? Why is one application awesome when others are terrible? <p>Back when I worked at Qualcomm, and previously Trigenix, I spent most of my time working on mobile phone UIs, or more specifically on uiOne and its earlier incarnations, a tool making it much easier to implement a mobile UI from scratch. As a matter of necessity, we spent a lot of time looking at UIs on embedded systems. I could go on, at length, about some appalling examples of usability, but that's a topic for another day. The question is, what makes a good UI? Why is one application awesome when others are terrible? </p>
<p><a href="http://www.joelonsoftware.com/">Joel Spolsky</a> makes a very good point about this. <em>Don't Surprise the User</em>. Well, I paraphrase. What he actually said was <a href="http://www.joelonsoftware.com/uibook/chapters/fog0000000057.html">'A user interface is well-designed when the program behaves exactly how the user thought it would'.</a></p>
<p>So what does that mean? An article I came across at <a href="http://www.mobilejones.com/archives/2006/2/">Mobile Jones</a> has good things to say about Shozu. <a href="http://www.shozu.com">Shozu</a> is a photo upload (among other things) application that runs on a number of clients. Of necessity, it uses the network a lot, and it takes note of the roaming status. If you are currently uploading a photo, or downloading a <a href="http://www.shozu.com/portal/tour.do?operation=pmedia">ZuCast</a> (Shozu's term for a podcast), and you move into a roaming area, Shozu will detect this, pause the transfer, and tell you that it has done so, and that it will resume automatically when you cease roaming. This, I think you'll agree, is a bit surprising. On the other hand, it's a whole lot less surprising for the user than receiving a phone bill with a £20 roaming charge. <em>Don't Surprise The User</em>.</p>Why doesn't my BREW project work on this machine?2007-02-23T11:29:00+00:002007-02-23T11:29:00+00:00Ben Blaukopftag:airsource.co.uk,2007-02-23:/blog/2007/02/23/why-doesnt-my-brew-project-work-on-this-machine/<p>I had a client call me yesterday; he'd built a BREW application (most of the code provided by Airsource) on one machine, and it wouldn't run on another. It ran on all of our machines, so this was a bit difficult for us to debug, until eventually we tried it on a pretty much vanilla VMWare partition - and it wouldn't start. "Unable to start application" chirped the simulator, leaving us none the wiser. </p>
<p>I had a client call me yesterday; he'd built a BREW application (most of the code provided by Airsource) on one machine, and it wouldn't run on another. It ran on all of our machines, so this was a bit difficult for us to debug, until eventually we tried it on a pretty much vanilla VMWare partition - and it wouldn't start. "Unable to start application" chirped the simulator, leaving us none the wiser. </p>
<p>Our instant thought was that we'd left a symbol in - say a stray memcpy instead of MEMCPY. But then again, it's not like the BREW Simulator doesn't use memcpy - and anyway, it worked on all of our machines.... </p>
<p>Some time later, after searching the BREW forums extensively, we discovered that if we installed Visual Studio, it all started working. And some time after that, we tracked it down to MSVCRTD.DLL, which is the RunTime Debug library that comes as part of Visual Studio. When you compile a Debug project in Visual Studio, it links against MSVCRTD.DLL. Compile in release mode, and you need MSVCRT.DLL - but that, or course, is present on every Windows machine. If the debug library is absent - which it will be if you haven't installed Visual Studio - then your applet can't load - and you get the ubiquitous "Unable to start application" error.</p>
<p>So the moral is - if you want to <em>run</em> your project on another machine - but not <em>debug</em> it - then build the release mode version. It doesn't hurt to test your release build occasionally either...</p>3GSM for £1002007-02-19T15:33:00+00:002007-02-19T15:33:00+00:00Nick Clareytag:airsource.co.uk,2007-02-19:/blog/2007/02/19/3gsm-for-100/<p>Well if that hasn't caught your attention I don't know what will!</p>
<p>Having worked in the wireless industry since University (almost 10 years now), I'm no stranger to attending conferences. They're a great way to meet potential customers, to gasbag with old acquaintances and to take the pulse of the entire industry.</p>
<p>Well if that hasn't caught your attention I don't know what will!</p>
<p>Having worked in the wireless industry since University (almost 10 years now), I'm no stranger to attending conferences. They're a great way to meet potential customers, to gasbag with old acquaintances and to take the pulse of the entire industry.</p>
<p>The first conference I went to was entitled "The WAP Congress" and it was in the late nineties. It was in Cannes - a beautiful spot and a good opportunity for me to brush up on my increasingly rusty French. It was at this event it all became crystal clear to me that WAP was going nowhere. A whole stack of operators had predicated their business plans on internet levels of growth combined with a walled-garden style access policy. It was doomed, and it was pretty frustrating that people couldn't see the writing on the wall.</p>
<p>Flash forward seven years, and finally we're seeing the unravelling of this plan. 3 announced a flat-rate internet tariff for their subscribers a month or so ago (with their "X-Series" phones) and this has generated real buzz in the industry. Could we actually start to see internet-style growth in data use? I sure hope so.</p>
<p>But, as usual, I digress.</p>
<p>The issue with conference attendance, particularly the big ones, is that companies are generally fairly strict with their budgets. It's widely seen as a bit of a jolly and while it's fine for the senior execs and the sales guys to go along, engineering staff are rarely allowed out to see what all the fuss is about - and this is widely despised. Engineers like a good jolly as much as the next person and to deprive them of this is a trifle short-sighted. I find that travelling brings out the creative juices and leaving the laptop behind is a great opportunity to think hard about what is really happening in the business without the constant pressure of email, ringing desk phones and project deliveries.</p>
<p>Being a new company, and therefore having very little budget for marketing, what's a CEO to do when 3GSM, the industry's biggest and brashest event, comes knocking?</p>
<p>Well, after prevaricating for several months, I finally decided to bite the bullet and attend. But I was determined to attend on a budget, and in fact to do so for as close to £100 if possible. No company I have ever worked for managed to get anyone to 3GSM for less than about 10 times that amount so I have set myself quite a challenge.</p>
<p>So, how did I do it? Here's some inside information and tips on getting to 3GSM on an aggressive budget;</p>
<ol>
<li>Get a free ticket.</li>
<li>Fly cheap.</li>
<li>Sleep on the floor.</li>
<li>Eat twice a day.</li>
<li>Take the train.</li>
<li>Party hard.</li>
</ol>
<p><strong>1) Get a free ticket.</strong></p>
<p>It's a little known fact that with every display stand you buy at 3GSM comes a small pile of exhibition-only passes. These tickets retail for about €600, obviously a budget-buster. For every company who has a stand, big or small, the number of tickets you get generally means that you're going to have a stack left over.</p>
<p>Responsible organisations give these away to people. After all, it spreads goodwill and they're yours anyway to do with what you want. They invite friends from other companies as guests. Unfortunately, I discovered that several very large companies in the wireless space sat on a very large ticket allocation and didn't give them away. This is bad for everyone and stops people who can't afford to go to 3GSM from attending.</p>
<p>Thanks to the guys at <a href="http://www.d2see.com/">d2see</a> I got a free exhibition pass which I was most pleased with. They did the right thing and gave them away on the Mobile Monday London mailing list which was brilliant. Without those guys I would have probably been unable to attend - so thanks!</p>
<p><strong>2) Fly cheap.</strong></p>
<p>It seems that every year when talking to people about 3GSM there are rumours about how every flight is booked out and that it's impossible to get to Barcelona without having a rich uncle or too much venture capital. Frankly, this seems to me to be slightly...exaggerated.</p>
<p>About 3-4 weeks before 3GSM I just booked my flight on Ryanair to Barcelona Girona airport (about 1 1/4 hours northwest of Barcelona). It set me back all of £67, including a bag and all taxes. People were claiming that it was just not possible, but I think many were going to Barcelona airport and were therefore paying a slightly insane premium. So go a little further out of Barcelona and you will find it is cheap and it is possible.</p>
<p><strong>3) Sleep on the floor.</strong></p>
<p>Given the size of the event, it's pretty unlikely that you aren't going to know at least someone out there who's still working for a big outfit and who's paying above the odds for a large room. So beg. Ask around. Find someone you know who is staying in a hotel and sleep on the floor.</p>
<p>It's pretty rare to find a hotel room (outside of Japan) that doesn't have enough floor space for an additional body. So make the most of those rooms, and if you're lucky you might even find that one of your friends is in a twin room with an extra bed! But even if you can't, sleep in a hostel. Even on the busiest days of 3GSM the hostels were all empty. Do you really need a hotel room?</p>
<p>I packed a sleeping bag and a Thermarest and a compatriot kindly volunteered a patch of carpet for me. Yeah, it wasn't exactly the most comfortable experience but it worked and I survived. Rough it!</p>
<p><strong>4) Eat twice a day.</strong></p>
<p>3GSM has plenty of food stands inside, but these all look pricey and a bit measly with their portions. I decided instead of blowing my budget buying sub-standard food I would spend my euros somewhere else.</p>
<p>In the centre of La Rambla in downtown Barcelona is "Cafe de L'Opera", a nice little joint which serves cooked breakfast a l'Angleterre. Although I ended up with a cold frankfurter on my plate, which was a little bizarre, for 12 euros I had a huge breakfast with double-sized orange juice and a cappucino. Not pretty, and a little lacking in carbs, but it will see you through the day.</p>
<p>It also has the side benefit that you make the most of your time in 3GSM by visiting stands while the crowds go and get lunch. You can always find your way to stands where people are serving up food as part of the attraction (I had afternoon tea courtesy of Yahoo! who were graciously giving out ice cream at their stand) but frankly 3 meals a day is a bit of a luxury anyway.</p>
<p>Eat hearty, eat twice.</p>
<p><strong>5) Take the train.</strong></p>
<p>In their infinite wisdom, the team organising 3GSM ensured that everyone was issued with public transport passes for their entire time at Barcelona. And guess what? Every single day, 5pm rolled around and the queue for taxis out the front of the event was completely crazy. Why?</p>
<p>Well, because all these people think they are too important to catch the train.</p>
<p>That may seem a bit harsh, some people may have been staying in locations where the public transport network didn't reach, but frankly I think that's pretty unlikely. The Barca residents mostly seem to make do with the train and bus, so frankly, why shouldn't you?</p>
<p>I used it for all my transport (except to and from the airport, which at €21 took a substantial portion of my budget) and frankly suffered no long-term damage. The Barcelona trains are generally clean, quiet and unlike the tube there is great mobile coverage throughout. So lose the attitude and take the train.</p>
<p><strong>6) Party hard.</strong></p>
<p>Finally, remember that 3GSM has some great parties going. Try to secure invitations to networking events and parties before you go and you'll probably get food and drink served to you there. It keeps you to your budget and it's additional networking time.</p>
<p>Being one of the keenest BREW supporters in Europe outside of Qualcomm, we were invited to the BREW and MediaFlo bash while at Barcelona. It was very cool to see all of the Qualcomm bigwigs there, including Paul Jacobs, the CEO. They're a really cool and down-to-earth crowd.</p>
<p>But there were probably 10-20 other parties every night. TI had one, Nokia had one, Motorola had one. There was a "Swedish Beers" event which I unfortunately missed on Tuesday night. There was a "BeatBrew" event (free beer!) I also missed on Wednesday night.</p>
<p>Get yourself invited to some parties and you will find that the hospitality will keep you from blowing that budget!</p>
<p><strong>Some final thoughts</strong></p>
<p>The key to doing these things is thinking creatively. Every year, countless backpackers manage to crisscross the globe and survive on practically nothing. Why aren't you thinking the same when it comes to growing your business and marketing your company?</p>
<p>The level of waste at some of these events is a little embarrassing, and by taking advantage of some of this you can ensure that you have a great time, network with the greats and keep to a miniscule budget.</p>
<p>Total spent for 3 days at 3GSM, including flights, meals and transport? : £104.50.</p>On PPTP and T-Mobile Hotspots2007-02-08T20:54:00+00:002007-02-08T20:54:00+00:00Nick Clareytag:airsource.co.uk,2007-02-08:/blog/2007/02/08/on-pptp-and-t-mobile-hotspots/<p>Being a small company just starting to get our feet off the ground, we (or rather, I) have to do all of our own IT management.</p>
<p>Amusingly, after I completed my degree at University I had to think long and hard about whether to go into Software or into IT. It was quite a hard decision - fortunately a wise gentleman who I was working with suggested that Software was definitely the place to be. I took his advice but I keep coming back to IT. But anyway, I digress.</p>
<p>Being a bit of a boffin, I like our IT system to be polished. So much so that even though we are small I have been working to set up a PPTP based VPN system to serve us when we are out and about. It's pretty straightforward; a Debian Linux box running pptpd (PoPToP) behind an ADSL firewall. <p>Being a small company just starting to get our feet off the ground, we (or rather, I) have to do all of our own IT management.</p>
<p>Amusingly, after I completed my degree at University I had to think long and hard about whether to go into Software or into IT. It was quite a hard decision - fortunately a wise gentleman who I was working with suggested that Software was definitely the place to be. I took his advice but I keep coming back to IT. But anyway, I digress.</p>
<p>Being a bit of a boffin, I like our IT system to be polished. So much so that even though we are small I have been working to set up a PPTP based VPN system to serve us when we are out and about. It's pretty straightforward; a Debian Linux box running pptpd (PoPToP) behind an ADSL firewall. </p>
<p>Imagine our collective consternation when our VPN setup works fine and dandy when I try to use it from my house to connect into our office but when we try to use it from a T-Mobile Hotspot - no dice! Snarl. Try a different hotspot. Still no dice. Queue much scratching of heads.</p>
<p>Symptoms - able to connect to the VPN but as soon as I start passing large amounts of traffic over the connection, splat.</p>
<p>After much jiggery-pokery, and some sessions with tcpdump, what do I find? Ethereal reporting some funny truncated packets. Very odd. A bit more googling and people reporting similar problems in other contexts.</p>
<p>Turns out that the problem is related to MTU and MRU settings on the PPP link. A flick of the wrist in /etc/ppp/pptp-options;</p>
<div class="highlight"><pre><span></span><code>#<span class="w"> </span><span class="nv">This</span><span class="w"> </span><span class="nv">might</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="nv">the</span><span class="w"> </span><span class="nv">trick</span>
<span class="nv">mru</span><span class="w"> </span><span class="mi">542</span>
<span class="nv">mtu</span><span class="w"> </span><span class="mi">576</span>
</code></pre></div>
<p>A mad scramble to the nearest Starbucks - and lo! - a happy VPN connection. Heavenly choirs sound. Several weeks of fiddling has finally paid off. Rejoice.</p>A searching look2007-02-07T10:20:00+00:002007-02-07T10:20:00+00:00Nick Clareytag:airsource.co.uk,2007-02-07:/blog/2007/02/07/a-searching-look/<p>A fascinating Mobile Monday on Feb 5th was hosted by the BBC at their White City headquarters in London. It's a huge glass and steel testament to the effectiveness of one of the UK's best known and best loved exports - part fancy office block and part uber-new media mecca. <p>A fascinating Mobile Monday on Feb 5th was hosted by the BBC at their White City headquarters in London. It's a huge glass and steel testament to the effectiveness of one of the UK's best known and best loved exports - part fancy office block and part uber-new media mecca. </p>
<p>Arrived a little early in the hope of finding somewhere to eat but had no idea what was about, unfortunately, so just had to use my eyes and my nose to locate somewhere to have dinner before the evening's events began. Starbucks to the rescue, conveniently located in the shopping-mall-like ground floor leading into the guts of the building network, and a somewhat nutrition-free meal followed.</p>
<p>Given that the topic of the evening's event was mobile search, it got me thinking, over my toasted panini and grande caramel macchiato - what difference would an effective means of mobile search had on my London outing?</p>
<p>20 minutes or so earlier, sitting on the Central Line tube winding my way through central London a hundred feet or so underground I had a bit of a craving for noodles. I have never been to White City before and so had no idea what was there and what was nearby. Being out of coverage would also have limited my ability to just open up my phone on my way over there and download details of my travel over the network. The only thing that would have saved me there would have been;</p>
<ul>
<li>A calendar synchronised to my handset with the location information in the calendar entry "up to date"</li>
<li>A software component on the handset that, at least 12 hours before the meeting, would look through my diary, see what was coming up and download a collection of relevant information regarding the location I was travelling to.</li>
</ul>
<p>The pieces for this are very much available now, but there is definitely some wizardry required to link it all together. The hard bits are;</p>
<ul>
<li>Disambiguating a "location" from a diary entry. Often times the location entry I put in my diary is just a vague prompt to me rather than something a computer could reasonably work out. On Monday night it was much more precise, so in this context at least, it would have worked out ok.</li>
<li>Defining "relevant information". I suppose a starting point would have been seeing the time of the meeting and working out that the meeting was around dinnertime and therefore it was likely I'd need food before it. Together with that would probably have been noticing the end of the meeting and working out that I was going to either need somewhere to stay in London or a means to get home from where I was.</li>
</ul>
<p>Clearly one of the main things to be downloaded as part of the relevant information is a map of the area - just one showing immediate environs (say 1km radius). Had this been available, the rest of the information downloaded could have come with coordinates to allow them to be displayed on the map.</p>
<p>Had this been on my handset, my tube trip would not have been spent looking at my reflection in the window, but instead I could have been able to see some recommendations (with associated locations) of good places to eat in the area. Maybe I would have been able to find those noodles I'd been craving...</p>Write Once, Run Anywhere?2007-01-29T10:26:00+00:002007-01-29T10:26:00+00:00Ben Blaukopftag:airsource.co.uk,2007-01-29:/blog/2007/01/29/write-once-run-anywhere/<p>Spent a couple of days this week chasing down a crash on a pre-release BREW 3.1.5 handset.</p>
<p>The handset rebooted when I exited my application, which was, err, not ideal. It turned out that the crash was caused by releasing (and thereby destroying) an <code>IROOTFORM</code> while handling <code>EVT_APP_EXIT</code>. Remove that line (and leak the object) and the crash went away. Clean up a whole bunch of other objects? Still no crash.<p>Spent a couple of days this week chasing down a crash on a pre-release BREW 3.1.5 handset.</p>
<p>The handset rebooted when I exited my application, which was, err, not ideal. It turned out that the crash was caused by releasing (and thereby destroying) an <code>IROOTFORM</code> while handling <code>EVT_APP_EXIT</code>. Remove that line (and leak the object) and the crash went away. Clean up a whole bunch of other objects? Still no crash.</p>
<p>This was starting to look seriously like some weird bug in my code - I mean how can releasing an <code>IROOTFORM</code> (but not, say, an <code>IFORM</code>) cause a crash?? So I wrote a 20 line application that replicated the bug and reliably crashed the handset. OK, now it was starting to look like the handset was at fault. I got someone to review my code, tested it on some other handsets, scratched my head, and decided that the OEM must have messed something up.</p>
<p>One of the problems with working on mobile phone platforms (especially pre-release, though unfortunately well-sold commercial handsets are not immune) is that the OEM is working to a tight timescale, and when it comes to the crunch, QA on the Java Engine/BREW implementation is one of the first things to go. Of course, by and large problems aren't the OEM's fault, and when chasing bugs it's important to remember this; many times we've been sitting at our desks swearing at the OEM (or even QUALCOMM) only to discover that the bug is staring us in the face in CodeWeWroteLateLastNight.c.</p>
<p>My test app is now with QUALCOMM who will pass it onto the OEM. I can only hope that my days of heartache will result in a fix ;-)</p>