O'Reilly logo

MacRuby: The Definitive Guide by Matt Aimonetti

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Foundation

As stated in Chapter 3, Foundation defines the primitive object classes and data types used by all the other classes in Cocoa. It’s therefore the first stop in our examination of frameworks for Cocoa and MacRuby.

Compatibility Table

When it comes to primitive types, MacRuby developers often have a choice between Cocoa Foundation classes and native Ruby classes. Before going through the list of key classes, their purposes, and how to use them, it is important to understand the differences and relationships between Ruby primitives and Foundation primitives.

Table 4-1 shows classes that are implemented in such a way that the Ruby classes are compatible with their Foundation and Core Foundation counterparts.

Table 4-1. Ruby/Foundation/Core Foundation compatibility table

Ruby classCompatible Foundation classCompatible Core Foundation type

String

NSString/NSMutableString

CFString/CFMutableString

Array

NSArray/NSMutableArray

CFArray/CFMutableArray

Hash

NSDictionary/NSMutableDictionary

CFDictionary/CFMutableDictionary

Integer (Fixnum, Bignum)

NSNumber

CFNumber

Float

NSNumber

CFNumber

Time

NSDate

CFDate

Note

Even though the Exception and NSException classes are not compatible per se, Ruby’s syntax can rescue NSException instances, meaning that the developer can define a behavior in case a defined exception is caught.

This compatibility map is important because it means that even though a certain API might expect to receive an instance of NSArray, if you send it an instance of Array, everything will work as expected. Also, if an API returns an NSMutableDictionary instance, for example, the Hash instance methods can be used on the returned object. You can use any of the Hash or NSDictionary/NSMutableDictionary instance methods on the returned object.

However, some Ruby classes are not compatible with their Foundation equivalents and when using them in conjunction with other Cocoa libraries, one needs to be careful to use the appropriate class. Table 4-2 shows Ruby and Foundation classes that play similar roles but are not compatible.

Table 4-2. Incompatible types in Ruby and Cocoa frameworks

MacRuby classFoundation counterparts

Set

NSSet, NSMutableSet, NSCountedSet, NSHashTable

Enumerator

NSEnumerator, NSFastEnumerationEnumerator

Date

NSDate, NSCalendar

This means if an API expects an object of a certain type, you can’t provide it with a counterpart from the other column. For instance, if a Cocoa API expects an instance of NSDate, you can’t pass it a Ruby date object. What you might want to do in such a case is convert an object as shown in Date, Time, and Calendars.

Now that we have this reference frame, let’s look at the key classes defined by Foundation.

Strings and Attributed Strings

Cocoa’s Foundation string class is NSString. MacRuby’s String class is fully compatible with NSString/NSMutableString because NSString is “toll-free bridged” with its Core Foundation counterpart: CFString. In other words, whenever a method is expecting a String, NSString, or CFStringRef, you are free to use whichever class instance you want.

NSString and String have different APIs, but offer more or less the same features. Here are a few NSString methods that are not available in the traditional Ruby API but are quite useful nonetheless:

pathComponents

Returns each path component of a path represented as a string:

>> framework 'Foundation'
=> true
>> "/Developer/Examples/Ruby/MacRuby".pathComponents
=> ["/", "Developer", "Examples", "Ruby", "MacRuby"]
pathExtension

Returns the extension of a path represented as a string:

>> framework 'Foundation'
=> true
file_path = "~/Music/born_to_be_alive.m4a"
file_path.pathExtension
# => "m4a"
# similar to Ruby's
File.extname(path)
# => ".m4a"

Attributed string objects, represented in Cocoa by NSAttributedString, manage sets of text attributes, such as font and kerning, that are associated with strings. NSAttributedString is not a subclass of NSString, but it does contain an NSString object to which it applies attributes. NSAttributedString supports HTML, Rich Text Format (including file attachments and graphics), drawing in NSView objects, and word and line calculation.

The NSAttributedString instance attributes are set/retrieved via a Hash:

>> framework 'Foundation'
=> true
font = NSFont.fontWithName("Helvetica", size:14.0)
content = "So Long, and Thanks for All the Fish"
attributes = {NSFontAttributeName => font}
attr_string = NSAttributedString.alloc.initWithString(content, attributes: attributes)
attr_string.string
# => "So Long, and Thanks for All the Fish"

