Caius Theory

Now with even more cowbell…

+[NSObject load] in MacRuby

If you've not heard of it, MacRuby is an implementation of Ruby 1.9 directly on top of Mac OS X core technologies such as the Objective-C runtime and garbage collector, the LLVM compiler infrastructure and the Foundation and ICU frameworks. Basically means you write in Ruby using Objective-C frameworks, and vice versa. It's pretty damn cool to be honest!

What is +[NSObject load]?

From the documentation:

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

This means when your class is loaded, and implements the load method, you get a load message sent to your class. Which means you can start doing stuff as soon as your class is loaded by the runtime.

The main place I've seen it used (and used it myself) is in SIMBL plugins. A SIMBL plugin is an NSBundle that contains code which is loaded (injected) into a running application shortly after said application is launched. It lets you extend (or "fix") cocoa applications with additional features. So you have this bundle of code, that gets loaded into a running application some point after it starts, and you want to run some code as the bundle is loaded - usually to kick off doing whatever you want to do in the plugin. This is where load becomes useful.

Here's a quick implementation that just logs to the console:

    @implementation MainController
    
    + (void) load
    {
        NSlog(@"MainController#load called");
    }

Now where does MacRuby come into this?

Well I came across a need to do the same in ruby, have some code triggered when the class is loaded into the runtime. Tried implementing Class.load but to no avail. Then remembered MacRuby is just ruby! And I can call any code from within my ruby class definition.

For continuity I still call it Class.load, but then call it as soon as I've defined it in the class. Eg:

class MainController
  def self.load
    NSLog "MainController#load called"
  end

  self.load
end

Of course, I'm not sure when the Objective-C method is called, it's probably after the entire class has been defined rather than as soon as load has been loaded into the runtime. So you might want to move the self.load call to just before the closing end.

Read standard input using Objective-C

On a couple of occasions now I've wanted to read from STDIN into an Objective-C command line tool, and both times I've had to hunt quite a bit to find the answer because nothing shows up in google for the search terms I used. "Objective-c read from stdin" and "objc read stdin" both turn up results ranging from using NSInputStream to dropping some C++ in there.

The answer is quite simple really, just use NSFileHandle. More specifically +[NSFileHandle fileHandleWithStandardInput]. You can then read all data currently in STDIN, monitor it for new data and anything else you can do with a normal NSFileHandle.

And here's some example code, reads all data from STDIN and stores it into an NSString:

NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];
NSData *inputData = [NSData dataWithData:[input readDataToEndOfFile]];
NSString *inputString = [[NSString alloc]
  initWithData:inputData encoding:NSUTF8StringEncoding];

I'm using this in GarbageCollected apps, memory management without GC is left as an exercise to the user.

Safari 4 Hidden Preferences

Updated 2009-06-09: This post is for the Safari 4 beta and will not work with the new Safari 4 released yesterday at the WWDC keynote. I've had a look through that release and can't see any way to revert the address bar, etc sorry.


Having a quick poke through the new Safari binary yields the following strings:

$ strings /Applications/Safari.app/Contents/MacOS/Safari | grep DebugSafari4
DebugSafari4TabBarIsOnTop
DebugSafari4IncludeToolbarRedesign
DebugSafari4IncludeFancyURLCompletionList
DebugSafari4IncludeGoogleSuggest
DebugSafari4LoadProgressStyle
DebugSafari4IncludeFlowViewInBookmarksView
DebugSafari4TopSitesZoomToPageAnimationDimsSnapshot
DebugSafari4IncludeTopSites

NB: Run these commands in Terminal.app and then you need to restart Safari for them to take effect.

DebugSafari4TabBarIsOnTop

This moves the tab bar back where you expect it to be:

$ defaults write com.apple.Safari DebugSafari4TabBarIsOnTop -bool NO

DebugSafari4IncludeToolbarRedesign and DebugSafari4LoadProgressStyle

When both set to NO it restores the blue loading bar behind the URL. Also puts a page loading spinner in the tab itself, which looks odd with the new tabs.

$ defaults write com.apple.Safari DebugSafari4IncludeToolbarRedesign -bool NO
$ defaults write com.apple.Safari DebugSafari4LoadProgressStyle -bool NO

DebugSafari4IncludeFancyURLCompletionList

Switches off the new URL autocomplete menu and goes back to the original one.

$ defaults write com.apple.Safari DebugSafari4IncludeFancyURLCompletionList -bool NO

DebugSafari4IncludeGoogleSuggest

Turns off the new Google suggest menu.

$ defaults write com.apple.Safari DebugSafari4IncludeGoogleSuggest -bool NO

DebugSafari4IncludeFlowViewInBookmarksView

Removes CoverFlow from the Bookmarks view entirely. (Credit to Erik)

$ defaults write com.apple.Safari DebugSafari4IncludeFlowViewInBookmarksView -bool NO

DebugSafari4TopSitesZoomToPageAnimationDimsSnapshot

Disables the dimming when you click on a Top Site and it scales the screenshot up to fill the screen.

$ defaults write com.apple.Safari DebugSafari4TopSitesZoomToPageAnimationDimsSnapshot -bool NO

DebugSafari4IncludeTopSites

Disables Top Sites feature completely.

$ defaults write com.apple.Safari DebugSafari4IncludeTopSites -bool NO

Undoing changes

Just run the defaults command with the delete flag for the appropriate key you wish to delete.

$ defaults delete com.apple.Safari <key>

NB: Don't include the -bool NO at the end, it just requires the key (eg: "DebugSafari4IncludeGoogleSuggest")

Update 2009-02-26

Jools points out in the comments how to reset the recent searches in the google search box.

Update 2009-05-26

Lowell's kindly created a Mac OS X application to edit these settings without using Terminal. http://github.com/cocoastep/tweaky

Update 2010-11-18

Patric has kindly translated this post into Belorussian and posted it on his site.

Merry Testing

Just a few examples of the same test written in a few languages. Its testing setting the date on an object that is created in the tests' setup method already. These fall under the unit testing, rather than full-stack testing.

Testing in ObjC with OCUnit

// Add a date and time
- (void)testSettingDate
{    
    NSDate *theDate = [NSDate date];        
    
    STAssertNoThrow([calc setDate:theDate], @"Shouldn't raise an exception");
    // And it should match when pulled out as well
    STAssertEqualObjects([calc date], theDate,
                         @"%@ should match %@",
                         [calc date], theDate);

}

Testing in Ruby using RSpec

it "should set the date successfully" do
  the_date = Date.today

  @calc.date = the_date
  # And it should match when pulled out as well
  @calc.date.should == the_date
end

Testing in Ruby using Test::Unit

def test_setting_date
  the_date = Date.today
  @calc.date = the_date
  # And it should match when pulled out as well
  assert_equal(@calc.date, the_date)
end

Testing in PHP using PHPUnit

function testSettingDate()
{
    $date = date();
    $calc->date = $date;
    # And it should match when pulled out as well
    $this->assertEquals($calc->date, $date);
}