<?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 &#187; development</title>
	<atom:link href="http://www.bdunagan.com/category/development/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>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>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>
		<item>
		<title>Cocoa Tip: Losing UITableView Selection</title>
		<link>http://www.bdunagan.com/2010/04/24/cocoa-tip-losing-uitableview-selection/</link>
		<comments>http://www.bdunagan.com/2010/04/24/cocoa-tip-losing-uitableview-selection/#comments</comments>
		<pubDate>Sat, 24 Apr 2010 17:52:30 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=846</guid>
		<description><![CDATA[In porting Dollar Clock from the iPhone to the iPad, I switched from using a flip view (using UIModalTransitionStyleFlipHorizontal) to a popover view (UIPopoverController). But a strange thing happened: the UITableView in the popover lost its initial selection, after viewDidAppear was called. Regardless of what I did, the row was always deselected, and indexPathForSelectedRow would [...]]]></description>
			<content:encoded><![CDATA[<p>In porting <a href="http://itunes.com/apps/dollarclock">Dollar Clock</a> from the iPhone to the iPad, I switched from using a flip view (using <tt>UIModalTransitionStyleFlipHorizontal</tt>) to a popover view (<tt>UIPopoverController</tt>). But a strange thing happened: the <tt>UITableView</tt> in the popover lost its initial selection, <i>after</i> <tt>viewDidAppear</tt> was called. Regardless of what I did, the row was always deselected, and <tt>indexPathForSelectedRow</tt> would go from returning the correct path to nil. Turns out I needed to <tt>reloadData</tt> before assigning it an initial selection.</p>
<pre class="brush: objc;">
- (void)viewDidAppear:(BOOL)animated {
	[preferencesListView reloadData]; // This call was necessary for the UITableView to keep its initial selection.
	[preferencesListView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:NO scrollPosition:UITableViewScrollPositionNone];
	[self updateInterface];
	[super viewDidAppear:animated];
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/04/24/cocoa-tip-losing-uitableview-selection/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Communicating with a Privileged Tool</title>
		<link>http://www.bdunagan.com/2010/04/18/communicating-with-a-privileged-tool/</link>
		<comments>http://www.bdunagan.com/2010/04/18/communicating-with-a-privileged-tool/#comments</comments>
		<pubDate>Sun, 18 Apr 2010 20:30:16 +0000</pubDate>
		<dc:creator>bdunagan</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.bdunagan.com/?p=833</guid>
		<description><![CDATA[In writing Multicast Ping for the Mac, I needed a two-way communication pipe between the interface (parent process) and the privileged tool (child process) and a clean method for terminating the tool through that pipe. NSTask provides setStandardInput and setStandardOutput for just this reason. However, I cannot launch a privileged tool with NSTask; instead, I [...]]]></description>
			<content:encoded><![CDATA[<p>In <a href="http://www.bdunagan.com/2010/03/31/multicast-ping-on-a-mac/">writing Multicast Ping</a> for the Mac, I needed a two-way communication pipe between the interface (parent process) and the privileged tool (child process) and a clean method for terminating the tool through that pipe. <a href="http://developer.apple.com/mac/library/documentation/cocoa/Reference/Foundation/Classes/NSTask_Class/Reference/Reference.html"><tt>NSTask</tt></a> provides <tt>setStandardInput</tt> and <tt>setStandardOutput</tt> for just this reason. However, I cannot launch a privileged tool with <tt>NSTask</tt>; instead, I have to use <a href="http://developer.apple.com/mac/library/documentation/Security/Reference/authorization_ref/Reference/reference.html#//apple_ref/c/func/AuthorizationExecuteWithPrivileges"><tt>AuthorizationExecuteWithPrivileges</tt></a> with its lower-level file handle mechanism.</p>
<p>With a bit of help, I implemented a two-way pipe at a high-level and utilize that pipe to cleanly terminate the child process with a simple EOF signal. <a href="http://developer.apple.com/mac/library/samplecode/BetterAuthorizationSample/Introduction/Intro.html#//apple_ref/doc/uid/DTS10004207-Intro-DontLinkElementID_2">Apple&#8217;s BetterAuthorizationSample sample project</a> touches on this communication pipe but does so at a low-level and only one-way. Matt Gallagher from Cocoa With Love also uses a pipe in <a href="http://cocoawithlove.com/2009/05/invoking-other-processes-in-cocoa.html">invoking other processes</a> when he creates an Open File Killer app, but the pipe is still only used one direction. Thanks to both those tutorials as well as a <a href="http://caiustheory.com/read-standard-input-using-objective-c">post from Caius Theory</a> for helping me along. See my <a href="http://github.com/bdunagan/multicast_ping">GitHub <tt>multicast_ping</tt> repo</a> for the code in context.</p>
<p><b>Interface: setup the pipe to the tool</b></p>
<pre class="brush: objc;">
// Execute the command with privileges from SFAuthorizationView.
FILE *handle;
OSErr processError = AuthorizationExecuteWithPrivileges([[authView authorization] authorizationRef], [helperPath UTF8String],
														kAuthorizationFlagDefaults, (char *const *)argv, &amp;handle);
free(argv);
if (processError != 0) {
	NSLog(@&quot;helper tool failed (%d)&quot;, processError);
	return NO;
}

// Setup the two-way pipe.
helperHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileno(handle)];
[helperHandle waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTaskOutput:) name:NSFileHandleDataAvailableNotification object:helperHandle];
</pre>
<p><b>Interface: wait for data from the tool</b></p>
<pre class="brush: objc;">
- (void)handleTaskOutput:(NSNotification *)notification {
	// Get the new data.
	NSFileHandle *handle = (NSFileHandle *)[notification object];
	NSData *data = [handle availableData];
	if ([data length] &gt; 0) {
		// Convert the data into a string.
		NSString *dataString = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding];
		if ([dataString isEqual:@&quot;port in use&quot;]) {
			// Tool failed.
			[self handleToolFailure];
			[dataString release];
			return;
		}

		// Process the string, expecting &quot;address,port,message&quot;.
		NSArray *array = [dataString componentsSeparatedByString:@&quot;,&quot;];
		[dataString release];
		if ([array count] == 3) {
			// Get attributes.
			NSString *address = [array objectAtIndex:0];
			int port = [[array objectAtIndex:1] intValue];
			NSString *message = [array objectAtIndex:2];

			// Create new message.
			Message *newMessage = [[Message alloc] initWithAddress:address andPort:port andMessage:message];
			[messages addObject:newMessage];
			[newMessage release];

			// Update view.
			[listView reloadData];
		}

		// Prepare for more data.
		[handle waitForDataInBackgroundAndNotify];
	}
	else {
		// No data means tool failed.
		[self handleToolFailure];
	}
}
</pre>
<p><b>Interface: terminate the tool</b></p>
<pre class="brush: objc;">
// Close pipe to terminate the helper tool.
[helperHandle closeFile];
helperHandle = nil;
</pre>
<p><b>Tool: send data to the interface</b></p>
<pre class="brush: objc;">
// Write out the new message to the pipe.
NSString *line = [NSString stringWithFormat:@&quot;%@,%d,%@&quot;, sentHost, sentPort, receivedMessage];
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
</pre>
<p><b>Tool: listen for terminate from the interface</b></p>
<pre class="brush: objc;">
// Wait for parent process to close the pipe. That will send the EOF signal.
[[NSFileHandle fileHandleWithStandardInput] readDataToEndOfFile];
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bdunagan.com/2010/04/18/communicating-with-a-privileged-tool/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
