Skip to content

OCaml bindings to macOS and iOS Cocoa frameworks

Notifications You must be signed in to change notification settings

DavidAlphaFox/camlkit

 
 

Repository files navigation

Overview

Camlkit provides OCaml bindings to the following Cocoa frameworks:

Features

  • Using the classes and objects from these Cocoa frameworks, defining new Cocoa classes, and accessing the functionality of other Cocoa frameworks (via the low-level Objective-C runtime API bindings) can be done from the comfort of OCaml. No need to write wrappers manually in C or Objective-C.
  • Cocoa object lifetimes can be managed by the OCaml GC.
  • GUI object hierarchies can be created either programmatically or visually using Xcode's Interface Builder.
  • 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 the starter project template. Follow the instructions there to set up your environment. Build the library with make and open the Xcode project with make open.

Sample programs

A few sample programs are provided in the examples repository. To give you a taste of what a program in Camlkit looks like, here is a "Hello World" iOS application:

open Foundation
open Uikit
open Uikit_globals
open Runtime

module AppDelegate = struct
  let show_hello _self _cmd _app _opts =
    let screen_bounds =
      UIScreen._class_ |> UIScreen.C.mainScreen |> UIScreen.bounds
    in
    let win =
      UIWindow._class_ |> NSObject.C.alloc
      |> UIWindow.initWithFrame screen_bounds
    and vc = UIViewController._class_ |> NSObject.C.new_
    and label = UILabel._class_ |> NSObject.C.new_
    in
    let view = vc |> UIViewController.view in
    view |> UIView.setFrame screen_bounds;
    view |> UIView.setBackgroundColor (UIColor._class_ |> UIColor.C.systemBackgroundColor);

    label |> UILabel.setText (new_string "Hello from OCaml!");
    label |> UILabel.setTextColor (UIColor._class_ |> UIColor.C.systemBlackColor);
    label |> UILabel.setTextAlignment _UITextAlignmentCenter;
    label |> UIView.setFrame screen_bounds;
    view |> UIView.addSubview label;

    win |> UIWindow.setRootViewController vc;
    win |> UIWindow.makeKeyAndVisible;
    true

  let _class_ = Define._class_ "AppDelegate"
    ~superclass: UIResponder._class_
    ~methods:
      [ Define._method_ show_hello
        ~cmd: (selector "application:didFinishLaunchingWithOptions:")
        ~args: Objc_t.[id; id]
        ~return: Objc_t.bool
      ]
end

let main () =
  let 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 ()

A more substantial example is available in the starter project template.

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

    NSObject.C.new_ NSObject._class_
    _new_ NSObject._class_
    NSString._class_ |> NSObject.C.alloc |> NSString.initWithUTF8String "Hello"
    alloc NSString._class_ |> NSString.initWithUTF8String "Hello"
    NSString._class_ |> NSString.C.stringWithUTF8String "Hello"
    new_string "Hello"

    To print a NSString in utop: myNSStr |> NSString._UTF8String |> print_string

  • Defining a new Cocoa class

    Objective-C:

    @interface MyClass : NSObject {
        id myVar;
    }
    - (void)myMethodWithArg1:(id)arg1 arg2:(id)arg2;
    @end
    
    @implementation MyClass
    - (void)myMethodWithArg1:(id)arg1 arg2:(id)arg2 {
        // method implementation
    }
    @end

    OCaml:

    Define._class_ "MyClass"
      ~ivars: [ Define.ivar "myVar" Objc_t.id ]
      ~methods: [
        Define._method_
          ~cmd: (selector "myMethodWithArg1:arg2:")
          ~args: Objc_t.[id; id]
          ~return: Objc_t.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_t.[]

  • Memory management

    A newly allocated object has a reference count of 1. When you want to keep an object around, you send it the retain message. This increments the reference count. When you no longer need an object, you send it the release message. This decrements the reference count. When the reference count reaches 0, the object is sent the dealloc message and its memory is reclaimed. See also autorelease and the NSAutoreleasePool class.

    Since OCaml has a garbage collector, you can leverage it to help manage the lifetimes of Cocoa objects. To this effect, we provide the gc_autorelease function, which ensures the object will be sent the release message when the OCaml reference to it is garbage collected.

  • Using objects from frameworks when bindings are not available

    The Objective-C runtime provides functions which 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
    an_instance |> msg_send (selector "anArbitrarySelector") ~args: Objc_t.[] ~return: Objc_t.void
  • Sending a message to the superclass

    Eg, viewDidLoad:

    Objective-C:

    - (void)viewDidLoad {
      [super viewDidLoad]
      ...
    }

    OCaml:

    let viewDidLoad self cmd =
      self |> msg_super cmd ~args: Objc_t.[] ~return: Objc_t.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_t.[id] ~return: Objc_t.void
      (fun block action -> (* block implementation *))
    |> (UIAction._class_ |> UIAction.C.actionWithHandler)
    |> (UIButton._class_ |> UIButton.C.systemButtonWithPrimaryAction)

    NOTE: The OCaml block handler function receives the block as the first parameter, which in Objective-C is an implicit parameter.

Documentation

At this time, the documentation of the project is lacking. 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:

Project status

The project is in active development and is still experimental. It can be considered at the alpha stage. If you are an early adopter, keep in mind that the API is subject to change.

Related projects

For iOS and Mac Catalyst development you will need to set up a cross-toolchain from opam-cross-ios.

About

OCaml bindings to macOS and iOS Cocoa frameworks

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • OCaml 100.0%