An Objective-C/Perl Bridge

Collections in Cocoa

Unlike Perl, which its native array and hash collection types, Cocoa defines a number of object-oriented collection classes. While support for "toll-free" bridging of Perl collections is now included in CamelBones, it's still useful to be aware of the methods used in the Cocoa collection classes.

The Cocoa collection classes are:

The Cocoa collection classes each define a immutable, or read-only, class and a corresponding mutable, or read-write class. Most of them can read and write their contents to or from a file on disk, add, remove, locate, and sort their contents, compare their contents with other collection objects, or create an enumerator object for iterating over each element within the collection.

Creating collection objects

Collection objects are created with the usual alloc/init combination used to create Cocoa objects. Here are some examples:

# Create a mutable array
my $arr = NSMutableArray->alloc->init;

# Create a mutable dictionary (hash)
my $dict = NSMutableDictionary->alloc->init;

Working with NSArray and NSMutableArray

# Add an item to a mutable array
$arr->addObject("This is a string");

# Count items
my $count = $arr->count;

# Insert an object into a mutable array at a specific position
$arr->insertObject_atIndex("This is now the first string", 0);

# Get the first object in an array
my $obj = $arr->objectAtIndex(0);

Working with NSDictionary and NSMutableDictionary

# Add an item to a mutable dictionary
$dict->setObject_forKey("This is a string", "keyValue");

# Count items
my $count = $dict->count;

# Get the object associated with a specific key
my $obj = $dict->objectForKey("keyValue");

Using enumerator objects

Most collection objects can create an NSEnumerator object with the objectEnumerator() method. NSDictionary also has the keyEnumerator() method, which iterates over key values. Here's a simple example that logs all the objects in an NSArray collection to the console:

# Create an enumerator object
my $enum = $arr->objectEnumerator;

# Loop through the objects in the enumerator
while (my $obj = $enum->nextObject) {
    NSLog($obj);
}

Toll Free Bridging

As of 0.2.2, toll free bridging has been introduced for NSArrays, NSDictionaries, and their mutable variants. What this means is efficient, seamless bridging of arrays and hashes to their Cocoa equivalents.

Array and hash references as arguments

If a method is declared as taking an NSArray or NSMutableArray as one of its arguments, you can pass a reference to a Perl array instead. The same is true for NSDictionary or NSMutableDictionary arguments, where you can use a reference to a Perl hash. Some examples:

# Create a new NSArray object from a Perl array reference
my $arrayRef = [1,2,3];
my $arr = NSArray->arrayWithArray($arrayRef);

# Create a new NSDictionary from an anonymous hash reference
my $dict = NSDictionary->dictionaryWithDictionary({
    'foo' => {
        'bar' => [ 0,2,4,6 ],
        'baz' => [ 2,4,6,8 ],
    },
});

Arrays and hashes as return values

If a method that returns an NSArray or NSMutableArray is called in list context, a Perl array is returned. Similarly, if a method that returns an NSDictionary or NSMutableDictionary is called in list context, a Perl hash is returned. In scalar context, an object reference is returned as above. Some examples:

# Returning an NSArray as a Perl array
my @arr = NSArray->arrayWithArray([0,1,2,NSNull->null]);

# Normal Perl functions work as expected
print 'Elements in @arr = ', scalar(@arr), "\n";
print 'Last index in @arr = ', $#arr, "\n";

print 'Elements in @arr:', "\n";
foreach my $elem (@arr) {       # Interate over each object
    print '  ', ref($elem) ? $elem->description : $elem, "\n";
}
print "\n";

my $manager = NSFileManager->defaultManager;
my %attrs = $manager->fileAttributesAtPath_traverseLink('/Applications', 0);

# Normal Perl functions work as expected
my @keys = keys(%attrs);        # Get all the key objects

print 'Keys & values in %attrs:', "\n";
foreach my $key (keys %attrs) { # Iterate over the keys
    my $obj = $attrs{$key};
    print '  ', $key, ' = ', ref($obj) ? $obj->description : $obj, "\n";
}

Mutable vs. immutable

Keep in mind that Cocoa collections, unlike Perl arrays and hashes, have both mutable and immutable variants. If you try to modify an immutable collection, you'll get an error.

Methods and forced list context

Also keep in mind that function and method calls force list context for any nested function or method calls found in their arguments. For example, the following does not work as you might expect it would:

# This DOES NOT WORK
NSLog(NSArray->arrayWithArray([0,1,2,3]));

NSLog knows how to handle Cocoa objects, and for non-string objects it automatically calls their description() method to get a text representation. You might expect it to do the same for the NSArray object created here - but it doesn't. What happens is that, because its within the parameter list for NSLog, the call to arrayWithArray() is made in list context, and returns a Perl array instead of a Cocoa object.

To work around this, you can either store the results of the method call in a scalar variable, and pass the variable to the method you want to call, or you can use Perl's scalar() function to force the inner method call to be made in scalar context:

# Correct
my $msg = NSArray->arrayWithArray([0,1,2,3]);
NSLog($msg);

# Correct, using scalar()
NSLog(scalar(NSArray->arrayWithArray([0,1,2,3])));