I spent a good chunk of time last week working on making Usertility work on operating systems other than Windows. The target last week was OS X. In this post, I’ll share a couple of things I learned about working with OS X in Delphi.

Specifically, I’ll talk about using objects directly from the operating system, in order to get access to features that FireMonkey doesn’t directly provide.

Note: This is obviously not cross-platform, since we’re going to be creating platform-specific code. If you’re going to use these techniques in a cross-platform application, you’ll need to rewrite this code for each platform, using $IFDEF to separate code for different platforms. In the case of OS X, you’ll want to enclose the platform-dependent code in {$IF DEFINED(MACOS)}..{$IFEND} pairs.

Rule #1 – The object you have is not the object you want

[Most of] The OS X API is object-oriented, which is cool. The platform API calls often require you to pass an object reference, known as an id in Objective-C parlance. Delphi is also object-oriented, and you can get those OS X objects as Delphi objects pretty easily (I’ll show you how a little below). But here’s the trick: The Delphi object is not the Objective-C id. That will make a little more sense below, but just remember that. If you have an object in Delphi, and you need to pass it to an OS X API, you need to get the id of the object.

With that out of the way, let’s look at doing something common direct from the API: Making an HTTP request.

Our quest: An HTTP request without using Indy

The Indy networking components are included with Delphi XE6, and work in applications created with the FireMonkey framework. Indy is kind of polarizing through, and while many developers use it and are very successful, others prefer not to use it. I’m not making any judgement of Indy, or anyone’s preferences, but for this demo, let’s assume that we want to make an HTTP request without any dependencies other than the operating system.

Step 1: How do we make an HTTP request in OS X?

The first step is figuring out what the OS X APIs would be in the first place. A little web searching leads us to the API docs for NSURLConnection , a class specifically designed for this task.

Looking at the API, we see this list of “tasks:”

This is what we Delphi programmers would call the “methods” of the NSURLConnection class. The ones that are prefixed with a ‘-‘ symbol are the “instance” methods, and the ones prefixed with a “+” symbol are the “class” methods.

To keep this simple, we’ll send send a synchronous request, which means that we’re going to send our HTTP request and then wait for a response. (You shouldn’t really do this in your application’s main thread, because it will block your UI. For real-world use, either use a synchronous request in a background thread, or use an asynchronous request). NSURLConnection has a “method” called + sendSynchronousRequest:returningResponse:error:

Let’s look at the documentation for that method:

This gives us a little more information:

It returns an NSData object

It takes 3 parameters: an NSURLRequest, which is in object that represents the URL to load an reference to an NSURLResponse variable, which the method will use as an OUT parameter a reference to an NSError variabls, which the method will use as an OUT parameter. This can be NULL (or nil in Delphi terms)



How do we call this method in Delphi?

To call this class method, we need two things: First, we need a reference to the NSURLConnection class, and second, we need the parameters.

So, here’s the next big idea:

Getting an OS X class reference in Delphi

Many of the OS X classes, including NSURLConnection, are defined in the Macapi.Foundation unit. Add Macapi.Foundation to your uses clause.

If we search Macapi.Foundation.pas for NSURLConnection, we find three apparently useful types:

NSURLConnectionClass

NSURLConnection

TNSURLConnection

We know that we want the sendSynchronousRequest method, and that appears to be defined in NSURLConnectionClass, but NSURLConnectionClass is an interface, and we can’t call class methods on an interface, so how do we get a reference to that interface?

The key here is TNSURLConnection. It’s the “bridge” between the Delphi interfaces that represent the class, and the underlying OS X classes. We can use TNSURLConnection.OCClass to get a reference to NSURLConnectionCass.

IMPORTANT: This is a pattern throughout Delphi. If you want to call a class method on an ObjectiveC class, use T<WhateverTheClassNameIs>.OCClass.callTheMethod(...) (I’m pretty sure OCClass stands for Objective-C Class).

So, we can call TNSURLConnection.OCClass.sendSynchronousRequest(???) . We just need to fill in the ???

The first parameter is an NSURLRequest. How do we get one of those?

Getting an OS X Object instance in Delphi

Most Objective-C classes don’t use a traditional constructor the way we’re used to in Delphi. Instead, they often have class methods that return an instance of the class. (If they do use a traditional constructor, it’s actually a pair of methods called alloc and init).

