Camlkit provides OCaml bindings to the following Cocoa frameworks:
- Foundation (all platforms)
- AppKit (macOS and GNUstep)
- UIKit (iOS and Mac Catalyst)
- Core{Animation, AutoLayout, Data, Foundation, Graphics, Image, Text, Video} (iOS and macOS)
- WebKit, SpriteKit, CloudKit, NaturalLanguage, Vision, Photos, FSEvents, Dispatch (aka Grand Central Dispatch) (iOS and macOS)
- Using the classes and objects from the above Cocoa frameworks and defining new Cocoa classes can be done from the comfort of OCaml. No need to write wrappers manually in C or Objective-C.
- Accessing the functionality of other Cocoa frameworks is possible via the Objective-C runtime API bindings. Framework bindings can also be generated using camlkit-bindings-generator.
- Cocoa object lifetimes on the OCaml side can be managed by the OCaml GC.
- GUI object hierarchies can be created either programmatically or visually using Interface Builder in Xcode.
- An Xcode project is not required. A complete macOS or iOS application can be developed entirely in OCaml.
The fastest way to get started developing an iOS app is to use a starter project template. Two iOS starter templates are provided:
For macOS, there is a starter project template and a few sample programs demonstrating different ways to organize an application.
To give you a taste of what a program in Camlkit looks like, here is a "Hello World" iOS application:
open UIKit
open Runtime
module AppDelegate = struct
let show_hello =
UIApplicationDelegate.application'didFinishLaunchingWithOptions' @@
fun _self _cmd _app _opts ->
let screen_bounds =
UIScreen.self |> UIScreenClass.mainScreen |> UIScreen.bounds in
let win =
UIWindow.self |> alloc |> UIWindow.initWithFrame screen_bounds
and vc = UIViewController.self |> alloc |> init
and label = UILabel.self |> alloc |> init in
let view = vc |> UIViewController.view in
label |> UIView.setFrame screen_bounds;
label |> UILabel.setText (new_string "Hello from OCaml!");
label |> UILabel.setTextColor (UIColor.self |> UIColorClass.systemBlackColor);
label |> UILabel.setTextAlignment _UITextAlignmentCenter;
view |> UIView.setFrame screen_bounds;
view |> UIView.setBackgroundColor (UIColor.self |> UIColorClass.systemBackgroundColor);
view |> UIView.addSubview label;
win |> UIWindow.setRootViewController vc;
win |> UIWindow.makeKeyAndVisible;
true
let define () =
Class.define "AppDelegate"
~superclass: UIResponder.self
~methods: [ show_hello ]
end
let main () =
let _ = AppDelegate.define ()
and argc = Array.length Sys.argv
and argv =
Sys.argv
|> Array.to_list
|> Objc.(CArray.of_list string)
|> Objc.CArray.start
in
_UIApplicationMain argc argv nil (new_string "AppDelegate") |> exit
let () = main ()
If you are familiar with Cocoa development in Objective-C or Swift, transferring your knowledge to Camlkit should be straightforward. Let's introduce some constructs by comparing the equivalent Objective-C and OCaml code.
-
Creating basic objects
Objective-C:
[NSObject new]; [[NSString alloc] initWithUTF8String: "Hello"]; [NSString stringWithUTF8String: "Hello"];
OCaml (showing multiple equivalent constructs):
NSObjectClass.new_ NSObject.self _new_ NSObject.self NSString.self |> NSObjectClass.alloc |> NSString.initWithUTF8String "Hello" alloc NSString.self |> NSString.initWithUTF8String "Hello" NSString.self |> NSStringClass.stringWithUTF8String "Hello" new_string "Hello"
NOTE: To print a NSString in utop:
nsstr |> NSString._UTF8String |> print_string
-
Defining a new Cocoa class
Objective-C:
@interface MyClass : NSObject { id myVar; } @property (retain) id myProp; - (void)myMethodWithArg1:(id)arg1 arg2:(id)arg2; @end @implementation MyClass - (void)myMethodWithArg1:(id)arg1 arg2:(id)arg2 { // method implementation } @end
OCaml:
Class.define "MyClass" ~ivars: [ Ivar.define "myVar" Objc_type.id ] ~properties: [ Property.define "myProp" Objc_type.id ] ~methods: [ Method.define ~cmd: (selector "myMethodWithArg1:arg2:") ~args: Objc_type.[id; id] ~return: Objc_type.void @@ fun self cmd arg1 arg2 -> (* method implementation *) ]
NOTE: The
~args
parameter includes only the explicit argument types. The number of arguments is the same as the number of:
in the selector. If your method does not accept arguments, the~args
parameter still has to be provided:Objc_type.[]
(or its synonymObjc_type.noargs
). -
Memory management
Objective-C objects use reference counting to manage memory. Since we are not compiling Objective-C source code, we cannot leverage Automatic Reference Counting (ARC). We have to use manual retain-release, as well as override the
dealloc
method when appropriate to release the objects owned by our classes.Objective-C objects that are referenced from the OCaml side can leverage the OCaml garbage collector to automatically receive the
release
message when the OCaml reference is garbage collected. This is achieved by thegc_autorelease
runtime function.NSString
objects created withnew_string
will be auto-released by the OCaml GC. -
Using frameworks when bindings are not available
When bindings for the framework you need are not available, you can generate the bindings yourself using the tools from camlkit-bindings-generator.
Another option is to use the lower-level functionality of the Objective-C runtime. The runtime functions enable you to get a hold of an arbitrary class by name and send it an arbitrary message, eg:
let a_class = Objc.get_class "AClassThatINeed" in let an_instance = alloc a_class |> init in msg_send (selector "anArbitrarySelector") ~self: an_instance ~args: Objc_type.[] ~return: Objc_type.void
-
Sending a message to the superclass
Eg,
viewDidLoad
:Objective-C:
- (void)viewDidLoad { [super viewDidLoad]; ... }
OCaml:
let viewDidLoad self cmd = msg_super ~self cmd ~args: Objc_type.[] ~return: Objc_type.void; ...
NOTE: Method implementations in OCaml always start with two parameters which are implicit in Objective-C: self - a pointer to the object; cmd - the current selector
-
Using blocks
Global blocks are supported. Here is an example of creating a
UIButton
using aUIAction
with a block handler:Block.make ~args: Objc_type.[id] ~return: Objc_type.void (fun block action -> (* block implementation *)) |> (UIAction.self |> UIActionClass.actionWithHandler) |> (UIButton.self |> UIButtonClass.systemButtonWithPrimaryAction)
NOTE: The OCaml block handler function receives the block as the first parameter, which in Objective-C is an implicit parameter.
Some documentation (particularly for the runtime library) can be generated by
running make doc
. It will be available in _build/default/_doc/_html/index.html
The framework bindings follow a regular naming pattern, so if you know the Objective-C method you want to call, figuring the name of the OCaml function should be easy. Read the Apple documentation for the classes and methods of interest. All books on iOS and macOS development in Objective-C are directly applicable.
Some usefull sources you may wish to examine include:
- Objective-C runtime bindings and basic functionality
- Representation of Objective-C types in OCaml
- Usage examples
- The Ctypes documentation
For iOS and Mac Catalyst development you will need to set up a cross-toolchain from opam-cross-ios.
Framework bindings can be generated using tools from camlkit-bindings-generator.