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 String
s
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 float
s
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.