total_range = NSMakeRange(0,attr_string.string.size)
attr_string.attributesAtIndex(0, effectiveRange: total_range)
=> {"NSFont"=>#<NSFont:0x20027d7c0>}

The AppKit framework also uses NSParagraphStyle/NSMutableParagraphStyle to encapsulate the paragraph or ruler attributes used by the NSAttributedString classes.

Warning

String instances are usually mutable (see Mutability for details about mutability), but might be modified to become immutable.

Arrays

MacRuby’s Array implementation is fully compatible with the mutable version of NSArray. That means that anywhere you need to use or pass a NSMutableArray, you can use an instance of Array, and vice versa.

Warning

Array instances are usually mutable (see Mutability for details about mutability), but some APIs might modify a passed array to become immutable. Make sure you know which type you have.

Ruby’s API is often easier to use and less verbose. For instance, here is how you create a new array instance using Cocoa’s NSMutableArray:

>> framework 'Foundation'
# don't forget nil as the last element
>> NSArray.arrayWithObjects('so', 'say', 'we', 'all', nil)
=> ["so", "say", "we", "all"]

Here’s the same thing using Ruby’s syntax:

>> ["so", "say", "we", "all"]
=> ["so", "say", "we", "all"]

You can read more about Ruby’s Array on Ruby’s document website.

The Ruby syntax creates mutable array versions by default, so if you receive an immutable version of an array and want a mutable version, you can use the mutableCopy method:

>> framework 'Foundation'
=> true
# create an immutable array
>> a = NSArray.arrayWithObjects('terrans', 'zerg', 'protoss', nil)
=> ["terrans", "zerg", "protoss"]
>> b = a.mutableCopy
=> ["terrans", "zerg", "protoss"]
>> b << "Xel'Naga"
=> ["terrans", "zerg", "protoss", "Xel'Naga"]

Conversely, if you want a copy of an mutable array to be immutable, use the #copy method available from NSObject:

>> array = ['M', 'V', 'C']
=> ["M", "V", "C"]
>> copy = array.copy
=> ["M", "V", "C"]
>> copy.class
=> Array

Another way to achieve the same result is to use Ruby’s dup instance method.

Read the NSCopying and NSMutableCopying Protocol References for more information. These protocols are implemented by a majority of the Cocoa classes.

A few Foundation methods that have no counterparts in the traditional Ruby API can be very useful, including the following:

firstObjectCommonWithArray

Returns the first object contained in the receiver that’s equal to an object in another given array. For instance:

>> [1,2,3].firstObjectCommonWithArray([3,4,5])
=> 3

is the same as:

>> ([1,2,3] & [3,4,5]).first
=> 3
writeToFile:atomically

Writes the contents of the receiver to a file at a given path:

>> order = ['double double', ['pickles', 'onions'], {:animal_style => true}]
=> ["double double", ["pickles", "onions"], {:animal_style=>true}]
>> order.writeToFile("/tmp/in-n-out", atomically: true)
=> true

The array is serialized to disk using a property list, producing the following file:

<?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">
<array>
  <string>double double</string>
  <array>
    <string>pickles</string>
    <string>onions</string>
  </array>
  <dict>
    <key>animal_style</key>
    <true/>
  </dict>
</array>
</plist>

It can then be deserialized using the arrayWithContentsOfFile class method:

>> NSMutableArray.arrayWithContentsOfFile("/tmp/in-n-out")
=> ["double double", ["pickles", "onions"], {"animal_style"=>true}]

Warning

This serialization process is fast and easy, but works with only a few basic types. For more general use, try other built-in serialization solutions such as YAML, JSON, marshaling, and a lower-level serialization provided by NSData.

Hashes/Dictionaries

MacRuby’s Hash implementation is fully compatible with NSMutableDictionary, allowing you to use Ruby and Objective-C’s methods on the same objects. Also, because NSDictionary is “toll-free bridged” with its Core Foundation counterpart, CFDictionary, the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object. In other words, you can use MacRuby, Foundation, or Core Foundation hash/dictionaries interchangeably. If you are calling a C function that requires an instance of one of these dictionary classes, you can pass it a Hash instance, and it will work just fine.

Since NSDictionary object creation is different from its Ruby counterpart, let’s compare both approaches:

Cocoa:

keys    = ['Matt', 'Laurent', 'Vincent']
objects = ['double-double', '3x3', 'cheeseburger']
NSMutableDictionary.dictionaryWithObjects(objects, forKeys:keys)
# => {"Matt"=>"double-double", "Laurent"=>"3x3", "Vincent"=>"cheeseburger"}

or:

NSMutableDictionary.dictionaryWithObjectsAndKeys(
       'double-double', 'Matt',
       '3x3', 'Laurent',
       'cheeseburger', 'Vincent', nil)
# => {"Matt"=>"double-double", "Laurent"=>"3x3", "Vincent"=>"cheeseburger"}

Ruby:

{"Matt"=>"double-double", "Laurent"=>"3x3", "Vincent"=>"cheeseburger"}
# => {"Matt"=>"double-double", "Laurent"=>"3x3", "Vincent"=>"cheeseburger"}

You can read more about Ruby’s Hash on Ruby’s document website.

Just as with NSArray, Ruby’s API is often easier to use. NSDictionary also implements serialization via writeToURL:atomically: and NSDictionary.dictionaryWithContentsOfFile.

Sets

Ruby’s Set, like Cocoa’s NSSet, NSMutableSet, NSCountedSet, and NSHashTable classes, implements unordered collections of distinct elements. It might seem convenient to build MacRuby’s Set on top of one of the Cocoa set classes, but it is actually implemented entirely from scratch. The main reason is that NSSet and its variants behave significantly differently from Ruby’s Set. So look at the Cocoa and Ruby implementations and choose whichever one makes more sense for your case.

The name NSHashTable might lead you to believe the class is somehow related to Ruby’s Hash class, but they are quite different, NSHashTable is a variant of NSSet. The big difference between NSHashTable and the other Set implementations is that NSHashTable supports weak references in a garbage-collected environment. Unless you have a very specific need for weak references, stick to Ruby or Cocoa’s Set implementations.

Enumerators

NSEnumerator and NSFastEnumerationEnumerator are conceptually the same as Ruby Enumerator, except that the Ruby version is much more powerful.

The aim of these classes is to allow collections of objects, such as arrays, sets, and dictionaries/hashes, to be processed one at a time.

When using the Ruby API, just pass a block (an anonymous method) to one of the many collection methods that return an enumerator:

["Eloy", "Matthias", "Joshua", "Thibault"].each do |name|
  puts "#{name} is a great developer!"
end
# => Eloy is a great developer!
# => Matthias is a great developer!
# => Joshua is a great developer!
# => Thibault is a great developer!
["Eloy", "Matthias", "Joshua", "Thibault"]

The array is being enumerated and each item is sent to the block.

You can also get an Enumerator instance and store it in a variable to call methods on it later on:

object_enumerator = {'nl' => "Eloy",
                     'ch' => "Matthias",
                     'us' => "Joshua",
                     'fr' => "Thibault"}.each_value
object_enumerator.each_slice(2) do |name_1, name_2|
  puts "#{name_1} and #{name_2}"
end

# outputs
Thibault and Matthias
Eloy and Joshua

Date, Time, and Calendars

Cocoa and MacRuby provide much more sophisticated time options than simple timestamps or even date/time formatting routines.

NSDate

NSDate is the class that implements dates and times in Cocoa. It is used to create date objects and perform date-based calculations. NSDate objects are invariant points in time and therefore immutable:

now = NSDate.date
now.description
# => "2009-12-30 23:09:16 -0500"

seconds_per_day = 24 * 60 * 60
tomorrow = NSDate.alloc.initWithTimeIntervalSinceNow(seconds_per_day)
tomorrow.description
# => "2009-12-31 23:09:27 -0500"

NSDate.date
# => 2009-12-30 23:10:02 -0500
NSDate.dateWithTimeIntervalSinceNow(10.0)
# => 2009-12-30 23:10:12 -0500

NSDate also has more flexible ways to create dates, for instance:

framework 'Foundation'
NSDate.dateWithNaturalLanguageString('next Tuesday dinner').description
# =>"2010-01-12 19:00:00 -0800"

NSDate.dateWithString("2010-01-10 23:51:05 -0800").description
=> "2010-01-10 23:51:05 -0800"

date_string = "3pm June 30, 2010"
NSDate.dateWithNaturalLanguageString(date_string).description
# => "2010-06-30 15:00:00 -0700"
NSDate.dateWithNaturalLanguageString("06/30/2010").description
# => "2010-06-30 12:00:00 -0700"

Here is the Ruby API to create Time instances:

>> Time.now
# => 2009-12-30 23:19:00 -0500
>> Time.now + (24 * 60 * 60)
# => 2009-12-31 23:19:00 -0500

Ruby’s date and time APIs are usually more flexible than their Cocoa counterparts. However, some Cocoa APIs expect NSDate instances and, in some cases, Ruby’s API lacks some features. Being familiar with Cocoa’s date and time management is therefore important.

If you want to convert an instance of Time and to an NSDate instance, use NSDate.dateWithString and pass it the string representation of your time object. For example:

NSDate.dateWithString(Time.now.to_s)

NSCalendar

The NSCalendar class represents calendar objects and covers the most used calendars—Buddhist, Gregorian, Hebrew, Islamic, and Japanese:

current_calendar = NSCalendar.currentCalendar

Calendars are used in conjunction with NSDateComponents and NSDate:

today      = NSDate.date
calendar   = NSCalendar.currentCalendar
units      = (NSDayCalendarUnit | NSWeekdayCalendarUnit)
components = calendar.components(units, fromDate: today)
components.day     # => 30
components.weekday # => 4

Consult the reference documentation about the available NSCalendarUnit constants to set the different units you might need. If you are using the Gregorian calendar, you can more easily access most of the date information using the strftime method of Ruby’s Time class. However, the Cocoa API is great for getting localized date management and access data unavailable in Ruby’s API, such as era and quarter.

Data

The NSData and NSMutableData classes are typically used for data storage. NSData is “toll-free bridged” with its Core Foundation counterpart: CFData. This means the Core Foundation type is interchangeable with the bridged Foundation object in function or method calls. If a function expects a CFDataRef parameter, you can send it an NSData instance, and vice versa.

If you have a string and you want to access its data representation, use the dataUsingEncoding method:

string = "Some classes gets initiated using data, (i.e NSXMLDocument)"
data = string.dataUsingEncoding(NSUTF8StringEncoding)
# => #<NSConcreteMutableData:0x200295cc0>

Conversely, if you have some data and want to access a string representation, use the following:

NSString.alloc.initWithData(data, encoding:NSUTF8StringEncoding)
# => "Some classes gets initiated using data, (i.e NSXMLDocument)"

Quite often, when I have to deal with a lot of string/data conversions, I reopen the String and Data classes and add some conversion methods:

class String
  def to_utf8_data
    self.dataUsingEncoding(NSUTF8StringEncoding)
  end
end

class NSData
  def to_utf8_string
    NSString.alloc.initWithData(self, encoding:NSUTF8StringEncoding)
  end
end

"Can't wait until Taco Tuesday!".to_utf8_data
# => #<NSConcreteMutableData:0x2002ac400>
data.to_utf8_string
=> "Can't wait until Taco Tuesday!"

Note

In Syntactic Sugar you saw that String instances respond to #to_data and NSData instances respond to #to_str. However, these methods encode the data using ASCII 8-bit instead of UTF-8, and sometimes you need the wider range of characters provided by UTF-8.

Locales

NSLocale, as its name implies, helps developers deal with different languages and linguistic, cultural, and technological conventions and standards. Using NSLocale, you can retrieve the system and/or user locale and load any of the available locales.

In addition to providing you with the user country and language codes, locales are very useful for displaying all sorts of localized data (time, date, monetary amounts, dimensions, etc.). Locales store information such as symbols used for the decimal separator in numbers, the way dates are formatted, and more:

framework 'Foundation'

locale = NSLocale.currentLocale
locale.objectForKey(NSLocaleLanguageCode)
# => "en"

# We could also use the MacRuby alias
locale[NSLocaleLanguageCode]
# => "en"

locale[NSLocaleCurrencyCode]
# => "USD"

locale[NSLocaleCurrencySymbol]
# => "$"

locale[NSLocaleUsesMetricSystem]
=> false

locale[NSLocaleCalendar].calendarIdentifier
=> "gregorian"

french_locale = NSLocale.alloc.initWithLocaleIdentifier("fr_FR")
french_locale.displayNameForKey(NSLocaleIdentifier, value: "en_US")
# => "anglais (États-Unis)"

Note

MacRuby adds some shortcuts/aliases to improve some of Cocoa’s APIs. For instance, if an object responds to objectForKey: and setObject:forKey: you can use [] and []= instead. This is exactly what we did in the previous example. Writing locale[NSLocaleLanguageCode] has exactly the same effect as writing locale.objectForKey(NSLocaleLanguageCode).

Time Zones

NSTimeZone will provide you with all the information you need to handle timezones:

time_zone = NSTimeZone.localTimeZone
time_zone.name
# => "America/Los_Angeles"

time_zone.abbreviation
# => "PST"

time_zone.secondsFromGMT
=> -28800

time_zone.daylightSavingTime?
# same as calling time_zone.isDaylightSavingTime
=> false

french_locale = NSLocale.alloc.initWithLocaleIdentifier("fr_FR")
time_zone.localizedName(NSTimeZoneNameStyleStandard, locale: french_locale)
# => "heure normale du Pacifique"

Exceptions

Cocoa’s NSException works the same as Ruby’s Exception. As the following example shows, raised NSException instances can be caught by the standard Ruby syntax:

exception = NSException.exceptionWithName('MacRuby Book',
                                          reason: 'documentation purposes',
                                          userInfo: nil)
begin
  exception.raise
rescue Exception => e
  puts e.message
end
# => MacRuby Book: documentation purposes

I/O

As with most of the tasks discussed in this chapter, input and output (I/O) operations can be performed using either the Ruby or the Cocoa API. However, Cocoa will more than likely make your applications more robust and efficient, mainly because a lot of its APIs are asynchronous. Asynchronous APIs are important when writing desktop/mobile applications, because you don’t want to block the main application process while waiting for your I/O operation to finish.

Let’s imagine that you have a Twitter client and want to fetch the latest updates. If you don’t use an asynchronous API, when the user clicks the refresh button, the application will freeze and the “beach volleyball/pizza of death” will spin until the API response is received from Twitter. To ensure a better user experience, it is recommended that you use an asynchronous API and provide some sort of indication of progress.

URLs/Requests/Connections

The NSURL, NSURLRequest, and NSURLConnection classes provide developers with ways to manipulate and load URLs and the resources they reference.

An NSURL object can refer to a local or remote resource. It’s the recommended way to refer to files. Various protocols are supported:

  • File Transfer Protocol (ftp://)

  • Hypertext Transfer Protocol (http://)

  • Secure 128-bit Hypertext Transfer Protocol (https://)

  • Local file URLs (file://)

NSURL also transparently supports both proxy servers and SOCKS gateways using the user’s system preferences.

Since NSURL is a class you will more than likely use often, let’s start by covering some of the frequently used methods:

NSURL.fileURLWithPath

Initializes and returns a newly created NSURL object as a file URL with a specified path:

framework 'Foundation'
url =  NSURL.fileURLWithPath("/usr/local/bin/macruby")
url.description
# => "file://localhost/usr/local/bin/macruby"
isFileReferenceURL or fileReferenceURL?

Returns True if the NSURL object refers to a file, false otherwise:

framework 'Foundation'
macruby =  NSURL.fileURLWithPath("/usr/local/bin/macruby")
website =  NSURL.URLWithString("http://macruby.org")

macruby.fileReferenceURL?
# => true
website.fileReferenceURL?
# => false

Note

MacRuby adds some syntactic sugar. Objective-C methods with an is prefix are aliased in MacRuby by adding a question mark at the end of the method name. For instance, an Objective-C method called isOpaque is also available in MacRuby using the opaque? method alias.

NSURL.URLWithString

Creates and returns an NSURL object initialized with the string provided. Here is an example with some of the available accessors. Refer to the API documentation for more details:

framework 'Foundation'
url = NSURL.URLWithString("http://macruby.org/downloads.html#beta")
# => #<NSURL:0x200262ba0>
url.absoluteString
# => "http://macruby.org/downloads.html#beta"
url.fragment
# => "beta"
url.host
# => "macruby.org"
url.path
# => "/downloads.html"
url = NSURL.URLWithString
("http://macruby.org/downloads.html?sorted=true&nightly=false")
url.query
# => "sorted=true&nightly=false"
url = NSURL.URLWithString("http://mattetti:apple@macruby.org:8888/admin")
url.port
# => 8888
url.user
# => "mattetti"
url.password
# => "apple"

The following list explains the methods used in the example. The different parts of a URL are defined by RFC 1808:

absoluteString

Returns a string representation of the URL.

fragment

Returns the fragment of the URL following a pound or hash sign.

host

Returns the URL’s host.

path

Returns the URL’s path.

query

Returns the query from the URL, the part following a question mark.

port

Returns the port from the URL.

user

Returns the user from the URL.

password

Returns the password from the URL.

NSURLRequest and NSMutableURLRequest objects represent URL load requests. These objects contain two different aspects of a load request: the URL to load and the cache policy to use. Once a URL request is defined, it can be loaded and processed using NSURLConnection or NSURLDownload.

The concepts in the following subsections apply to both NSURLRequest and NSMutableURLRequest, but for the sake of simplicity I’ll refer just to NSURLRequest.

Cache Policy and Cache Access

Caching allows better performance and optimized resource usage. Of the standard protocols, NSURLConnection caches only HTTP and HTTPS requests, but custom protocols can also provide caching. NSURLRequest relies on a composite on-disk and in-memory cache, which is enabled by default.

Here is an example where we are sending a request to the MacRuby website ignoring any cached data:

framework 'Foundation'
url = NSURL.URLWithString('http://macruby.org')
request = NSMutableURLRequest.requestWithURL(url,
                    cachePolicy:NSURLRequestReloadIgnoringCacheData,
                    timeoutInterval:30.0)

NSURLConnection.connectionWithRequest(request, delegate:self)
puts "Connecting to http://macruby.org"

# response callback
def connection(connection, didReceiveResponse:response)
  puts "response received with status code: #{response.statusCode}"
  exit
end

# Keep the main loop running
NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

Unless specified otherwise, HTTP requests use the default HTTP cache protocol, which will cache them and use their ETag and Last-Modified headers to determine whether subsequent requests are stale. Using the headers, the cache is evaluated for each request. Read more about HTTP caching in RFC 2616 to see how to take advantage of this feature.

NSURLConnection allows you to alter default caching behavior. Four types of caching are available and can be passed as an argument:

Default cache policy (NSURLRequestUseProtocolCachePolicy)

The default policy is specific to the protocol used and is the best conforming policy for the protocol.

Disabled cache policy (NSURLRequestReloadIgnoringCacheData)

This ignores the cache, disabling it.

Use cached data, fallback on loading the resource (NSURLRequestReturnCacheDataElseLoad)

Use of the cache can be forced, ignoring cache data age and expiration date. The request is loaded from its source only if no cache data is available. The data is also cached after it is loaded.

Offline mode (NSURLRequestReturnCacheDataDontLoad)

This caching policy simulates an offline mode and loads data only from the cache.

NSURLDownload does not cache responses.

Note

Defining a request using NSURLRequest.requestWithURL uses the default cache policy and a default timeout of 30 seconds. But, of course, you can specify your own settings using NSRequest.requestWithURL:cachePolicy:timeoutInterval:.

Asynchronous Versus Synchronous

We have already touched briefly on the topic of asynchronous versus synchronous APIs. Ruby I/O APIs are synchronous, meaning that when an operation is performed, the process is blocked, waiting for the operation to be done. Blocking a process may not always be a problem. Separate threads can also be started to handle blocking I/Os.

When writing a web application, your application reacts to an action triggered by a user. If there is no traffic on your website, your code is idle. However, when writing a desktop or mobile application, your code is constantly running without requiring user interaction. To keep running, the application has a main run loop that keeps the code running. Within the run loops, you can use asynchronous APIs. Basically, you initiate an operation, return to other activities (or wait for further user input), and receive notification when the operation is done. To get the notification, you implement delegate methods and point to them when initiating the asynchronous operation.

If you are satisfied using synchronous APIs, stick to the Ruby standard libraries such as net/http or the methods available on NSString or NSData, as shown here:

framework 'Foundation'
url = NSURL.URLWithString 'http://macruby.org'
content = NSMutableString.alloc.initWithContentsOfURL(url,
                                                      encoding:NSUTF8StringEncoding,
                                                      error:nil)

or:

require 'net/http'
content = Net::HTTP.get('www.macruby.org', '/')

However, you will probably be more interested in using the asynchronous APIs provided by Cocoa. As an example, I’ll discuss the NSURLDownload call mentioned in the previous section, which downloads remote content to file.

NSURLDownload downloads requests asynchronously. The request’s body is saved into a file. When initiating the download, you specify the destination path and the delegator object containing the callbacks you want to use during the download. Within the delegator object, you can define a rich set of optional methods to check on redirection, download status, authentication challenges, and completion. These run automatically as the Cocoa runtime detects events. You can also cancel the load if needed.

Here is a sample download script. It starts by defining a class arbitrarily named DownloadDelegator. I’ve chosen to define four methods, including two variants of download. After defining the class, the script initiates a download by creating an object of type NSURLDownload. The runtime takes care of the rest, calling downloadDidBegin and downloadDidFinish at the appropriate moments.

The two download methods are defined on the DownloadDelegator class. They follow the delegate method signature defined by the NSURLDownload API. When we create our NSURLDownload instance, we also create an instance of DownloadDelegator, which we assign as a delegate object. This way the NSURLDownload instance will automatically try to dispatch the delegate methods if they are available on the delegate object. The download methods are then called automatically:

framework 'Cocoa'

class DownloadDelegator

  def downloadDidBegin(dl_process)
    puts "downloading..."
  end

  def download(dl_process, decideDestinationWithSuggestedFilename:filename)
    home = NSHomeDirectory()
    path = home.stringByAppendingPathComponent('Desktop')
    path = path.stringByAppendingPathComponent(filename)
    dl_process.setDestination(path, allowOverwrite:true)
  end

  def download(dl_process, didFailWithError:error)
    error_description = error.localizedDescription
    more_details      = error.userInfo[NSErrorFailingURLStringKey]
    puts "Download failed. #{error_description} - #{more_details}"
    exit
  end

  def downloadDidFinish(dl_process)
    puts "Download finished!"
    exit
  end

end

url_string = 'http://www.macruby.org/files/nightlies/macruby_nightly-latest.pkg'
url        = NSURL.URLWithString(url_string)
req        = NSURLRequest.requestWithURL(url)
file_download = NSURLDownload.alloc.initWithRequest(req, 
                                    delegate: DownloadDelegator.new)

# keep the run loop running
NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

Pipes

In Unix-like computer operating systems, pipes allow you to chain processes. The output of a process (stdout) feeds directly the input (stdin) of another one.

The NSPipe class provides an interface for accessing pipes. An NSPipe class instance represents both ends of a pipe and enables communication through the pipe.

File handles are used in the NSTask example a bit later in this chapter. Look at the example’s code to learn how to create and monitor pipes.

File Handles

The NSFileHandle class provides a wrapper for accessing open files or communications channels. File handles can be retrieved for a URL, a path, standard input, standard output, standard error, or a null device. Some objects, such as pipes, also expose a file handle. Using a file handle, one can read or write the data source it references asynchronously.

File handles are used in the NSTask example a bit later in this chapter. Look at the example’s code to learn how to use file handles.

Bundles

A bundle is a collection of code and resources used by an application, generally created outside the application. Bundles are usually built with Xcode using the Application or Framework project types, or using plug-ins. In most cases, your application bundle will contain .nib/.xib files, media assets (images/sounds), dynamic libraries, and frameworks. The NSBundle class creates objects that represent bundles.

Useful methods include:

NSBundle.mainBundle

A class method returning the NSBundle object that usually corresponds to the application file package or application wrapper (the .app folder holding your code).

localizations

Returns a list of all the localizations available in the bundle:

framework 'Foundation'
NSBundle.mainBundle.localizations
# => ['English', 'French']
localizedStringForKey:value:table:

Returns the localized version of a string from the Localizable.strings file.

pathForResource:ofType:

Returns the full pathname for the resource identified by the specified name and file extension.

The following example registers a custom font (akaDylan.ttf) that was added to the resources folder. We can use it in our UI without having to install it on the user’s machine:

framework 'Foundation'
font_location = NSBundle.mainBundle.pathForResource('akaDylan', ofType: 'ttf')
font_url = NSURL.fileURLWithPath(font_location)
# using this Core Text Font Manager function to register the embedded font.
CTFontManagerRegisterFontsForURL(font_url, KCTFontManagerScopeProcess, nil)

Scheduling

The OS X runtime offers both low-level threads and a collection of convenient ways to schedule activities such as timers and operation queues. We’ll look at the various options in this section.

Run Loops

Run loops process input from the mouse, keyboard events from the window system, NSPort objects, NSConnection objects, NSTimer events, and other sources. Run loops are essential to keeping your application running and handling inputs. In Cocoa, the NSRunLoop class implements the run loop concept.

You don’t need to create or manage NSRunLoop instances. Every NSThread object you create is already attached to its own NSRunLoop instance.

We’ve already used NSRunLoop in many of the examples in this chapter. What we did was retrieve the current thread’s run loop and force it to run until a distant date:

framework 'Foundation'

future = NSDate.distantFuture
NSRunLoop.currentRunLoop.runUntilDate(future)

The most interesting methods provided by NSRunLoop are:

NSRunLoop.currentRunLoop

As already shown, this returns the NSRunLoop object for the current thread. If a run loop does not yet exist, one is created and returned.

NSRunLoop.mainRunLoop

Returns the run loop of the main thread.

currentMode

Run loops can run in different modes, each of which defines its own set of objects to monitor. This method returns the run loop’s input mode.

runUntilDate

Runs the run loop for a delimited amount of time. More exactly, it sets the run loop to run until a specific date.

Timers

Sometimes you might want to trigger a method to run at a regular interval. For instance, you might want to redraw a video game screen 30 times a second or call an API every 2 minutes to check the status of a service. To do that, you can use a timer object defined by the NSTimer class.

Timers can be repeated or run once. If it’s a repeating timer, it sends a specified method to a target object again and again based on the defined time interval.

Here is an example of a game loop implementation based on NSTimer:

framework 'Foundation'

class GameLoop

  def start
    @timer = NSTimer.scheduledTimerWithTimeInterval 0.06,
                                           target: self,
                                           selector: 'refresh_screen:',
                                           userInfo: nil,
                                           repeats: true
  end

  def refresh_screen(timer)
    puts "refresh"
  end

  def stop_refreshing
    @timer.invalidate && @timer = nil if @timer
  end

end

GameLoop.new.start
NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

NSTimer instances can fire only when the run loop they live in is running. Also, you need to keep in mind that the effective resolution of a timer is approximately 50 to 100 milliseconds. This should be fine for UI-driven interactive applications that don’t depend on finer-grained execution times.

There are three ways to schedule a timer. The first is to use the following:

NSTimer.scheduledTimerWithTimeInterval:invocation:repeats:

or:

NSTimer.scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

This method creates the timer and schedules it on the current run loop in the default mode.

This method relies on the additional NSInvocation class, so to keep things simpler, let’s look at an example using a second approach:

framework 'Foundation'

def do_something(timer)
  puts 'Do something'
end

NSTimer.scheduledTimerWithTimeInterval 0.06,
                         target: self,
                         selector: 'do_something:',
                         userInfo: nil,
                         repeats: true

NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

The first argument is the time interval, so in this case, the timer is going to fire 15 times a second (every 0.06 seconds). When the timer is triggered, it will send the do_something: selector to self (target:) and will pass a nil argument (specified by userInfo:). The timer will be repeated indefinitely.

Note

Selectors represent methods. A selector is just a string consisting of the method name followed by a colon.

You can pass any object to the method triggered by the timer. To do so, just pass your object via the userInfo argument. If you have multiple items of data to pass, combine them in an array or object. To retrieve the passed data, call #userInfo on the timer object passed to the callback.

The following is an example of a timer that fires just once:

framework 'Foundation'

def do_something(timer)
  puts "#{timer.userInfo} says: do something!"
  exit
end

NSTimer.scheduledTimerWithTimeInterval 0.5,
                         target: self,
                         selector: 'do_something:',
                         userInfo: 'Simon',
                         repeats: false

NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

# outputs:
# => 'Simon says: do something!'

Notice that the userInfo object is evaluated only the first time it’s called. The following contrived example should make that clear. No matter when the timer runs, the do_something prints just the time that was stored in its argument the first time it ran:

framework 'Foundation'

def do_something(timer)
  puts timer.userInfo
end

NSTimer.scheduledTimerWithTimeInterval 0.5,
                         target: self,
                         selector: 'do_something:',
                         userInfo: Time.now,
                         repeats: true

NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

# outputs:
# => 2010-01-10 23:05:45 -0800
# => 2010-01-10 23:05:45 -0800
# => 2010-01-10 23:05:45 -0800
# => 2010-01-10 23:05:45 -0800
# => 2010-01-10 23:05:45 -0800
# [...]

Another option is to create a timer and not schedule it to a run loop right away. Do this using one of the following calls:

NSTimer.timerWithTimeInterval:invocation:repeats:a
NSTimer.timerWithTimeInterval:target:selector:userInfo:repeats:

After the timer is created, you must add it to a run loop using the NSRunLoop’s addTimer:forMode: method. For example:

framework 'Foundation'

def do_something(timer)
  puts 'do something'
end

timer = NSTimer.timerWithTimeInterval 0.5,
                         target: self,
                         selector: 'do_something:',
                         userInfo: nil,
                         repeats: true

# the timer isn't scheduled yet
# let's schedule it:
NSRunLoop.currentRunLoop.addTimer(timer, forMode: NSDefaultRunLoopMode)
NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

Finally, you can create a timer with a defined fire date and then attach it to a run loop:

framework 'Foundation'

def do_something(timer)
  puts 'do something'
  exit
end

in_2_seconds = Time.now + 2
timer = NSTimer.alloc.initWithFireDate NSDate.dateWithString(in_2_seconds.to_s),
                         interval: 0.5,
                         target: self,
                         selector: 'do_something:',
                         userInfo: nil,
                         repeats: false

NSRunLoop.currentRunLoop.addTimer(timer, forMode: NSDefaultRunLoopMode)
NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

Tasks/Subprocesses

You might want to run another program as a subprocess and monitor its execution. An easy way to do that is to use Ruby’s API to shell out and call a program:

puts `/bin/ls -la /`

The back ticks execute the command, which is then printed. While this is very easy and nice, it blocks the main loop. So, if we were to start, for instance, an encoding process, we would have to wait for it to finish before we could continue the execution of our program. Not always ideal.

This is exactly when Cocoa’s NSTask class shines. NSTask objects create separate executable entities. You can monitor the execution of your task using observers.

An NSTask needs to be passed a command to execute, the directory in which to run the task, and optional standard input/output/error values. An NSTask instance can be run only once. Subsequent attempts to run the task will raise an exception.

Here is an example showing how to asynchronously start a new process and monitor it:

framework 'Foundation'

class AsyncHandler
  def data_ready(notification)
    data = notification.userInfo[NSFileHandleNotificationDataItem]
    output = NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
    puts output
  end
  def task_terminated(notification)
    exit
  end
end

notification_handler = AsyncHandler.new
nc = NSNotificationCenter.defaultCenter

task = NSTask.alloc.init
pipe_out = NSPipe.alloc.init

task.arguments = ['-la']
task.currentDirectoryPath = "/"
task.launchPath     = "/bin/ls"
task.standardOutput = pipe_out
file_handle = pipe_out.fileHandleForReading

nc.addObserver(notification_handler,
                selector: "data_ready:",
                name: NSFileHandleReadCompletionNotification,
                object: file_handle)

nc.addObserver(notification_handler,
                selector: "task_terminated:",
                name: NSTaskDidTerminateNotification,
                object: task)

file_handle.readInBackgroundAndNotify
task.launch

# keep the run loop running
NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

We start by defining an AsyncHandler class that implements some callback methods (data_ready and task_terminated). We then create our task and a pipe to monitor the output of the task. Finally, we add an observer on the pipe’s file handle (so we’ll know when data is being written to it) and another notification (so we’ll know when the task is done running). These observers invoke the callback methods we defined at the start.

The task is executed and the output is printed as it is pushed by the notification system. Once the task is done running, the program exits.

Threads

Threads are often used to run code that might take some time to execute, when the developer doesn’t want to block the execution of the rest of the application.

In the case of a Cocoa application, the main thread handles the UI and the various inputs (user, network, devices, etc.). Using threads leaves the main thread to run smoothly while lengthy processing runs separately.

Using multiple threads can also distribute the load to multiple cores and therefore improve the performance of your code when run on multicore machines.

OS X uses POSIX threads, which are a standard seen on many Unix-type systems. But Cocoa has enhanced these threads to use a simple locking mechanism, NSLock. You can create threads using Cocoa’s NSThread class or Ruby’s Thread. Ruby’s Thread class creates only POSIX threads, while NSThread creates Cocoa’s enhanced threads by default, with the option of creating POSIX threads.

The only “inconvenience” with using POSIX threads is that unless you start an NSThread instance, Cocoa frameworks will think you are running in a single-threaded mode. This was historically important to optimize threaded versus nonthreaded code path, but nowadays the difference is not very important.

Warning

It’s fine to mix Cocoa threads and POSIX threads, but make sure to use their respective mutex classes to lock them.

An NSThread can be initiated with a target and method to call on it. Here is an example showing how to start an expensive process without blocking the main thread:

framework 'Foundation'

class ExpensiveCalculation
  def start(to_process)
    puts "processing on a separate thread"
    sleep(2)
    puts "processing is over"
  end
end

calculation = ExpensiveCalculation.new
thread = NSThread.alloc.initWithTarget( calculation,
                                        selector:'start:',
                                        object:'dummy_obj' )
thread.start
puts "the main thread is not blocked"

NSRunLoop.currentRunLoop.runUntilDate(NSDate.dateWithTimeIntervalSinceNow(3.0))

The output will be something like the following. The order of statements may be different, because the order in which the main thread and subordinate thread run is unpredictable:

processing on a separate thread
the main thread is not blocked
processing is over

Once you have an NSThread instance, you can also ask for its status:

framework 'Foundation'
thread = NSThread.alloc.init
thread.start

thread.cancelled?
# => false
thread.executing?
# => false
thread.finished?
# => true

MacRuby developers might, however, prefer to use Ruby’s Thread API or Grand Central Dispatch (GCD).

As mentioned earlier, MacRuby’s Thread class creates POSIX threads. The API is very simple:

def print_char(char)
  1.upto(5_000) do
    print char
  end
end

Thread.new{print_char('1')}
print_char('0')

The output will look more or less like the following:

# [..]
0000000000001111111111111111111111111111111111111111111111111111111111111111111
1111111111011111111011111111111110011111111111110011111111111111001111111111111
0111111011111111111111001111111111111100000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000011
1111101111111111111111100111111111111100111111011111111011111111111111000000000
0000000000000000000000000000000000000000000000000000000000000001111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111
# [..]

You can also pass a variable to a thread as follows:

['Joshua', 'Laurent', 'Matt'].each do |dev_name|
  Thread.new(dev_name){|name| puts "Hello, #{name}!\n"}
end

Each thread has access to its own local variable called name.

Note

MacRuby has been specifically designed with concurrency in mind. Unlike the main Ruby interpreter, threads are executed in parallel and multiple threads can concurrently call into MacRuby without blocking.

Operations/Operation Queues

Dealing with threads can be quite cumbersome and difficult. Thankfully, Cocoa encapsulates threads by grouping work that you want to perform asynchronously into what are called operations. Operations can be used on their own or queued up and prioritized to run asynchronously.

Operations are discussed more in depth in Concurrency, but let’s look at how you can create a queue and add operations to it. To do this, we will use the NSOperationQueue class and NSOperation.

Because the NSOperation class is an abstract class, we will need to create a subclass to define our custom operation and add it to a queue:

framework 'Foundation'

class MyOperation < NSOperation
  attr_reader :name

  def initWithName(name)
    init
    @name = name
    self
  end

  def main
    puts "#{name} get to work!"
  end

end

operation_matt  = MyOperation.alloc.initWithName('Matt')

queue = NSOperationQueue.alloc.init
queue.addOperation(operation_matt)

# run the main loop for 2 seconds
NSRunLoop.currentRunLoop.runUntilDate(NSDate.dateWithTimeIntervalSinceNow(2.0))

To create a valid NSOperation subclass, we need to create a custom initiator (in this case, we used initWithName) and define a main method that will be executed when the operation is triggered.

Note

When creating a custom initiator, don’t forget to return self.

Notifications

Most applications trigger a lot of events in the runtime, ranging from a user clicking on a UI element to an I/O operation. Cocoa’s Foundation supplies a programming architecture that allows you to send and receive notifications and to subscribe to occurring events.

The notification architecture is divided into three parts: the notifications themselves, the notification centers, and the notification observers. When an event happens, a notification is created and sent to a notification center, which then broadcasts the notification to all the observers that registered for the event. Notifications can also be held in a notification queue before being broadcast.

NSNotification objects encapsulate information about events. The objects don’t know much about anything except the event that sends them.

Notification Centers

If you need to manage notifications in just one process, use the NSNotificationCenter class. Otherwise, you will have to rely on NSDistributedNotificationCenter.

As explained earlier, notification centers receive notifications posted by events and dispatch them to registered observers. Observers are added to notification centers, and notifications can be manually posted to a center or automatically triggered by classes triggering notifications. The default notification center can be accessed using the NSNotificationCenter.defaultCenter method:

framework 'Foundation'

class NotificationHandler
  def tea_time(notification)
    puts "it's tea time!"
  end
end

center = NSNotificationCenter.defaultCenter
notification_handler = NotificationHandler.new

center.addObserver( notification_handler,
                    selector: "tea_time:",
                    name: 'tea_time_reminder',
                    object: nil )

center.postNotificationName("tea_time_reminder", object:self)

Warning

A common problem reported by programmers new to notification centers is that their notifications are not working. This is often because their observers are garbage-collected, and, therefore, never get triggered. By design, observers use weak references instead of strong references, and registrations are automatically cleaned up when an observer is collected. You therefore need to ensure that the observers don’t get garbage-collected for as long as you want the notification registration to remain. One way to do this is to assign them to instance variables of the class.

Notification Queues

One potential problem with notifications is that they are synchronous. Therefore, if an observer calls a slow method, the execution of the rest of the code is delayed. Notification queues, on the other hand, allow the coalescing of notifications as well as asynchronous posting.

As mentioned earlier, every thread has its own default notification center, but what I did not mention is that every default notification center also has a default notification queue. You can also create your own notification queue and associate it with a notification center.

To retrieve the default notification queue in the current thread, use the defaultQueue class method on NSNotificationQueue:

framework 'Foundation'
NSNotificationQueue.defaultQueue
# => #<NSNotificationQueue:0x200211100>

Now that you have a queue, you can post notifications to it. You have three options for doing this:

  • Posting as soon as possible, bypassing the queue (but posting only at the end of current run loop iteration).

  • Posting when the queue is idle.

  • Posting synchronously but with coalescence. The notification queue coalesces (combines) messages in two ways: by discarding identical duplicates of a message that arrive during a given time period and by combining small messages into a single notification.

These notification styles or priorities are flags that you set when posting the notification. To dispatch a notification using each of the three priorities, retrieve the default notification queue for the current thread and create a notification that is ready to be posted:

framework 'Foundation'
queue = NSNotificationQueue.defaultQueue
notification = NSNotification.notificationWithName('fetch_feed', object:nil)

To post a notification as soon as possible, use the NSPostASAP style:

queue.enqueueNotification(notification, postingStyle: NSPostASAP)

This is often used for posting to an expensive resource, such as the display server, to avoid having many clients flushing the window buffer after drawing within the same run loop iteration. You can have all the clients post the same NSPostASAP style notification with coalescing on name and object. As a result, only one of those notifications is dispatched at the end of the run loop and the window buffer is flushed only once.

To post a notification when the run loop is waiting (idle), use the following:

queue.enqueueNotification(notification, postingStyle: NSPostWhenIdle)

Here is a simple example: imagine you are writing a text editor and you want to display some statistics at the bottom of the page, such as the number of lines, characters, words, and paragraphs in the buffer. If you were to recalculate this information every time a key is pressed, it would result in quite a lot of resource usage, especially if the user types quickly. Instead, you can queue the notifications using the NSPostWhenIdle style and post the information only when there is a pause in typing. This saves loads of resources. The notification posting code will look like the following code. Imagine that text_field returns the main editor window with the text field we want to monitor:

notification = NSNotification.notificationWithName('text_edited', object:text_field)
queue = NSNotificationQueue.defaultQueue
queue.enqueueNotification(notification,
                          postingStyle: NSPostWhenIdle
                          coalesceMask: NSNotificationCoalescingOnName
                          forModes: nil)

The coalesceMask parameter tells the notification queue how to coalesce notifications. Notifications can be coalesced by name (NSNotificationCoalescingOnName) or by object (NSNotificationCoalescingOnSender). Alternatively, this parameter can disable coalescing (NSNotificationNoCoalescing). You can also combine the constants using the bitwise OR operator:

NSNotificationCoalescingOnSender | NSNotificationCoalescingOnName

Finally, to post a notification synchronously but still using the coalescence feature of the queue, use the following:

queue.enqueueNotification(notification, postingStyle: NSPostNow)

Using the NSPostNow style is the same thing as using postNotification or notificationWithName, but it has the advantage of being able to combine similar notifications using coalescence. You will often want to use this type of notification when you want a synchronous behavior, basically, whenever you want to ensure that observing objects have received and processed the notification before doing something else. Using coalescence, you still ensure dispatched notifications are handled synchronously, but you also guarantee the uniqueness of these notifications.

Archiving and Serialization

There are situations where you need to convert some of your objects into a form that can be saved to a file or transmitted to another process or machine, and then reconstructed. The native binary format that represents objects in memory while you manipulate them is not appropriate for storage and may not be readable on another system. Therefore, Cocoa and Ruby provide standard ways to translate between the binary formats and a format more suitable for storage, often in a human-readable text format. Translating the data into such a format is known as serialization or marshaling.

In some cases, you just need to serialize a simple hierarchical relationship like an API response, whereas in other cases you need to archive complex relationships, as Interface Builder does when it stores the objects and relationships that make up a UI in a nib file.

In Cocoa, archiving uses the NSCoder abstract class, which can encode and decode Objective-C objects, scalars, arrays, structures, and strings. To be properly encoded and decoded, objects have to implement the NSCoding protocol. This defines two methods: one to encode the object and one to restore it. All of the Foundation primitive classes (NSString, NSArray, NSNumber, and so on) and most of the Application Kit UI objects implement the NSCoding protocol and can be put into an archive.

NSCoder has three types of archives, which differ in the encoding/decoding process they use:

Sequential archives

Decoding is done in the same sequence in which the encoding took place. Use for sequential archives when your content needs to be processed linearly.

Keyed archives

Information is encoded and decoded in a random access manner using assigned name keys. Because the keys can be requested by name, you can decode parts of the data in any order. This option offers flexibility for making serialized objects forward and backward compatible.

Distributed objects

Used to implement distributed object architectures. To be used only when implementing distributed object architectures, which means when you want to do interprocess messaging between applications or threads.

Here is a simple keyed archiving process:

framework 'Foundation'
# let's assume we have a collection of Objective-C objects stored in
# a variable called objc_objects
archive_path = NSTemporaryDirectory().stringByAppendingPathComponent("Objs.archive")
result = NSKeyedArchiver.archiveRootObject( objc_objects, toFile: archive_path)

In Ruby, archiving is done using the Marshal class. All Ruby objects can be converted into a byte stream and then restored:

class CylonCenturion
  attr_accessor :battery_life, :ammo
end

class CylonSkinJob
  attr_accessor :physical_health, :mental_health
end
squadron = []
10.times{ squadron << CylonCenturion.new }
2.times{ squadron << CylonSkinJob.new }

# encode and save to file
File.open('cylon_squadron', 'w+'){|f| f << Marshal.dump(squadron)}
# reload
loaded_squadron = Marshal.load File.read('cylon_squadron')
leader = loaded_squadron.find{|soldier| soldier.is_a?(CylonSkinJob)}

Often, you don’t need to serialize complex relationships and objects. What you need is to serialize some basic object types, such as to save some user’s preferences to disk. To do that, you have a few options:

  • writeToURL/writeToFile

  • NSPropertyListSerialization

  • YAML

  • JSON

The writeToURL/writeTofile options were covered when we looked at the primitive classes added by Foundation. Refer to their documentation to see how to use this API:

For NSPropertyListSerialization, use the following:

framework 'Foundation'
user_info = { :points     => 4200,
              :level      => 3,
              :name       => 'Matt',
              :teams      => ['Blue', 'Red'],
              :twitter_update => false,
              :urls       => {:github  => 'http://github.com/mattetti',
                              :twitter => 'http://twitter.com/merbist'}
            }

plist_data = NSPropertyListSerialization.dataWithPropertyList(
                                            user_info,
                                            format: NSPropertyListXMLFormat_v1_0,
                                            options: 0,
                                            error: nil)

file_path = NSTemporaryDirectory().stringByAppendingPathComponent("user_info.plist")
plist_data.writeToFile(file_path, atomically:true)

You can also use MacRuby’s syntactical sugar:

user_info = { :points     => 4200,
              :level      => 3,
              :name       => 'Matt',
              :teams      => ['Blue', 'Red'],
              :twitter_update => false,
              :urls       => {:github  => 'http://github.com/mattetti',
                              :twitter => 'http://twitter.com/merbist'}
            }
plist = user_info.to_plist

Here is what the file content looks like:

<?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>level</key>
        <integer>3</integer>
        <key>name</key>
        <string>Matt</string>
        <key>points</key>
        <integer>4200</integer>
        <key>teams</key>
        <array>
                <string>Blue</string>
                <string>Red</string>
        </array>
        <key>twitter_update</key>
        <false/>
        <key>urls</key>
        <dict>
                <key>github</key>
                <string>http://github.com/mattetti</string>
                <key>twitter</key>
                <string>http://twitter.com/merbist</string>
        </dict>
</dict>
</plist>

To load and recover the object, you can reload the object and use the following code:

framework 'Foundation'
file_path = File.expand_path("~/user_info.plist")
plist_data = NSData.alloc.initWithContentsOfFile(file_path)
user_info = NSPropertyListSerialization.propertyListFromData(plist_data,
                                      mutabilityOption:
            NSPropertyListMutableContainersAndLeaves,
                                      format:nil,
                                      errorDescription:nil)

p user_info
# => {"points"=>4200, "twitter_update"=>false, 
"urls"=>{"github"=>"http://github.com/mattetti",
 "twitter"=>"http://twitter.com/merbist"}, "level"=>3, 
"teams"=>["Blue", "Red"], "name"=>"Matt"}

Notice that in this example, I chose to use the plist XML format, but I could have chosen to use the plist binary format instead.

Another way to deserialize a property list file is to use MacRuby’s helper to convert the content of the plist file:

file_path = File.expand_path("~/user_info.plist")
user_info = load_plist(File.read(file_path))

Because we know the type of object we are expecting from the deserialization (a dictionary), we could have also done the following:

file_path = File.expand_path("~/user_info.plist")
Hash.dictionaryWithContentsOfFile(file_path)

To encode basic types, we can also use YAML or JSON:

require 'yaml'
user_info = {"points"=>4200, "twitter_update"=>false, "urls"=>{"github"=>
 "http://github.com/mattetti", "twitter"=>"http://twitter.com/merbist"}, "level"=>3,
 "teams"=>["Blue", "Red"], "name"=>"Matt"}
File.open('user_info.yml', 'w+'){|f| f << user_info.to_yaml}

The content of the YAML file looks like the following:

---
points: 4200
twitter_update: false
urls:
  github: http://github.com/mattetti
  twitter: http://twitter.com/merbist
level: 3
teams:
- Blue
- Red
name: Matt

To deserialize the content of the file, simply do the following:

require 'yaml'
YAML.load_file('user_info.yml')
# => {"points"=>4200, "name"=>"Matt", "twitter_update"=>false,
"urls"=>{"twitter"=>"http://twitter.com/merbist", "github"=>
"http://github.com/mattetti"}, "level"=>3, "teams"=>["Blue", "Red"]}

Finally, you can use the JSON serialization format, as follows:

require 'json'
user_info = {"points"=>4200, "twitter_update"=>false, "urls"=>{"github"=>
"http://github.com/mattetti", "twitter"=>"http://twitter.com/merbist"}, "level"=>3,
 "teams"=>["Blue", "Red"], "name"=>"Matt"}
File.open('user_info.json', 'w+'){|f| f << user_info.to_json}

The content of the saved file looks like this:

{"points":4200,"name":"Matt","twitter_update":false,
 "urls":{"twitter":"http://twitter.com/merbist",
 "github":"http://github.com/mattetti"},"level":3,"teams":["Blue","Red"]}

To deserialize the file, use JSON.load:

require 'json'
JSON.parse File.open('user_info.json').read
# => {"points"=>4200, "teams"=>["Blue", "Red"], "twitter_update"=>false,
 "name"=>"Matt", "urls"=>{"twitter"=>"http://twitter.com/merbist",
 "github"=>"http://github.com/mattetti"}, "level"=>3}

Miscellaneous Classes

The Foundation framework also comes with other useful miscellaneous classes.

XML Parsing

Foundation offers two approaches to XML parsing: an event-driven parser called NSXMLParser and a NSXMLNode based solution using XPath.

The NSXMLParser solution is useful for processing a complete XML file, and is based on delegates. You need to initiate an NSXMLParser object with a URL or data. In the following example, I use NSXMLParser.alloc.initWithContentsOfURL to load XML from a URL. I then specify a delegate that will be called when the parser finds elements. Once that is done, I call parse on the parser object.

Here is a Really Simple Syndication parser built using NSXMLParser. I won’t go through the details of this script, but the overall structure gives you an idea of how this kind of parser works. There is documentation for NSXMLParser delegates at the Apple developer site:

framework 'Cocoa'
class RSSParser
  attr_accessor :parser, :xml_url, :doc

  def initialize(xml_url)
    @xml_url = xml_url
    NSApplication.sharedApplication
    url = NSURL.alloc.initWithString(xml_url)
    @parser = NSXMLParser.alloc.initWithContentsOfURL(url)
    @parser.shouldProcessNamespaces = true
    @parser.delegate = self
    @items = []
  end

  # RSSItem is a simple class that holds all of the RSS items.
  # Extend this class to display/process the item differently.
  class RSSItem
    attr_accessor :title, :description, :link, :guid, :pubDate, :enclosure
    def initialize
      @title, @description, @link, @pubDate, @guid = '', '', '', '', ''
    end
  end

  # Starts the parsing and send each parsed item through its block.
  #
  # Usage:
  #   feed.block_while_parsing do |item|
  #     puts item.link
  #   end
  def parse(&block)
    @block = block
    puts "Parsing #{xml_url}"
    @parser.parse
  end

  # Starts the parsing but keeps blocking the main run loop
  # until the parsing is done.
  # Do not use this method in a GUI app. Use #parse instead.
  def block_while_parsing(&block)
    @parsed = false
    parse(&block)
    NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)
  end

  # Delegate getting called when parsing starts
  def parserDidStartDocument(parser)
    puts "starting parsing.."
  end

  # Delegate being called when an element starts being processed
  def parser(parser, didStartElement:element, namespaceURI:uri, qualifiedName:name, 
                                                                  attributes:attrs)
    if element == 'item'
      @current_item = RSSItem.new
    elsif element == 'enclosure'
      @current_item.enclosure = attrs
    end
    @current_element = element
  end

  # as the parser finds characters, this method is being called
  def parser(parser, foundCharacters:string)
    if @current_item && @current_item.respond_to?(@current_element)
      el = @current_item.send(@current_element)
      el << string
    end
  end

  # method called when an element is done being parsed
  def parser(parser, didEndElement:element, namespaceURI:uri, qualifiedName:name)
    if element == 'item'
      @items << @current_item
    end
  end

  # delegate getting called when the parsing is done
  # If a block was set, it will be called on each parsed item
  def parserDidEndDocument(parser)
    @parsed = true
    puts "done parsing"
    if @block
      @items.each{|item| @block.call(item)}
    end
  end

end

twitter = RSSParser.new("http://twitter.com/statuses/user_timeline/16476741.rss")

# because we are running in a script, we need the run loop to keep running
# until we are done with parsing
#
# If we would to use the above code in a GUI app,
# we would use #parse instead of #block_while_parsing
twitter.block_while_parsing do |item|
  print item.title
end

The delegate object should implement some methods so it is alerted when the parser encounters an event. Delegate methods look like the following:

def parser(parser, didStartElement:element, namespaceURI:uri, qualifiedName:name, 
  attributes:attrs)
end

This method, for instance, is called when the parser encounters the beginning of an element. It’s up to the developer to set up the proper business logic to capture and process the information that the parser outputs.

In some cases, however, you already know the structure of the XML document you are going to process and you will want to access just one or a limited set of notes. To do that, you can rely on NSXMLNode/NSXMLDocument and XPath or XQuery.

Here is a simple example that fetches the MacRuby home page and searches for the current version using XPath:

framework 'Foundation'
url = NSURL.alloc.initWithString('http://macruby.org')
url_content = NSMutableString.alloc.initWithContentsOfURL(url,
                                              encoding:NSUTF8StringEncoding,
                                              error:nil)
data = url_content.dataUsingEncoding(NSUTF8StringEncoding)
document = NSXMLDocument.alloc.initWithData(data, options:NSXMLDocumentTidyHTML, 
                                              error:nil)
root = document.rootElement
version_xpath = '//*[@id="current_version"]'
error = Pointer.new(:object)
nodes = root.nodesForXPath(version_xpath, error:error)
if nodes.empty?
  puts error[0].description
else
  puts nodes.first.stringValue
end

Filtering/Logical Conditions

Using Ruby’s enumerators and blocks, you can query a collection of objects. The following example illustrates the procedure:

Actor = Struct.new :name, :oscars
actors = []
actors << Actor.new("Marlon Brando", 2)
actors << Actor.new("Sean Penn", 2)
actors << Actor.new("Jack Nicholson", 3)
actors << Actor.new("Adrien Brody", 1)
actors << Actor.new("Neil Patrick Harris", 0)

winners = actors.find_all{|actor| actor.oscars >= 1 }
# => [#<struct Actor name="Marlon Brando", oscars=2>, #<struct Actor name="Sean Penn", 
  oscars=2>, #<struct Actor name="Jack Nicholson", oscars=3>, 
#<struct Actor name="Adrien Brody", oscars=1>]

max_oscars = actors.map{|actor| actor.oscars}.max
super_star = winners.find{|actor| actor.oscars == max_oscars}
# => #<struct Actor name="Marlon Brando", oscars=3>

The code within curly braces resembles what you would put in a do loop. The first enumerator, for instance, is:

|actor| actor.oscars >= 1

Ruby mixes in the Enumerable module in collection classes to offer various traversal and searching methods such as max, find, and find_all. Thus, as part of the find_all method, the previous code extracts each item that is associated with one or more Oscar awards.

Note

In this example, I did not explicitly create the Actor class and its accessors. Instead, I used the Struct class to generate the Actor class. Read the Ruby documentation to learn more about this interesting way to generate simple classes.

In Cocoa, filtering and searching uses the NSPredicate class. As the Cocoa documentation explains quite well, this class is used to define logical conditions that constrain a search either for a fetch or for in-memory filtering. However, NSPredicate can be used only on objects that implement the key-value coding (KVC) protocol, which can be done manually on custom objects.

Here are a few examples using NSPredicate:

framework 'Foundation'
predicate = NSPredicate.predicateWithFormat("SELF IN %@", ['Ninh', 'Hongli'])
predicate.evaluateWithObject "Matt"
# => false

filter = NSPredicate.predicateWithFormat("SELF beginswith[c] 'm'")
['Matt', 'Mike', 'Nate'].filteredArrayUsingPredicate(filter)
# => ["Matt", "Mike"]

Undo/Redo

Undo and redo are common patterns in applications that revert the state of an object. Before the user changes the state of an object, the object registers the initial state as well as the method called on the object. This way, the change can be undone and redone.

The NSUndoManager class is implemented in the Foundation framework because executables other than applications might want to revert changes to their states.

Application Kit also implements undo and redo in its NSTextView class, making it available to all its subclasses.

Here is an example implementing undo/redo on a player class:

framework 'Foundation'

class Player
  attr_accessor :x, :y

  def initialize
    @x = @y = 0
  end

  def undo_manager
    @manager ||= NSUndoManager.alloc.init
  end

  def left
    undo_manager.prepareWithInvocationTarget(self).right
    @x -= 1
  end

  def right
    undo_manager.prepareWithInvocationTarget(self).left
    @x += 1
  end
end

And now if we used the code, here is what it would look like:

>> lara = Player.new
=> <Player:0x200267c80 @y=0 @x=0>
>> lara.undo_manager.canUndo
=> false # normal since we did not do anything yet
>> lara.left
=> -1
>> lara.x # -1
=> -1
>> lara.undo_manager.canUndo
=> true # now we can undo, so let's try
>> lara.undo_manager.undo # undo back to initial position
=> #<NSUndoManager:0x200257560>
>> lara.x
=> 0
>> lara.undo_manager.canUndo
=> false # we can't do any more undoing
>> lara.undo_manager.canRedo
=> true # however we can redo what was just undone
>> lara.undo_manager.redo # redo to before we called undo
=> #<NSUndoManager:0x200257560>
>> lara.x
=> -1

User’s Preferences

Foundation offers a convenient way to store a user’s preferences via the NSUserDefaults class. Preferences are saved in a shared database where developers can save and load keyed settings using some primitive objects. Here is a quick example that saves a token in the user’s preferences. Keep on reading after the example to see how to save other object types, such as arrays and hashes, and search for a preference key:

framework 'Foundation'

def set_api_token(token)
  NSUserDefaults.standardUserDefaults['oreilly.api_token'] = token
  NSUserDefaults.standardUserDefaults.synchronize # force sync
  api_token
end

def api_token
 NSUserDefaults.standardUserDefaults["oreilly.api_token"]
end

if api_token.nil?
  puts "The API token has not been set yet, please enter it now:"
  cli_token = gets
  set_api_token(cli_token.strip)
  puts "API token set, thank you!"
else
  puts "Currently stored API token: #{api_token}"
end

After saving this code in a file called api_pref.rb, run it from the command line. The output will look like the following:

$ macruby api_pref.rb
The API token has not been set yet, please enter it now:
# typed my token: 42sosayweall42
API token set, thank you!

Running it a second time will skip the token prompt:

$ macruby api_pref.rb
Currently stored API token: 42sosayweall42

Let’s jump to macirb and play with the preferences:

$ macirb --simple-prompt
>> require 'api_pref.rb'
Currently stored API token: 42sosayweall42
=> true
>> NSUserDefaults.standardUserDefaults.removeObjectForKey('oreilly.api_token')
=> #<NSUserDefaults:0x2002362a0>
>> api_token
=> nil
>> set_api_token 'macruby is awesome'
=> "macruby is awesome"
>> api_token
=> "macruby is awesome"
>> NSUserDefaults.standardUserDefaults.dictionaryRepresentation.keys.grep /oreilly/
=> ["oreilly.api_token"]
>> NSUserDefaults.standardUserDefaults['oreilly.owned_books'] = 
[{'topic' => 'macruby',
   'isbn' => '9781449380373'}]
>> NSUserDefaults.standardUserDefaults['oreilly.owned_books'].first['isbn']
=> "9781449380373"

Note

As shown here, we can delete preferences using NSUserDefaults.standardUserDefaults.removeObjectForKey, as well as access all the preferences using NSUserDefaults.standardUserDefaults.dictionaryRepresentation, which returns a Hash.

Finally, we set a more complex preference: an array containing a hash. However, the storage is limited to objects supported by the property lists, that is, objects of the following class families: NSData, NSString, NSNumber, NSDate, NSArray, and NSDictionary.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required