Code Workshop2022-06-07T00:19:41-06:00Matt Stevenshttp://codeworkshop.net/feedXcode Build Setting Transformationstag:codeworkshop.net,2015-08-08:14390598132015-08-08T12:50:13-06:002015-08-17T15:55:22-06:00Matt Stevens<p>Xcode supports the ability to substitute the value of build settings using the <code>$(BUILD_SETTING_NAME)</code> or <code>${BUILD_SETTING_NAME}</code> syntax in a number of places including Info.plists, other build setting values, and .xcconfig files. In these substitutions it’s also possible to add operators that transform the value in various ways. You may have seen one of these in the Info.plist included in project templates:</p>
<pre><code>com.company.$(PRODUCT_NAME:rfc1034identifier)
</code></pre>
<p>This results in the value of the <code>PRODUCT_NAME</code> build setting being transformed into a representation suitable for use in the reverse DNS format used by <code>CFBundleIdentifier</code>. So if <code>PRODUCT_NAME</code> is “Whatever App” the resulting string is “com.company.Whatever-App”.</p>
<p>These transformations can be quite useful but don’t appear to be documented, so here’s the list of available operators and what they do:</p>
<table>
<tr><th align="left">Operator</th><th align="left">Returns</th></tr>
<tr><td>identifier</td><td>A C identifier representation suitable for use in source code.</td></tr>
<tr><td>c99extidentifier</td><td>Like <code>identifier</code>, but with support for extended characters allowed by C99. Added in Xcode 6.</td></tr>
<tr><td>rfc1034identifier</td><td>A representation suitable for use in a DNS name.</td></tr>
<tr><td>quote</td><td>A representation suitable for use as a shell argument.</td></tr>
<tr><td>lower</td><td>A lowercase representation.</td></tr>
<tr><td>upper</td><td>An uppercase representation.</td></tr>
<tr><td>standardizepath</td><td>The equivalent of calling <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/index.html#//apple_ref/occ/instm/NSString/stringByStandardizingPath">stringByStandardizingPath</a> on the string.</td></tr>
<tr><td>base</td><td>The base name of a path - the last path component with any extension removed.</td></tr>
<tr><td>dir</td><td>The directory portion of a path.</td></tr>
<tr><td>file</td><td>The file portion of a path.</td></tr>
<tr><td>suffix</td><td>The extension of a path including the '.' divider.</td></tr>
</table>
<p>Note that these operators can be chained, so you can do things like <code>$(PRODUCT_NAME:lower:rfc1034identifier)</code> or <code>$(CONFIGURATION:upper:identifier)</code>.</p>
An Objective-C API Diff Tooltag:codeworkshop.net,2014-06-17:14030547932014-06-17T19:26:33-06:002014-06-17T19:26:33-06:00Matt Stevens<p>For the last year most of my work has been in Objective-C framework development. With each release I would look through a diff of the headers since the previous release and make note of the API modifications so that users of the framework could easily see what had changed. While not terribly time consuming it was tedious and required some care, especially when declarations were relocated. Having previously contributed to the <a href="https://github.com/landonf/objectdoc">ObjectDoc</a> project’s Objective-C wrapper for libclang, this seemed like a good opportunity to make use of that work and put together a tool to automate the diff generation. The result is <a href="http://codeworkshop.net/objc-diff/">objc-diff</a>, a libclang-based tool to generate an Objective-C API diff report.</p>
<p>The reports generated by the tool (<a href="http://codeworkshop.net/objc-diff/example.html">example</a>) are similar to those provided by Apple for the system frameworks, with a couple of improvements:</p>
<ul>
<li><p>When a class or protocol is moved to a different header a relocation is reported only for that entity rather than it and all of its methods and properties. The relocation of the children is implied and this eliminates a lot of noise from the report.</p></li>
<li><p>Conversions between explicit accessor methods and <code>@property</code> declarations are reported as modifications to the declaration rather than a series of additions and removals. This more accurately describes the change and makes the actual additions and removals easier to identify.</p></li>
</ul>
<p>Binary releases are available on the <a href="http://codeworkshop.net/objc-diff/">project page</a>, and the source is available on <a href="https://github.com/mattstevens/objc-diff">GitHub</a>.</p>
Clean File Templates for Xcode 4tag:codeworkshop.net,2013-02-01:13597300622013-02-01T07:47:42-07:002013-02-01T07:47:42-07:00Matt Stevens<p>When creating new classes through Xcode I tend to delete just about everything provided in the default file templates. The information in the comment block headers is better tracked by a source control system and I often don’t want the default method implementations. To support this I’ve created a set of file templates that include nothing but an <code>#import</code> appropriate for the base class and an empty <code>@interface</code> and <code>@implementation</code>.</p>
<p>To install these templates create ~/Library/Developer/Xcode/Templates/File Templates if it doesn’t already exist and clone the <a href="https://github.com/mattstevens/xcode-clean-file-templates">GitHub repo</a> into a sub-directory name of your choice (I use “Clean”). This directory name will appear as a category in Xcode’s new file dialog. Once installed you can use these instead of the normal Cocoa / Cocoa Touch templates and enjoy one less step when adding new classes to your project.</p>
Disabling Xcode 4.6's Garbage Collection Warningtag:codeworkshop.net,2013-01-28:13594260972013-01-28T19:21:37-07:002014-03-10T20:22:40-06:00Matt Stevens<p>Xcode 4.6 generates a Target Integrity warning if the build setting <code>GCC_ENABLE_OBJC_GCC</code> is set to a value other than “unsupported”, stating that garbage collection is deprecated and encouraging you to migrate to ARC. However, for targets like System Preference panes garbage collection support is still required so GC is enabled for a good reason. In such cases this warning is annoying and there is no obvious way to turn it off. But it turns out that with a little build setting manipulation it is possible.</p>
<p>Set the default value of <code>GCC_ENABLE_OBJC_GCC</code> to “unsupported”, then expand the build setting and for each configuration add a per-architecture setting with a value of “supported”. If you’re using GC for multiple architectures do this for each supported architecture, as using “Any Architecture” will still cause Xcode to complain. You should end up with something like this:</p>
<center><img class="bordered" src="http://codeworkshop.net/images/xcode46-gc-setting.png" width="368" height="107" alt="Updated Garbage Collection Build Setting"></center>
<p>This silences the warning and allows you to continue to build with GC support, at least for now.</p>
<p><strong>Update</strong>: This works in Xcode 4.6 through 5.0.2. Support for garbage collection was removed in Xcode 5.1.</p>
Power Nap and the Networktag:codeworkshop.net,2012-10-18:13505955342012-10-18T15:25:34-06:002012-10-18T15:25:34-06:00Matt Stevens<p>With the introduction of <a href="http://support.apple.com/kb/HT5394">Power Nap</a> developers have something new to think about: applications running while the system appears to be asleep. Every hour Power Nap enabled systems partially wake up and any running applications will briefly execute with disk and network access. In most cases this works just fine, but for some apps it can result in behavior that the user does not expect. For example, consider apps like instant messaging and IRC clients that advertise the presence of a person or service. Every hour they will reconnect to their networks and make it appear as if the user is available when they really aren’t. Such reconnections can also cause downright annoying behavior like the AIM multiple sign-in warning, improper redirection of incoming messages, or an offline IRC buffer being sent to the wrong device.</p>
<p>For apps like these the behavior is likely caused by the application attempting to reconnect in response to a closed connection or low-level network state notifications. Previously this worked as expected because the reconnection code did not run until the user woke up the system, but under Power Nap it is running while the system is still asleep from the user’s point of view. Apps that run into trouble here will need to suspend their reconnect behavior until the system is fully awake, and fortunately this is pretty easy to do. The partial wake state used by Power Nap is known as Dark Wake, and while in this state the <code>NSWorkspaceDidWakeNotification</code> and <code>SCNetworkReachability</code> callbacks are not sent. As a result a simple solution is to watch for the <code>NSWorkspaceWillSleepNotification</code> and disable reconnects or network state observers until an <code>NSWorkspaceDidWakeNotification</code> is received. Alternatively an application can adopt the <code>SCNetworkReachability</code> APIs if possible and rely on those callbacks to inform the app when it is appropriate to reconnect.</p>
<p>If you want to test your changes without waiting an hour for Power Nap to kick in an easy way to trigger Dark Wake for a few seconds is to put the system to sleep then connect or disconnect AC power or a peripheral. This is also useful for testing a direct transition from Dark Wake to full wake by connecting power then pressing a key to wake up the system.</p>
Notification Center Updates for Homebrewtag:codeworkshop.net,2012-07-25:13432543402012-07-25T16:12:20-06:002012-08-21T14:12:34-06:00Matt Stevens
<center><img class="bordered" src="http://codeworkshop.net/images/notification-center-homebrew@2x.png" width="341" height="115" alt="Notification Center Homebrew Update"></center>
<p>I’ve modified my <a href="http://codeworkshop.net/posts/growl-notifications-for-homebrew">Homebrew update notification script</a> to use Notification Center thanks to <a href="https://github.com/alloy/terminal-notifier">terminal-notifier</a>, a growlnotify replacement that allows posting to Notification Center from the command line. The script now looks like this:</p>
<pre><code>export PATH=/usr/local/bin:$PATH
notifier=/Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier
# Give the network a chance to connect
sleep 10
brew update > /dev/null 2>&1
outdated=`brew outdated | sort | tr '\n' ' '`
if [ ! -z "$outdated" ]; then
$notifier \
-group net.codeworkshop.homebrewupdate \
-title "Homebrew Updates Available" \
-message "$outdated" \
-activate com.apple.Terminal > /dev/null 2>&1
fi
</code></pre>
<p>The launch agent remains the same, checking for updates every day at 6:00 p.m. or the next time the Mac wakes from sleep. To install, first download terminal-notifier and place it in your Applications folder. Then download the <a href="http://codeworkshop.net/files/homebrewupdate-nc.zip">script files</a> and move homebrewupdate into /usr/local/bin and net.codeworkshop.homebrewupdate.plist into ~/Library/LaunchAgents. Then load the launch agent and you should be set.</p>
<pre><code>launchctl load ~/Library/LaunchAgents/net.codeworkshop.homebrewupdate.plist
</code></pre>
<p>If you’re upgrading from the Growl version you only need to replace the homebrewupdate script. The launch agent works just as before.</p>
Switching to an Inexpensive Code Signing Certificatetag:codeworkshop.net,2012-07-19:13427016382012-07-19T06:40:38-06:002012-08-09T22:39:16-06:00Matt Stevens<p>For the last few years I’ve been using a code signing certificate from Thawte. At the time I just wanted to get my app out the door and some quick research turned up Thawte as a popular choice and a trusted certificate authority on every OS version I was targeting. The $275 a year fee always annoyed me a bit though and with the introduction of <a href="https://developer.apple.com/resources/developer-id/">Gatekeeper</a> meaning that I’ll only be using the certificate to silence scary warnings on Windows I decided to look into less expensive options.</p>
<p>The reason certificate prices vary so widely is a combination of reputation and OS support. By purchasing a certificate you are linking yourself to the certificate authority’s good name, so if they have a more widely known name supposedly that is better for you. In practice very few people care what CA your code signing certificate comes from as long as it works. OS support is also not likely to be an issue. CAs that have been around for a long time are trusted by default on more operating systems, with the big names going all the way back to Windows 95. There is a slim chance this matters to you for an SSL certificate, but for code signing you only care about the CA being trusted on the oldest OS your software supports. In my case that’s Windows XP and this opens the door to other options, namely Comodo and any of its resellers. Comodo’s direct price is $170 a year but from a reseller you can get that down to $90 a year or less.</p>
<p>After some searching I decided to buy from <a href="http://codesigning.ksoftware.net/">K Software</a>. There are slightly cheaper options out there but K Software has a great site, documentation to guide you through the whole process, and a lot of positive mentions on developer blogs. I pulled the trigger and after responding to a couple requests from Comodo I had my new cert a few hours later.</p>
<center><img src="http://codeworkshop.net/images/k-software-cert-chain@2x.png" width="450" height="549" alt="Certificate Chain for K Software Code Signing Certificate"></center>
<p>The certificate you receive comes directly from Comodo and doesn’t involve the reseller in the certificate chain in any way. This means that you can renew through any other reseller or directly from Comodo in the future, and anyone looking at the certificate won’t know who you actually bought it from.</p>
<p>To double-check OS compatibility I signed my app with the new certificate and tested it on Windows 7 and an up-to-date version of XP. Then I fired up a virtual machine running a clean install of XP from a 2002 retail disk. Without any updates or service packs applied the app’s signature verified just fine, and if it works there it will work anywhere.</p>
<p>I was initially on the fence about buying from a reseller because the price difference kept making me wonder what the catch was. But after looking into why the difference exists and seeing the results for myself I can’t see any reason why you wouldn’t go for the best deal that supports your oldest target OS. The next time you’re in the market for a code signing certificate I highly recommend giving the inexpensive options a look.</p>
Filtering Sparkle Release Notes with JavaScripttag:codeworkshop.net,2012-02-21:13298523592012-02-21T12:25:59-07:002012-08-09T22:39:21-06:00Matt Stevens<p>Up ‘til now I’ve been using the <code>description</code> field in my Sparkle appcast to display release notes. It’s easy and I like showing users just the notes that are relevant to the update. The downside to this approach is that if someone doesn’t run the app often they are likely to be a couple releases behind and not get the full picture. This is especially true if I’ve recently released a significant update. Such updates usually have the most important notes and are usually followed pretty quickly by a patch release. Unless the user runs the app before the patch release goes out they’ll miss the good bits.</p>
<p>There are a few ways to deal with this. The simplest is to use the <code>releaseNotesLink</code> field and just show release notes for all versions every time, leaving it up to the user to look at the version numbers and figure out what is relevant. Another option is to append a “new in $(last significant update)” section to the description HTML as Apple does. I thought about going this route, but remembered that in the apps I use sometimes it has been the smallest change that has had the most impact. If it was that One Thing that drove me nuts about an otherwise-great app that’s what I would be looking for every time.</p>
<p>The final approach I’ve seen is to pass the version range as parameters to the feed URL and build a custom appcast on the server. This offers the output I’m looking for but I wondered if I could do the same thing with a static page. With a little Sparkle hack it turned out to be possible and I’m pretty happy with the result.</p>
<center><img src="http://codeworkshop.net/images/signal-release-notes-filtered@2x.png" width="692" height="509" alt="Filtered Sparkle Release Notes"></center>
<p>To build this I took a page with <a href="http://www.alloysoft.com/download/signal/releasenotes_mac.html">full release note history</a> and wrapped every release in a <code>div</code> with its <code>id</code> set to the version number. These were grouped under a parent element with nothing else in it, then I put the following in a script tag:</p>
<pre><code>function getQueryParameter(name) {
// From http://james.padolsey.com/javascript/bujs-1-getparameterbyname/
var match = RegExp("[?&]" + name + "=([^&]*)").exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
}
function filterVersions() {
var currentVersion = getQueryParameter("currentVersion");
var updateVersion = getQueryParameter("updateVersion");
var updateVersionIndex = undefined;
var currentVersionIndex = undefined;
if (!updateVersion) {
updateVersionIndex = 0;
}
if (!currentVersion || currentVersion == updateVersion) {
return;
}
var releases = document.getElementById("releases").children;
var length = releases.length;
for (var i=0; i < length; i++) {
var elem = releases[i];
if (updateVersionIndex === undefined && elem.id == updateVersion) {
updateVersionIndex = i;
}
if (currentVersionIndex === undefined && elem.id == currentVersion) {
currentVersionIndex = i;
}
if (i >= currentVersionIndex || updateVersionIndex === undefined) {
releases[i].style.display = "none";
}
}
}
addEventListener("DOMContentLoaded", filterVersions);
</code></pre>
<p>Assuming my releases are in descending order by version this will hide everything that falls outside the range parameters included in the URL. The next step was to get Sparkle to include those parameters when requesting the release notes. I replaced the <code>description</code> field with a <code>releaseNotesLink</code> pointing to the new page, then used the following tweak in the app:</p>
<pre><code>- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update {
NSString *urlString = [[update releaseNotesURL] absoluteString];
urlString = [urlString stringByAppendingFormat:@"?currentVersion=%@&updateVersion=%@",
[[[updater hostBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"],
[update displayVersionString]];
[update setValue:[NSURL URLWithString:urlString] forKey:@"releaseNotesURL"];
}
</code></pre>
<p>There isn’t a public API to modify <code>releaseNotesURL</code> but there is a set method implemented so I used <code>setValue:forKey:</code> to invoke it and boom, release notes tailored to the updated version range without anything special running on the server.</p>
Silent Updatestag:codeworkshop.net,2012-01-23:13273347472012-01-23T09:05:47-07:002012-07-31T18:19:34-06:00Matt Stevens<p>Lately I’ve been thinking about software updates on the Mac, specifically patch releases. With just about every app that isn’t in the App Store adopting Sparkle I see that update prompt quite a bit, usually populated by release notes consisting of bug fixes and performance improvements. It’s fantastic that developers are improving the apps I use, but with updates like this I pretty much mindlessly click install, then wait while the update is downloaded and applied. Depending on the speed of my connection to the update server and the complexity of the install process I could be waiting a while.</p>
<p>Compare this with the experience of using Chrome or Dropbox. Both apps just install the latest version with little to no presentation to the user. Chrome shows you its Arrow of Judgement if you don’t restart the app for a while when an update is pending, but normally you don’t know anything has happened. Dropbox is able to restart itself and shows no UI at all. When using these apps you don’t have to think about software updates. You just use the app and benefit from the improvements.</p>
<p>For patch releases the silent update process strikes me as the better choice. Your apps improve with fewer update prompts and less thinking about software updates for everyone. I say patch releases because an across the board silent update seems like going too far for most desktop apps. The goal of a silent update process should be to refine the experience the user is familiar with. The UI and application behavior shouldn’t just change unannounced. Imagine if you were giving a presentation and launched an app to find that it had silently updated with a redesigned user interface. All the saved update prompts in the world aren’t going to make that up to you.</p>
<p>With this in mind I came up with some guidelines for how I would implement a silent update process:</p>
<ul>
<li>Automatic updates are enabled by default.</li>
<li>Automatic updates can be disabled.</li>
<li>There is a method to manually check for an update.</li>
<li>Updates that substantially change the application’s behavior or user interface require user approval.</li>
<li>Updates that interact with the user during installation require user approval.</li>
</ul>
<p>The ability to disable updates doesn’t need to be part of the UI, but it should be possible. If a user runs into an update that doesn’t work on their system they should have a way of going back to a previous version and staying there. Likewise, a manual update check doesn’t need to be obvious in the UI but should be there for users that follow your blog or Twitter feed. If they see that a new version has been released they should be able to use the software update process to get it.</p>
<p>I hope to implement something like this in my own apps in the near future. Besides making a small dent in the number of update prompts slung at users I think this will increase the number of updates I release. Many times I’ve made a small fix and then sat on it for weeks because it didn’t seem worthy of a release by itself. When such releases are invisible there’s no reason not to get it into everyone’s hands right away.</p>
<p><strong>Update</strong>: Work is in progress to implement this in Sparkle’s <a href="https://github.com/andymatuschak/Sparkle/tree/quiet-automatic-update">quiet-automatic-update</a> branch.</p>
Growl Notifications for Homebrewtag:codeworkshop.net,2011-08-25:13143092012011-08-25T15:53:21-06:002012-08-09T22:39:10-06:00Matt Stevens
<center><img class="bordered" src="http://codeworkshop.net/images/growl-homebrew@2x.png" width="341" height="115" alt="Growl Homebrew Update"></center>
<p>Here’s a simple script I use to update Homebrew and display a Growl notification if there are any outdated formula:</p>
<pre><code>export PATH=/usr/local/bin:$PATH
# Give the network a chance to connect
sleep 10
brew update > /dev/null 2>&1
outdated=`brew outdated`
if [ ! -z "$outdated" ]; then
growlnotify -t "Homebrew Updates Available" -m "$outdated"
fi
</code></pre>
<p>To use it you’ll also need <a href="http://growl.info/extras.php#growlnotify">growlnotify</a>:</p>
<pre><code>brew install growlnotify
</code></pre>
<p>Add the script to your scheduler of choice and you’ll have one less thing to obsess about keeping up-to-date. I use a launch agent to run the check every day at 6:00 p.m. While not as simple as cron, the nice thing about launchd is that if the computer is asleep during the scheduled time the task will be performed as soon as the computer wakes up. The script’s sleep call is to give the network a chance to connect in this case. Incidentally, if anyone knows of a better way to delay execution until the system is on the network I’d love to hear about it.</p>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.codeworkshop.homebrewupdate</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/homebrewupdate</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>18</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/dev/null</string>
<key>StandardErrorPath</key>
<string>/dev/null</string>
</dict>
</plist>
</code></pre>
<p>If you’d like to use my setup you can <a href="http://codeworkshop.net/files/homebrewupdate.zip">download these files</a> and move homebrewupdate into /usr/local/bin and net.codeworkshop.homebrewupdate.plist into ~/Library/LaunchAgents. Then load the launch agent and you should be set.</p>
<pre><code>launchctl load ~/Library/LaunchAgents/net.codeworkshop.homebrewupdate.plist
</code></pre>