Haskell Objective-C FFI: Objective-C 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.
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.