See: Description
| Package | Description |
|---|---|
| ca.weblite.objc | |
| ca.weblite.objc.annotations | |
| ca.weblite.objc.mappers | |
| com.sun.jna |
This class library enables you to use any Objective-C library directly from Java without having to generate any Java class stubs. It is built on top of the powerful JNA library, and interacts with Objective-C through the C functions in the Objective-C runtime.
The following example shows the bridge being used to display an NSOpenPanel, and add a Java class as its delegate to listen for changes to the user selection:
Steve Hannah @shannah78
The Java Cocoa bridge can be used at three levels of abstraction:
Runtime.INSTANCE singleton object
to access these functions through the Java Cocoa Bridge.
RuntimeUtils class and its
set of static methods.
Client, Proxy,
and NSObject classes primarily.
The low-level API is nothing more than a JNA interface for the Objective-C runtime functions. If you are familiar with these functions, and you are comfortable calling them directly, then you are free to call this API directly. In most cases, however, you will likely want to use a higher level API that handles a bit more of the plumbing.
The following is a sample from the RuntimeTest Unit test that makes use of this low-level API:
A few things to comment on here:
com.sun.jna.Pointer class is used throughout this API to represent
a C pointer.
const *char) expect to receive a java string as a parameter. Messages that
take NSStrings, however, will not accept Strings of this type. You need to provide a pointer to an NSString object
in these cases.
The mid-level API consists of a set of static convenience methods around commonly used functions of the Objective-C runtime API. These include:
RuntimeUtils.sel() and RuntimeUtils.selName()
methods.
RuntimeUtils.cls() and RuntimeUtils.clsName()
methods.
RuntimeUtils.msg() variants.Even if your are working with the high-level API, you will still likely need to use the mid-level API in some instances. For convenience, you may want to
use a static import of all of the RuntimeUtils static methods so that you can access them quickly and easily:
import static ca.weblite.objc.RuntimeUtils.*;
The following sample code is taken from one of the unit tests and gives a good example of how to use the mid-level API to interact with the Objective-C runtime:
In order to provide a contrast with the low-level API, this example performs the same logic as the low-level
example above. It makes use of the RuntimeUtils.sel() function
to obtain a selector rather than longer Runtime.INSTANCE.sel_getUid(). It also uses the cls() and clsName()
functions to go back and forth between a class object and its class name.
However, we can shorten this still more. The msg() function includes a variant that accepts Strings
as their first two arguments. If the first argument is a String, then it is assumed to be a class name. If
the second argument is a String, then it is assumed to be a selector name. Using this knowledge we can
compress the above code significantly:
The RuntimeUtils class also includes a few methods that act as a link and foundation
to the higher level APIs. It includes a variant of the msg() function that can optionally perform type mapping
of input arguments and their return values.
The method signature is as follows:
Object msg(boolean coerceOutput,
boolean coerceInput,
Pointer receiver,
Pointer selector,
Object... args)
Note, that there are variants that allow you to specify the receiver or selector as strings also.
If you pass true for both coerceOutput and coerceInput, then the method will
perform type mapping on the input args and the return value so that you are able to pass
more familiar Java objects in. It will also perform type-checking to ensure that the correct
variant of objc_msgSend() is sent to the Objective-C runtime (e.g. messages that return floats
and doubles need to use the objc_msgSend_fret() function, and messages
that return structures need to use the objc_msgSend_sret() function).
The type mapping is minimal and non-complex. There are just a few key mappings that are performed by this method:
String objects to Objective-C NSString objects for arguments
that are passed to messages that are expecting NSStrings. It uses the method signature
of the method to determine if the transformation needs to be made. I.e. if a Java String
is passed as an argument to a message that is expecting an Objective-C object (i.e. type encoding "@"),
then the string will be converted to an NSString automatically.
Proxy objects into Pointer objects for arguments
that are expecting a Pointer or an Objective-C object.
objc_msgSend* depending on the return type of
the message (as specified in the Objective-C message signature).
NSString return values to Java String objects.
NSObject return values in Proxy objects so that
they can be used more easily. It performs this conversion in a smart way using the Proxy.load()
method to make sure that there is ever only one instance of a Java proxy for each instance of an Objective-C
object. I.e. If a Proxy object already exists for a particular Objective-C object, then this method
will look up the existing Proxy object and return that. Otherwise it creates a new Proxy object
to wrap the return value and registers this proxy with the central proxy registry.
com.sun.jna.Structure
The following is some sample code taken from one of the unit tests to demonstrate how this variant of msg() can
be used to enable automatic type mapping in the return values of Objective-c messages.
Now that you see the foundation upon which the Java/Objective-C bridge sits, we can jump straight into the higher-level object oriented API. This API consists of three main classes that you'll wish to use.:
Client - An object that allows you to send messages to the Objective-C runtime. You'll generally
use this to instantiate new Objective-C objects and obtain Proxies to them.
Proxy - A wrapper around an Objective-C object that enables you to easily send messages
to the native object. It includes convenience methods for sending messages that return various types. E.g. the sentInt()
method sends a message to which you expect an int to be returned. It returns a Java int so that
you don't need to perform any ugly type casting.
NSObject - A Java wrapper around an Objective-C object that enables two-way communication. Objective-C
can call methods on this Java class, and you are able to send messages to the object's native Peer from Java. This is a subclass
of Proxy so that it includes all of the same capabilities for sending messages. Typically you would subclass
NSObject if you need to create a delegate for an objective-C object. You can define Java methods
in your subclass that can be used in Objective-C via the Msg annotation.
The following snippet is taken from a unit test. It shows the use of the Client class and Proxy
class to create an NSMutable array and add some strings to it.
The NSObject class can be extended to define your own Java classes that can be called from Objective-C.
You specify which methods can handle Objective-C messages using the Msg annotation. There
is an example at the top of the page that uses this method to add a delegate to the NSOpenPanel dialog.