<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>fill the void</title>
	<atom:link href="http://www.bdunagan.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.bdunagan.com</link>
	<description></description>
	<lastBuildDate>Sat, 28 Aug 2010 06:42:22 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>WordPress Tip: Debug Logging</title>
		<link>http://www.bdunagan.com/2010/08/27/wordpress-tip-debug-logging/</link>
		<comments>http://www.bdunagan.com/2010/08/27/wordpress-tip-debug-logging/#comments</comments>
		<pubDate>Sat, 28 Aug 2010 06:42:22 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=1036</guid>
		<description><![CDATA[Today, I needed a debug log for WordPress. Luckily, the WordPress Codex details just a couple lines of code to turn on error logging. This method is especially nice for hosted environments like DreamHost, where there&#8217;s no access to php.ini. Simply add the following to wp-config.php: # http://codex.wordpress.org/Editing_wp-config.php#Debug define('WP_DEBUG', true); # http://codex.wordpress.org/Editing_wp-config.php#Configure_Error_Log @ini_set('log_errors','On'); @ini_set('display_errors','Off'); @ini_set('error_log',dirname(__FILE__).'/logs/php_error.log');]]></description>
			<content:encoded><![CDATA[<p>Today, I needed a debug log for WordPress. Luckily, the <a href="http://codex.wordpress.org/Editing_wp-config.php">WordPress Codex</a> details just a couple lines of code to turn on error logging. This method is especially nice for hosted environments like DreamHost, where there&#8217;s no access to <tt>php.ini</tt>. Simply add the following to <tt>wp-config.php</tt>:</p>
<pre class="brush: php;">
# http://codex.wordpress.org/Editing_wp-config.php#Debug
define('WP_DEBUG', true);
# http://codex.wordpress.org/Editing_wp-config.php#Configure_Error_Log
@ini_set('log_errors','On');
@ini_set('display_errors','Off');
@ini_set('error_log',dirname(__FILE__).'/logs/php_error.log');
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/08/27/wordpress-tip-debug-logging/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Dedupe Files with 50 Lines of Ruby</title>
		<link>http://www.bdunagan.com/2010/08/24/dedupe-files-with-50-lines-of-ruby/</link>
		<comments>http://www.bdunagan.com/2010/08/24/dedupe-files-with-50-lines-of-ruby/#comments</comments>
		<pubDate>Wed, 25 Aug 2010 03:54:18 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=1022</guid>
		<description><![CDATA[Somehow, my iPhoto library contains duplicates. Lots of duplicates. I tried Brattoo Propaganda&#8217;s Duplicate Annihilator, but while it took care of many, many photos, there are some photos and quite a few movies duplicated. Ruby to the rescue! I wrote a 50-line Ruby script to list out the duplicate files. The script uses SQLite3 and [...]]]></description>
			<content:encoded><![CDATA[<p>Somehow, my iPhoto library contains duplicates. Lots of duplicates. I tried <a href="http://brattoo.com/propaganda/">Brattoo Propaganda&#8217;s Duplicate Annihilator</a>, but while it took care of many, many photos, there are some photos and quite a few movies duplicated. Ruby to the rescue!</p>
<p>I wrote a 50-line Ruby script to list out the duplicate files. The script uses SQLite3 and SHA-1 digests to identify multiple copies of each file in a directory. The end result was 2K duplicate files and 20 GB of freed disk space. Woot.</p>
<p>Here&#8217;s the script:</p>
<pre class="brush: ruby;">
#!/usr/bin/ruby

# deduplicate_files.rb

require 'rubygems'
require 'sqlite3' # Look at 'http://github.com/luislavena/sqlite3-ruby' then do 'sudo gem install sqlite3-ruby'
require 'digest/sha1'
require 'pathname'

# Pass in the directory or assume the current one.
arg = ARGV[0] || &quot;.&quot;
root_path = Pathname.new(arg).realpath.to_s
puts &quot;Examining #{root_path}&quot;

# Create a SQLite3 database in the current directory.
db = SQLite3::Database.new(&quot;deduplicate_files.db&quot;)
db.execute(&quot;create table files(digest varchar(40),path varchar(1024))&quot;)

# Recursively generate hash digests of all files.
Dir.chdir(&quot;#{root_path}&quot;)
current_file = 0
Dir['**/*.*'].each do |file|
  path = &quot;#{root_path}/#{file}&quot;
  # Ignore non-existent files (symbolic links) and directories.
  next if !File.exists?(&quot;#{path}&quot;) || File.directory?(&quot;#{path}&quot;)
  # Create a hash digest for the current file.
  digest = Digest::SHA1.new
  File.open(file, 'r') do |handle|
    while buffer = handle.read(1024)
      digest &lt;&lt; buffer
    end
  end
  # Store the hash digest and full path in the database.
  db.execute(&quot;insert into files values(\&quot;#{digest}\&quot;,\&quot;#{path}\&quot;)&quot;)
  # Print out every Nth file.
  puts &quot;[#{digest}] #{path} (#{current_file})&quot; if current_file % 100 == 0
  current_file = current_file + 1
end

# Loop through digests.
db.execute(&quot;select digest,count(1) as count from files group by digest order by count desc&quot;).each do |row|
  if row[1] &gt; 1 # Skip unique files.
    puts &quot;Duplicates found:&quot;
    digest = row[0]
    # List the duplicate files.
    db.execute(&quot;select digest,path from files where digest='#{digest}'&quot;).each do |dup_row|
      puts &quot;[#{digest}] #{dup_row[1]}&quot;
    end
  end
end
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/08/24/dedupe-files-with-50-lines-of-ruby/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cocoa Tip: NO vs nil for Preferences</title>
		<link>http://www.bdunagan.com/2010/08/17/cocoa-tip-no-vs-nil-for-preferences/</link>
		<comments>http://www.bdunagan.com/2010/08/17/cocoa-tip-no-vs-nil-for-preferences/#comments</comments>
		<pubDate>Wed, 18 Aug 2010 05:25:18 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=1004</guid>
		<description><![CDATA[Recently, I wanted to store a key/value pair in Info.plist or in preferences (NSUserDefaults) and know if the key existed and the value if it did. I started by using boolForKey, but this method doesn&#8217;t distinguish between NO and nil. Instead, I switched to valueForKey. Here are six lines of code to detect the three [...]]]></description>
			<content:encoded><![CDATA[<p>Recently, I wanted to store a key/value pair in <tt>Info.plist</tt> or in preferences (<tt>NSUserDefaults</tt>) and know if the key existed and the value if it did. I started by using <tt>boolForKey</tt>, but this method doesn&#8217;t distinguish between <tt>NO</tt> and <tt>nil</tt>. Instead, I switched to <tt>valueForKey</tt>. Here are six lines of code to detect the three values:</p>
<pre class="brush: objc;">
NSDictionary *infoPlistDictionary = [[NSBundle mainBundle] infoDictionary];
BOOL doesKeyExistInPlist = [infoPlistDictionary valueForKey:PLIST_KEY] != nil;
BOOL isKeyTrueInPlist = [[infoPlistDictionary valueForKey:PLIST_KEY] boolValue] != NO;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL doesKeyExistInPrefs = [defaults valueForKey:PLIST_KEY] != nil;
BOOL isKeyTrueInPrefs = [[defaults valueForKey:PLIST_KEY] boolValue] != NO;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/08/17/cocoa-tip-no-vs-nil-for-preferences/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Cocoa Tip: URLs in NSTextFields</title>
		<link>http://www.bdunagan.com/2010/07/10/cocoa-tip-urls-in-nstextfields/</link>
		<comments>http://www.bdunagan.com/2010/07/10/cocoa-tip-urls-in-nstextfields/#comments</comments>
		<pubDate>Sun, 11 Jul 2010 02:31:19 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=997</guid>
		<description><![CDATA[Apple has a great Technical Q&#038;A document for embedding a URL in an NSTextField. However, after implementing their approach, I found two issues: Font: When I clicked the link, the URL&#8217;s font changed. To keep it consistent, I needed to set it myself. I added the following code to Apple&#8217;s NSAttributedString category from the link [...]]]></description>
			<content:encoded><![CDATA[<p>Apple has a great Technical Q&#038;A document for <a href="http://developer.apple.com/mac/library/qa/qa2006/qa1487.html">embedding a URL in an NSTextField</a>. However, after implementing their approach, I found two issues:</p>
<p><b>Font:</b> When I clicked the link, the URL&#8217;s font changed. To keep it consistent, I needed to set it myself. I added the following code to Apple&#8217;s <tt>NSAttributedString</tt> category from the link above:</p>
<pre class="brush: objc;">
[attrString addAttribute:NSFontAttributeName value:[NSFont fontWithName:@&quot;Lucida Grande&quot; size:13] range:range];
</pre>
<p><b>Cursor:</b> With Apple&#8217;s code, the pointing hand cursor only appeared after I clicked the link. To get the correct cursor before the link is clicked, I followed the advice from a cocoa-dev post and <a href="http://www.cocoabuilder.com/archive/cocoa/79376-pointinghandcursor-solved.html">subclassed <tt>NSTextField</tt> to override <tt>resetCursorRects</tt></a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/07/10/cocoa-tip-urls-in-nstextfields/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Don&#8217;t make users feel bad</title>
		<link>http://www.bdunagan.com/2010/06/15/dont-make-users-feel-bad/</link>
		<comments>http://www.bdunagan.com/2010/06/15/dont-make-users-feel-bad/#comments</comments>
		<pubDate>Wed, 16 Jun 2010 03:27:51 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[design]]></category>
		<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=983</guid>
		<description><![CDATA[Matt Drance posted an interesting thought the other day about the average consumer rejecting bad user experience. When helping out my parents or other non-technical folk, I&#8217;ve never found that. At least, not exactly. Average consumers do not think about user experience. They don&#8217;t think about workflows, calls to action, marketing copy, or corner cases. [...]]]></description>
			<content:encoded><![CDATA[<p>Matt Drance posted an interesting thought the other day about <a href="http://www.appleoutsider.com/2010/05/23/features-dont-matter-anymore/">the average consumer rejecting bad user experience</a>. When helping out my parents or other non-technical folk, I&#8217;ve never found that. At least, not exactly. Average consumers do not think about user experience. They don&#8217;t think about workflows, calls to action, marketing copy, or corner cases. Consumers just use stuff. When something goes wrong, it&#8217;s their fault. They internalize any problems. They blame themselves.</p>
<p>When consumers have a great user experience, they don&#8217;t notice. They don&#8217;t wonder how you designed the product so well. They just use it and move on, but that&#8217;s a good thing. They shouldn&#8217;t feel bad while using your product. Don&#8217;t make users feel bad because eventually they&#8217;ll choose a better user experience as a coping mechanism.</p>
<p>User experience: Because your users internalize your product&#8217;s failures.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/06/15/dont-make-users-feel-bad/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>A Mac App Store</title>
		<link>http://www.bdunagan.com/2010/05/26/a-mac-app-store/</link>
		<comments>http://www.bdunagan.com/2010/05/26/a-mac-app-store/#comments</comments>
		<pubDate>Thu, 27 May 2010 01:40:50 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[ideas]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=934</guid>
		<description><![CDATA[Billions of app downloads through a mobile application store was not a certainty or even speculated. Apple had no idea the App Store was going to be such a runaway success. By an account on Daring Fireball, there was a heated debate simply over the phone&#8217;s OS: Mac OS X or something else. And even [...]]]></description>
			<content:encoded><![CDATA[<p>Billions of app downloads through a mobile application store was not a certainty or even speculated. Apple had no idea the App Store was going to be such a runaway success. By <a href="http://daringfireball.net/2008/11/executive_scuttlebutt">an account on Daring Fireball</a>, there was a heated debate simply over the phone&#8217;s OS: Mac OS X or something else. And even after CocoaTouch was built, Apple didn&#8217;t ship the App Store and iPhone SDK for a full year. The popularity of third-party apps was not guaranteed.</p>
<p>But then Apple launched the App Store, and everyone saw just how popular it was. The App Store transformed the device from an outstanding Apple product, like Apple TV, into a revolutionary mobile device. Apple&#8217;s interest was sealed at that point. Think about <a href="http://www.apple.com/pr/library/2004/jan/06macosx.html">Apple&#8217;s 2004 Macworld press release</a>, three years after it launched Mac OS X: &#8220;Mac OS X Users Approach 10 Million. More Than 10,000 Native Applications Now Available.&#8221; Compare that to <a href="http://www.apple.com/pr/library/2010/04/08iphoneos.html">Apple&#8217;s recent iPhone 4.0 press release</a>, three years after it launched the iPhone: 85M iPhone OS devices, 185K apps, 4B downloads.</p>
<p>However, the Mac lacks a comparable experience. We&#8217;ve seen <a href="http://daringfireball.net/2009/09/how_should_mac_apps_be_distributed">how much confusion</a> the Mac&#8217;s current installation process causes. DMGs and zipped app bundles are better than Windows applications&#8217; arbitrary installer workflows, but still, non-technical people simply do not understand what&#8217;s going on. Which is fine, they <i>shouldn&#8217;t need</i> to know about the man behind the curtain. They&#8217;re just <a href="http://www.readwriteweb.com/archives/facebook_wants_to_be_your_one_true_login.php">normal people</a>.</p>
<p>The computer industry has been blind to their needs for decades, and experiences like the App Store are revolutionary improvements. The App Store proves that there is a huge pent-up demand for software that&#8217;s easily purchased and installed. At no point in using the App Store does someone wonder how to find an app, how to install it, where the Downloads folder is, or what a DMG is. The abstraction of the App Store is complete.</p>
<p>Of course Apple is thinking about a Mac App Store. Bringing the App Store&#8217;s distribution model, payment model, and user experience to Mac OS X is a natural progression. And Apple&#8217;s next OS upgrade, 10.7, seems like a fitting place to debut a Mac App Store. It won&#8217;t lock out all other software, like some suggest. Why bother? The Mac platform is already established. The concerns over battery life and content quality are moot. They&#8217;re just layering on an additional layer of simplicity, much like Mac OS X layered a beautiful user interface and tool set on top of BSD Unix. Valve&#8217;s Steam is an App Store for games. Facebook is an App Store for friends. Again, it&#8217;s all about abstraction.</p>
<p>And from a business perspective, it&#8217;s all about sales. Hardware sales. What drives sales? Killer apps. Apps that convince people to buy the hardware. What drives iPhone OS sales? The App Store. It delivers killer apps. It&#8217;s a meta-killer app. A Mac App Store would surface the best Mac apps through iTunes to millions of people, who use either a Mac or a PC to sync their iPod or iPhone or iPad. It would provide potential customers thousands of reasons to purchase a Mac.</p>
<p>Consider the present alternatives. Apple Downloads lists around 6K apps. People can find it from menu bar under &#8220;Mac OS X Software&#8230;&#8221; and, until recently, the Downloads link on Apple.com. The site is available but not blatantly obvious. Beyond that controlled environment, there are <a href="http://mac.softpedia.com">Softpedia</a> (72K apps), <a href="http://www.versiontracker.com/macosx/">VersionTracker</a> (23K apps), and <a href="http://macupdate.com/">MacUpdate</a>. Their goals are ad revenue, not user experience; hence why my <a href="http://www.bdunagan.com/2010/03/31/multicast-ping-on-a-mac/">Multicast Ping</a> was automagically added to <a href="http://mac.softpedia.com/get/Network-Admin/Multicast-Ping.shtml">Softpedia</a>. And there is the open web. Sure, potential customers can search around to fill their needs or find apps by word of mouth. But even ignoring the search issues, the installation workflow is far from ideal. Finally, there is <a href="http://appbodega.com/">Bodega</a>, the third-party Mac app trying to solve this very problem and doing a really good job at it. Their software is clean and inviting, and their 100K+ downloads support the theory that there is a need. Nonetheless, Bodega doesn&#8217;t drive Mac sales. Apple needs its own Mac App Store to do that.</p>
<p>Yes, people have <a href="http://lowendmac.com/musings/08mm/mac-app-store.html">speculated about a Mac App Store</a> since shortly after the iPhone App Store debuted, and the idea has seen <a href="http://9to5mac.com/99-mac-app-store-coming-3546093465">renewed gossip</a> since Apple converted ADC to a flat $99/year membership, removed the Downloads link from their site&#8217;s header, and <a href="http://mashable.com/2010/04/25/mac-app-store/">explicitly said it will never be</a>. I remember when Jobs said <a href="http://www.engadget.com/2004/04/29/steve-jobs-says-it-again-no-video-ipod/">no video iPods</a>.</p>
<p>So that&#8217;s my prediction: Mac App Store through iTunes in 10.7. But let&#8217;s say I&#8217;m wrong. Apple decides not to extend its billion-download idea onto the Mac. What does that say about Apple&#8217;s interest in the Mac? I&#8217;d guess <a href="http://www.antipope.org/charlie/blog-static/2010/04/why-steve-jobs-hates-flash.html">Charlie Stross is right</a>, and Apple views the Mac as a dying platform.</p>
<p>I doubt that, though.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/05/26/a-mac-app-store/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>class-dump: Dumping Classes</title>
		<link>http://www.bdunagan.com/2010/05/17/class-dump-dumping-classes/</link>
		<comments>http://www.bdunagan.com/2010/05/17/class-dump-dumping-classes/#comments</comments>
		<pubDate>Tue, 18 May 2010 04:10:57 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=860</guid>
		<description><![CDATA[class-dump path_to_executable If the tool nm and its wealth of information seems impressive, class-dump is amazing. Feed it an executable and get back a complete list of class names, internal variables, and methods, even as a set of header files, thanks to its deep understanding of Objective-C and Mach-O. Apple&#8217;s product might be stripped of [...]]]></description>
			<content:encoded><![CDATA[<p><tt>class-dump path_to_executable</tt></p>
<p>If the tool <tt>nm</tt> and its <a href="http://www.bdunagan.com/2010/05/14/symbolification-shipping-symbols/">wealth of information</a> seems impressive, <a href="http://www.codethecode.com/projects/class-dump/"><tt>class-dump</tt></a> is amazing. Feed it an executable and get back a complete list of class names, internal variables, and methods, even as a set of header files, thanks to its deep understanding of Objective-C and Mach-O. Apple&#8217;s product might be stripped of their symbols, but <tt>class-dump</tt> can list their internal structure.</p>
<p>I created a template application, added a method (<tt>shouldShowInClassDump</tt>), and built it with all the symbols stripped out. Running <tt>class-dump</tt> on the resulting Release executable revealed the following:</p>
<pre class="brush: objc;">
/*
 *     Generated by class-dump 3.3.1 (64 bit).
 *
 *     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2009 by Steve Nygard.
 */

#pragma mark -

/*
 * File: build/Release/SampleApp.app/Contents/MacOS/SampleApp
 * Arch: Intel x86-64 (x86_64)
 *
 *       Objective-C Garbage Collection: Unsupported
 */

@interface SampleAppAppDelegate : NSObject
{
    NSWindow *window;
}

- (void)applicationDidFinishLaunching:(id)arg1;
- (BOOL)shouldShowInClassDump:(id)arg1;
@property NSWindow *window; // @synthesize window;

@end
</pre>
<p>To see how applications compared in aggregate, I wrote a simple ruby script to walk through <tt>/Applications</tt> and sum up the number of lines of headers from <tt>class-dump</tt> and the number of <tt>@interface</tt> declarations. Both are crude measures of complexity.</p>
<p><i>Top 10 by Header Lines</i></p>
<pre class="brush: plain;">
37968	iPhoto
37053	Aperture
21228	iWeb
19307	Keynote
16564	iMovie
16235	Coda
15625	Keynote
15111	Grapher
14735	Pages
14724	iCal
</pre>
<p><i>Top 10 by Interfaces</i></p>
<pre class="brush: plain;">
1203	Aperture
1066	iWeb
1004	Grapher
 967	iPhoto
 859	Path Finder
 748	Keynote
 730	iMovie
 664	LaunchBar
 649	iCal
 646	Coda
</pre>
<p><i>Top 10 by Lines Per Interface</i></p>
<pre class="brush: plain;">
39.54	Retrospect
39.33	Grab
39.26	iPhoto
37.72	MemoryMiner
37.28	Papers
35.72	iTunes
35.65	Sequel Pro
34.71	Console
32.72	iChat
32.38	GarageBand
</pre>
<p>Apple&#8217;s tools are clearly complex, with iPhoto and Aperture leading by almost a factor of two in header lines, but apps like Panic&#8217;s Coda hold their own. And Retrospect topping the final list is obviously an indictment of my coding abilities.</p>
<p><i>Ruby Script</i></p>
<pre class="brush: ruby;">
#!/usr/local/bin/ruby19

# analyze_apps.rb
# * Analyze dump from 'class-dump path_to_executable' to collect statistics: Interfaces, Lines

# Collect app paths.
CLASS_DUMP_PATH = &quot;/usr/local/bin/class-dump&quot;
apps = []
apps_folders = [&quot;/Applications&quot;, &quot;/Applications/iWork '08&quot;, &quot;/Applications/iWork '09&quot;, &quot;/Applications/Microsoft Office 2004&quot;, &quot;/Applications/Microsoft Office 2008&quot;, &quot;/Applications/Utilities&quot;]
apps_folders.each do |app_folder|
  app_list = Dir.entries(app_folder)
  app_list.each do |app|
    apps &lt;&lt; &quot;#{app_folder}/#{app}&quot;
  end
end

# Loop through apps.
apps.each do |app|
  # Check that the app exists.
  if Dir.exists?(&quot;#{app}&quot;) &amp;&amp; app.include?(&quot;.app&quot;)
    app_array = app.split(&quot;/&quot;)
    app_name = app_array[app_array.count-1].split(&quot;.app&quot;)[0]
    exe_path = &quot;#{app}/Contents/MacOS/#{app_name}&quot;
    # Check that the executable exists.
    if File.exists?(exe_path)
      # Use 'classdump' to dump Mach-O header information.
      results = %x[#{CLASS_DUMP_PATH} &quot;#{exe_path}&quot;].split(&quot;\n&quot;)
      line_count = results.count
      interface_count = 0
      results.each do |line|
        if line.include?(&quot;@interface &quot;)
          interface_count = interface_count + 1
          # puts &quot;#{app_name}: #{line}&quot;
        end
      end
      puts &quot;#{app_name},#{app},#{interface_count},#{line_count}&quot;
    end
  end
end
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/05/17/class-dump-dumping-classes/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Symbolification: Shipping Symbols</title>
		<link>http://www.bdunagan.com/2010/05/15/symbolification-shipping-symbols/</link>
		<comments>http://www.bdunagan.com/2010/05/15/symbolification-shipping-symbols/#comments</comments>
		<pubDate>Sat, 15 May 2010 21:50:41 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=924</guid>
		<description><![CDATA[nm -a path_to_executable When Mac applications are compiled, Xcode has a setting to include or exclude debugging symbols: the &#8220;Strip Style&#8221;. When set to fully strip, the bundled executable (AppName.app/Contents/MacOS/AppName) contains very few symbols, but when all the symbols are included, the executable is a wealth of information. Unfortunately, Xcode&#8217;s default Release configuration includes all [...]]]></description>
			<content:encoded><![CDATA[<p><tt>nm -a path_to_executable</tt></p>
<p>When Mac applications are compiled, Xcode has a setting to include or exclude debugging symbols: the <a href="http://developer.apple.com/mac/library/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW96">&#8220;Strip Style&#8221;</a>. When set to fully strip, the bundled executable (<tt>AppName.app/Contents/MacOS/AppName</tt>) contains very few symbols, but when all the symbols are included, the executable is a wealth of information. Unfortunately, Xcode&#8217;s default Release configuration includes <i>all</i> the symbols, leading to shipping applications with lots of symbol information, including method names and even source files. These are accessible with <a href="http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man1/nm.1.html"><tt>nm</tt></a>. Let&#8217;s see what we find for Retrospect Client (from my company):</p>
<p><i>Method Names</i></p>
<pre class="brush: plain;">
...
0000431d t -[PrefController awakeFromNib]
0000431d - 01 001f   FUN -[PrefController awakeFromNib]:f(0,1)
...
</pre>
<p><i>Source Files</i></p>
<pre class="brush: plain;">
...
0000276e - 01 0032    SO /Volumes/Leopard/projects/trunk/retrospect/client/mac/cpgui/AboutPanel.m
00004963 - 01 0032    SO /Volumes/Leopard/projects/trunk/retrospect/client/mac/cpgui/PasswordController.m
...
</pre>
<p>While interesting in isolation, the information is richer in aggregate. With a quick ruby script, we can rank the apps in <tt>/Applications</tt> by various metrics:</p>
<p><i>Top 10 by Methods</i></p>
<pre class="brush: plain;">
8857	Coda
8036	Evernote
7660	Cornerstone
6312	Pages
6054	Jing
5100	Papers
4805	Delicious Library 2
4589	Socialite
4563	Retrospect
4048	CSSEdit
</pre>
<p><i>Top 10 by Source Files</i></p>
<pre class="brush: plain;">
  304	Delicious Library 2
  295	LittleSnapper
  257	Socialite
  240	Evernote
  192	NetNewsWire
  158	Microsoft Document Connection
  144	Tweetie
  115	Retrospect
  105	Clipstart
   87	Transmit
</pre>
<p>Apple&#8217;s products are not in either list because they strip out the symbols for every major application. However, they apparently missed App Store&#8217;s Application Loader 1.1 (72), codenamed StarGazer:</p>
<pre class="brush: plain;">
...
00000000 - 00 0000    SO /Users/jfosback/jingle/iTMSTransporter/branches/iTMSTransporter-1.4/Applications/Stargazer/Source/ITunesPurpleSoftwareUploadDocumentController.m
...
</pre>
<p><b>Xcode Settings</b><br />
Over half of the apps in my <tt>/Applications</tt> had no symbol information. The developers stripped it out, as described by <a href="http://developer.apple.com/tools/xcode/symbolizingcrashdumps.html">Apple&#8217;s documentation on symbolizing crash dumps</a>.</p>
<p>As I mentioned earlier, Apple&#8217;s default Release configuration does not strip out symbols. In fact, it leaves them all in, regardless of what &#8220;Strip Style&#8221; (<tt>STRIP_STYLE</tt>) is set to. The culprit is &#8220;Deployment Postprocessing&#8221; (<tt>DEPLOYMENT_POSTPROCESSING</tt>). When not enabled, Xcode ignores the setting for &#8220;Strip Style&#8221;. Apple describes this &#8220;Deployment Postprocessing&#8221; option in <a href="http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/XcodeBuildSystem/400-Build_Configurations/build_configs.html#//apple_ref/doc/uid/TP40002692-BBCFBCJA">Xcode Build System Guide: Build Configuration</a> and further in <a href="http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/XcodeProjectManagement/210-Building_Products/building.html#//apple_ref/doc/uid/TP40002693-SW101">Xcode Project Management Guide: Building Products</a>.</p>
<p><img src="/files/Xcode_Deployment_Postprocessing.png"/></p>
<p>I wrote a quick sample application in Xcode and used the default Release configuration to generate the following dumps from <tt>nm</tt>:</p>
<p><i>No symbols are stripped (Xcode&#8217;s default Release configuration)</i></p>
<pre class="brush: plain;">
# Deployment Postprocessing: false
# Strip Style: any
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
0000000000000011 - 01 0000 ENSYM
0000000000000011 - 00 0000   FUN
0000000000000011 - 00 0000   FUN
00000001000018fd - 01 0000 BNSYM
0000000000000000 - 00 0000    SO
00000001000018dc - 01 0000 BNSYM
000000000000000a - 00 0000   FUN
000000000000000a - 01 0000 ENSYM
0000000000000000 - 01 0000    SO
0000000000000000 - 00 0000    SO
0000000000000000 - 01 0000    SO
0000000000000011 - 01 0000 ENSYM
00000001000018e6 - 01 0000 BNSYM
0000000000000006 - 00 0000   FUN
0000000000000006 - 01 0000 ENSYM
00000001000018ec - 01 0000 BNSYM
0000000100001e8b s  stub helpers
00000001000018e6 t -[SampleAppAppDelegate applicationDidFinishLaunching:]
00000001000018e6 - 01 0000   FUN -[SampleAppAppDelegate applicationDidFinishLaunching:]
00000001000018fd t -[SampleAppAppDelegate setWindow:]
00000001000018fd - 01 0000   FUN -[SampleAppAppDelegate setWindow:]
00000001000018ec - 01 0000   FUN -[SampleAppAppDelegate window]
00000001000018ec t -[SampleAppAppDelegate window]
0000000000000000 - 00 0000    SO /Users/bdunagan/Desktop/SampleApp/SampleAppAppDelegate.m
000000004be395c1 - 00 0001   OSO /Users/bdunagan/Desktop/SampleApp/build/SampleApp.build/Release/SampleApp.build/Objects-normal/x86_64/SampleAppAppDelegate.o
000000004be395c1 - 00 0001   OSO /Users/bdunagan/Desktop/SampleApp/build/SampleApp.build/Release/SampleApp.build/Objects-normal/x86_64/main.o
0000000000000000 - 00 0000    SO /Users/bdunagan/Desktop/SampleApp/main.m
                 U _NSApplicationMain
0000000100002660 D _NXArgc
0000000100002668 D _NXArgv
                 U _OBJC_CLASS_$_NSObject
0000000100002070 - 0a 0000 STSYM _OBJC_CLASS_$_SampleAppAppDelegate
0000000100002070 s _OBJC_CLASS_$_SampleAppAppDelegate
00000001000021c8 s _OBJC_IVAR_$_SampleAppAppDelegate.window
00000001000021c8 - 0b 0000 STSYM _OBJC_IVAR_$_SampleAppAppDelegate.window
                 U _OBJC_METACLASS_$_NSObject
0000000100002048 s _OBJC_METACLASS_$_SampleAppAppDelegate
0000000100002048 - 0a 0000 STSYM _OBJC_METACLASS_$_SampleAppAppDelegate
0000000100002678 D ___progname
0000000100000000 A __mh_execute_header
                 U __objc_empty_cache
                 U __objc_empty_vtable
0000000100002670 D _environ
                 U _exit
00000001000018dc t _main
00000001000018dc - 01 0000   FUN _main
                 U dyld_stub_binder
00000001000018a0 T start
</pre>
<p><i>Debugging symbols are stripped</i></p>
<pre class="brush: plain;">
# Deployment Postprocessing: true
# Strip Style: Debugging Symbols (STRIP_STYLE=debugging)
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
0000000100001e8b s  stub helpers
00000001000018e6 t -[SampleAppAppDelegate applicationDidFinishLaunching:]
00000001000018fd t -[SampleAppAppDelegate setWindow:]
00000001000018ec t -[SampleAppAppDelegate window]
                 U _NSApplicationMain
0000000100002660 D _NXArgc
0000000100002668 D _NXArgv
                 U _OBJC_CLASS_$_NSObject
0000000100002070 s _OBJC_CLASS_$_SampleAppAppDelegate
00000001000021c8 s _OBJC_IVAR_$_SampleAppAppDelegate.window
                 U _OBJC_METACLASS_$_NSObject
0000000100002048 s _OBJC_METACLASS_$_SampleAppAppDelegate
0000000100002678 D ___progname
0000000100000000 A __mh_execute_header
                 U __objc_empty_cache
                 U __objc_empty_vtable
0000000100002670 D _environ
                 U _exit
00000001000018dc t _main
                 U dyld_stub_binder
00000001000018a0 T start
</pre>
<p><i>Non-global symbols are stripped</i></p>
<pre class="brush: plain;">
# Deployment Postprocessing: true
# Strip Style: Non-Global Symbols (STRIP_STYLE=non-global)
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
                 U _NSApplicationMain
0000000100002660 D _NXArgc
0000000100002668 D _NXArgv
                 U _OBJC_CLASS_$_NSObject
                 U _OBJC_METACLASS_$_NSObject
0000000100002678 D ___progname
0000000100000000 A __mh_execute_header
                 U __objc_empty_cache
                 U __objc_empty_vtable
0000000100002670 D _environ
                 U _exit
                 U dyld_stub_binder
0000000005614542 - 00 0000   OPT radr://5614542
00000001000018a0 T start
</pre>
<p><i>All Symbols are stripped</i></p>
<pre class="brush: plain;">
# Deployment Postprocessing: true
# Strip Style: All Symbols (STRIP_STYLE=all) (default)
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
                 U _NSApplicationMain
                 U _OBJC_CLASS_$_NSObject
                 U _OBJC_METACLASS_$_NSObject
0000000100000000 A __mh_execute_header
                 U __objc_empty_cache
                 U __objc_empty_vtable
                 U _exit
                 U dyld_stub_binder
0000000005614542 - 00 0000   OPT radr://5614542
</pre>
<p><b>Ruby Script</b><br />
To generate the statistics for the apps in <tt>/Applications</tt>, I wrote a short ruby script:</p>
<pre class="brush: ruby;">
#!/usr/local/bin/ruby19

# analyze_apps.rb
# * Analyze dump from 'nm -a path_to_executable' to collect statistics: Name, Path, Lines, Archs, Files, Class Methods, Instance Methods

# Collect app paths.
apps = []
apps_folders = [&quot;/Applications&quot;, &quot;/Applications/iWork '08&quot;, &quot;/Applications/iWork '09&quot;, &quot;/Applications/Microsoft Office 2004&quot;, &quot;/Applications/Microsoft Office 2008&quot;, &quot;/Applications/Utilities&quot;]
apps_folders.each do |app_folder|
  app_list = Dir.entries(app_folder)
  app_list.each do |app|
    apps &lt;&lt; &quot;#{app_folder}/#{app}&quot;
  end
end

# Include source code?
PRINT_SOURCE_CODE = false

# Identify columns of data.
puts &quot;Name, Path, Lines, Archs, Files, Class Methods, Instance Methods&quot;

# Loop through apps.
apps.each do |app|
  # Check that the app exists.
  if Dir.exists?(&quot;#{app}&quot;) &amp;&amp; app.include?(&quot;.app&quot;)
    app_array = app.split(&quot;/&quot;)
    app_name = app_array[app_array.count-1].split(&quot;.app&quot;)[0]
    exe_path = &quot;#{app}/Contents/MacOS/#{app_name}&quot;
    # Check that the executable exists.
    if File.exists?(exe_path)
      # Use 'nm -a path_to_executable' to dump symbol information.
      results = %x[nm -a &quot;#{exe_path}&quot;]

      # Get number of architectures supported. All other information is duplicated per architecture.
      arch_count = results.scan(&quot;(for architecture &quot;).count
      arch_count = 1 if arch_count == 0

      # Get the number of lines, class methods, and instance methods per architecture.
      line_count = results.scan(&quot;\n&quot;).count / arch_count
      class_methods = results.scan(&quot; t +[&quot;).count / arch_count
      instance_methods = results.scan(&quot; t -[&quot;).count / arch_count

      # Loop through the results to more accurately count the source files (though not perfectly).
      source_lookup = []
      source_count = 0
      results.split(&quot;\n&quot;).each do |line|
        # Check that the line references &quot;SO&quot; or &quot;SOL&quot; but isn't repeated.
        if line.include?(&quot;SO &quot;) &amp;&amp; line.split(&quot;SO &quot;).count &gt; 1 &amp;&amp; line.split(&quot;SO &quot;)[1].include?(&quot;.m&quot;) &amp;&amp; !source_lookup.include?(line.split(&quot;SO &quot;)[1])
          file = line.split(&quot;SO &quot;)[1]
          # Add file name to lookup.
          source_lookup &lt;&lt; file
          # Increment counter.
          source_count = source_count + 1
          # Print out file name.
          puts &quot;#{app_name}: #{file}&quot; if PRINT_SOURCE_CODE
        elsif line.include?(&quot;SOL &quot;) &amp;&amp; line.split(&quot;SOL &quot;).count &gt; 1 &amp;&amp; line.split(&quot;SOL &quot;)[1].include?(&quot;.m&quot;) &amp;&amp; !source_lookup.include?(line.split(&quot;SOL &quot;)[1])
          file = line.split(&quot;SOL &quot;)[1]
          # Add file name to lookup.
          source_lookup &lt;&lt; file
          # Increment counter.
          source_count = source_count + 1
          # Print out file name.
          puts &quot;#{app_name}: #{file}&quot; if PRINT_SOURCE_CODE
        end
      end

      # Print out the application's stats.
      puts &quot;#{app_name}, #{app}, #{line_count}, #{arch_count}, #{source_count}, #{class_methods}, #{instance_methods}&quot;
    end
  end
end
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/05/15/symbolification-shipping-symbols/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Measuring Design Changes</title>
		<link>http://www.bdunagan.com/2010/05/12/measuring-design-changes/</link>
		<comments>http://www.bdunagan.com/2010/05/12/measuring-design-changes/#comments</comments>
		<pubDate>Thu, 13 May 2010 02:38:44 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[design]]></category>
		<category><![CDATA[marketing]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=867</guid>
		<description><![CDATA[A week ago, I replaced the Flickr photos in my blog&#8217;s header with iPhone OS apps: Retrospect Touch and Dollar Clock. Since I hadn&#8217;t updated Flickr since Christmas, the iPhone/iPad apps seemed more appropriate, and I was curious to see if the switch affected the download numbers. Here&#8217;s the before and after: Today, I checked [...]]]></description>
			<content:encoded><![CDATA[<p>A week ago, I replaced the Flickr photos in my blog&#8217;s header with iPhone OS apps: Retrospect Touch and Dollar Clock. Since I hadn&#8217;t updated Flickr since Christmas, the iPhone/iPad apps seemed more appropriate, and I was curious to see if the switch affected the download numbers. Here&#8217;s the before and after:</p>
<p><img src="/files/blog_header_change.png"/></p>
<p>Today, I checked the numbers in iTunes Connect for Dollar Clock. At first, I only looked at the total downloads, in the left graph, where the hash mark notes the header change. Clearly, the header change increased the downloads. Then I looked at the purchases and updates separately, in the right graph, and the pattern was less clear. During the week after the header change (the bar after the hash mark), more of the downloads were updates, not purchases.</p>
<p><img src="/files/Dollar_Clock_Purchases.png"/></p>
<p>During the same week I changed the header, Dollar Clock 2.0 was posted to the App Store, and many people downloaded the update that week. The total downloads indicates my header change had a significant effect, but the breakdown reveals confounding variables. Perhaps if I had more variables, I would find that the header change had absolutely no effect.</p>
<p>Or maybe I just wanted an excuse to use Tufte&#8217;s <a href="http://en.wikipedia.org/wiki/Small_multiple">small multiples. <img src='http://www.bdunagan.com/blog/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/05/12/measuring-design-changes/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Dollar Clock on the iPad: Meeting Waste in HD</title>
		<link>http://www.bdunagan.com/2010/04/28/dollar-clock-on-the-ipad-meeting-waste-in-hd/</link>
		<comments>http://www.bdunagan.com/2010/04/28/dollar-clock-on-the-ipad-meeting-waste-in-hd/#comments</comments>
		<pubDate>Thu, 29 Apr 2010 03:20:49 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=854</guid>
		<description><![CDATA[Dollar Clock 2.0 got accepted yesterday, complete with landscape mode and iPad support. Now you can track meeting waste in HD!]]></description>
			<content:encoded><![CDATA[<p><a href="http://itunes.com/apps/dollarclock">Dollar Clock 2.0</a> got accepted yesterday, complete with landscape mode and iPad support. Now you can track meeting waste in HD!</p>
<p><img src="/files/DollarClock_iPad_main_portrait_small.jpg" style="padding: 2px;"/><br />
<img src="/files/DollarClock_iPad_main_landscape_small.jpg" style="padding: 2px;"/><br />
<img src="/files/DollarClock_iPad_settings_portrait_small.jpg" style="padding: 2px;"/><br />
<img src="/files/DollarClock_iPad_tweet_portrait_small.jpg" style="padding: 2px;"/></p>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/04/28/dollar-clock-on-the-ipad-meeting-waste-in-hd/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
