Haskell Objective-C FFI: Objective-C Classes
Declaring classes
Importing an ObjC class has two components: (1) we declare the class type, i.e., the Haskell type of objects of that class, and (2) we import the class object; i.e., the object on which we invoke class methods.
foreign class objc "UIKit/UIKit.h UIView" C'UIView -- declares the class type
foreign import objc "UIKit/UIKit.h UIView" o'UIView :: Class -- imports the class object
The class type is implemented as a newtype of a ForeignPtr
. The type is abstract, but can be passed to and received from ObjC land; i.e., it is a foreign type in the sense of Section 3.2 of the Haskell FFI. The class object is looked up by the module initialisation code using objc_getClass
and Class
is defined in Foreign.ObjectiveC
and corresponds to the type Class
of the ObjC runtime.
Finaliser
A foreign class
declaration can optionally specify a finaliser for the objects of the declared class. By default, the finaliser sends a release
message to the finalised object. If a custom finaliser is specified, it needs to be implemented in ObjC (as per the requirements of Foreign.ForeignPtr
).
Implementing ObjC classes in Haskell
Any ObjC class implemented in Haskell requires an @interface
definition in a standard ObjC header file. Instead, of a .m
file that contains the implementation, we provide the implementation in Haskell. In Haskell, we specify in the foreign class
declaration that the curent Haskell module implements the class; thusly,
foreign class objc "MyClass.h @implementation MyClass" MyClass
For any Haskell module that implements one or more class, we generate an ObjC stub file (not unlike the C stubs generated by GHC for foreign export wrappers in the C FFI). The stub file contains the actual class implementation.
Class and instance methods
For any class or instance method of a class implemented in Haskell, we have one foreign export
statement; for example,
foreign export objc "-[MyClass doSomethingCool:]" doSomethingCool
:: MyClass -> UIView -> IO BOOL
[FIXME Need to make sure that we can determine the ObjC signature from the Haskell signature in every case.] [FIXME Shouldn't that be "-[MyClass doSomethingCool:(UIView*)]"? Or will the types be filled in somehow?]
Ivars and properties
Any ivars and properties of a class need to be defined in the header with the interface. We can import or export setters and getters of properties as any other selector. However, if the class is implemented in Haskell, we might want to have the ObjC compiler synthesize the setter and getter. We can achieve this via a @synthesize
directive in the foreign import
of the setter and getter.
foreign import objc "@synthesize myProperty -[MyClass myProperty]" myProperty
:: MyClass -> IO CInt
foreign import objc "@synthesize myProperty -[MyClass setMyProperty:]" setMyProperty
:: MyClass -> CInt -> IO ()
The synthesize directive includes the property name, as the property declaration may have specified non-default names for the setter and getter. Moreover, the directive should be duplicated by specifying it in the setter and getter (except for a readonly
property) – this is much like header file specifications are replicated across foreign declarations in the C FFI.
We provide no special support to access the ivars directly beyond the functionality already available in the C FFI for Haskell. This approach is not valid for the non-fragile runtime used in 64-bit processes and on iOS.