Skip to content
/ camlkit Public

OCaml bindings to macOS and iOS Cocoa frameworks

Notifications You must be signed in to change notification settings

dboris/camlkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Overview

Camlkit provides OCaml bindings to the following Cocoa frameworks:

Features

  • 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.

Getting started

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 ()

Introduction

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 synonym Objc_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 the gc_autorelease runtime function. NSString objects created with new_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 a UIAction 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.

Documentation

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:

Related projects

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.