NSURLRequest has a class method +(id)requestWithURL: that returns an NSURLRequest. We’ll use that as our constructor, using the same pattern as above:

//var URLRequest: NSURLRequest URLRequest := TNSURLRequest.OCClass.requestWithURL(???); //We'll figure out the parameter below

This looks good, but in fact, it’s wrong!

Remember rule #1: The object you want isn’t the object you have. The requestWithURL method returns the id of the Objective-C class. We can’t call Delphi methods on this id. We need the Delphi interface that wraps around this id. Again, TNSURLRequest is the bridge. We can call the wrap method of TNSURLRequest to get the Delphi wrapper interface for the Objective-C id:

URLRequest := TNSURLRequest.Wrap(TNSURLRequest.OCClass.requestWithURL(??)); //This works

IMPORTANT: This is the other side of the pattern. To get a Delphi interface for an Objective-C class, use T<WhateverTheClassNameIs>.Wrap(ObjC_id);

Of course, now the rabbit hole goes deeper. We need that parameter to requestWithURL, which is an NSURL.

(At this point, we start to get a little frustrated: I just want to give it a string with a URL and get back the data at that URL! Never fear; we’re almost there.)

Looking at the API docs for NSURL, we find a glimmer of hope: NSURL has a class method +(id)URLWithString: . Hopefully that will let us create a url from our string and finally put this all together to get a result.

And indeed, we can. First attempt:

//var URL: NSURL URL := TNSUrl.Wrap(TNSURL.OCClass.URLWithString('http://www.twodesk.com'));

This almost works, except it doesn’t compile. There’s an error “There is no overloaded version of URLWithString that can be called with these arguments.”

Remember rule #1 again. URLWithString takes an NSString, and we’re trying to pass it a Delphi string. The object we have is not the object we want. Thankfully, there’s a simple utility method to convert a Delphi string into an NSString object. It’s in the Macapi.Helpers unit, and it’s called StrToNSStr :

URL := TNSURL.Wrap(TNSURL.OCClass.URLWithString(StrToNSStr('http://www.twodesk.com')));

Finally! We have a URL object. Let’s put it all together:

var URL: NSURL; URLRequest: NSURLRequest; Data: NSData; Response: Pointer; begin URL := TNSURL.Wrap(TNSURL.OCClass.URLWithString( StrToNSStr('http://www.twodesk.com'))); URLRequest := TNSURLRequest.Wrap(TNSURLRequest.OCClass.requestWithURL(URL)); Data := TNSURLConnection.OCClass.sendSynchronousRequest(URLRequest, @Response, nil); //Do something with the data end;

The last step is to do something with the data:

Using NSData

NSData is a general-purpose class for representing any generic data. Its two most important members are NSData.bytes , which is a pointer to the actual data, and NSData.length , which is the size of the data in bytes.

In the case of an NSData object returned by TNSURLConnection in the example above, bytes is UTF-8 encoded text. This means we can directly access the bytes as a PAnsiChar in Delphi:

//var S: string S := PAnsiChar(Data.bytes);

Put it all together

I created a FireMonkey HD Desktop app, and put a TButton (Button1) and TMemo (Memo1) on the form. Then, I changed the target platform to OS X (If you don’t change the target platform, none of the platform-specific code will compile). Here’s the code for the Button1 click event:

procedure TForm1.Button1Click(Sender: TObject); var URL: NSURL; URLRequest: NSURLRequest; Data: NSData; Response: Pointer; begin URL := TNSURL.Wrap(TNSURL.OCClass.URLWithString( StrToNSStr('http://www.twodesk.com'))); URLRequest := TNSURLRequest.Wrap(TNSURLRequest.OCClass.requestWithURL(URL)); Data := TNSURLConnection.OCClass.sendSynchronousRequest(URLRequest, @Response, nil); Memo1.Lines.Text := PAnsiChar(Data.bytes); end;

Recap

To get direct access to the static methods of an Objective-C class in Delphi: T<ObjCClassName>.OCClass.<StaticMethodName>

To get a Delphi interface to an instance of an Objective-C class: T<ObjCClassName>.Wrap(<ObjC_id>)

(We didn’t use this in the code above, but for the sake of completeness) To get the Objective-C id from a Delphi interface: (<DelphiInterface as ILocalObject).GetObjectID