Skip to content

Instantly share code, notes, and snippets.

@waywardmonkeys
Last active August 29, 2015 13:55
Show Gist options
  • Save waywardmonkeys/8728860 to your computer and use it in GitHub Desktop.
Save waywardmonkeys/8728860 to your computer and use it in GitHub Desktop.
Design notes for type-safe and easier Objective C / Dylan bridging
// An instance of an Objective C object gets wrapped:
define abstract class <objc/instance> (<object>)
constant slot raw-instance :: <machine-word>,
required-init-keyword: instance:;
constant each-subclass slot instance-objc-class :: <objc/class>;
end;
// However, we want them to be type safe and we want to
// to be able to dispatch on their arguments, so we will
// define a shadow Dylan class for each Objective C
// class:
define constant $NSObject = objc/get-class("NSObject");
define class <ns/object> (<objc/instance>)
inherited slot instance-objc-class, init-value: $NSObject;
end;
objc/register-shadow-class($NSObject, <ns/object>);
// This should come from a definer macro, which can
// also take the Objective C class hierarchy into account:
define objc-shadow-class <ns/object> => NSObject;
define objc-shadow-class <ns/value> (<ns/object>) => NSValue;
define objc-shadow-class <ns/number> (<ns/value>) => NSNumber;
// We call this objc-shadow-class because we're going to want to
// use 'define objc-class' for when we're creating a new class
// that is visible to the Objective C world (subclassing an Objective
// C class).
// How to deal with Objective C protocols? We wrap them in double
// angle brackets to indicate that they're a protocol, since some
// protocols have the same name as a class (like <<ns/object>>):
define objc-protocol <<ns/object>>;
define objc-protocol <<ns/copying>>;
define objc-protocol <<ns/secure-coding>>;
// This expands to:
define abstract class <<ns-copying>> (<object>)
end;
// And now, more accurate definitions of <ns/object> and <ns/value>:
define objc-shadow-class <ns/object> (<objc/instance>, <<ns/object>>)
=> NSObject;
define objc-shadow-class <ns/value> (<ns/object>, <<ns/copying>>,
<<ns/secure-coding>>)
=> NSValue;
// Now, we have the ability to do type-safe dispatch on
// Objective C object instances.
// When we create an instance of a class and want the Dylan-side
// binding, we need to do a bit of work:
define function objc/make-instance
(raw-instance :: <machine-word>)
=> (objc-instance :: <objc/instance>)
let objc-class = objc/instance-class(raw-instance);
// This uses the mapping set up above by objc/register-shadow-class.
let shadow-class = objc/shadow-class-for(objc-class);
make(shadow-class, instance: raw-instance)
end;
// Unfortunately, that doesn't give the Dylan compiler all
// of the type info that we have available. Is there
// a better way?
// One thing that we can do is to auto-generate a method
// from the objc-shadow-class-definer:
define method as (class == <ns/value>, objc-instance)
=> (objc-instance :: <ns/value>)
objc-instance
end;
// Next up, sending messages to an object. We'd like for
// this to be simpler. It starts out looking like this:
// This is the message (selector) we are sending. This
// is essentially identifying the method that we want
// to run
let sel/alloc = objc/register-selector("alloc");
// We get back a <raw-machine-word>, so we wrap
// it in a <machine-word> which is a Dylan type
// that can hold a pointer on the machine.
let raw-instance
= primitive-wrap-machine-word
(%objc-msgsend ($NSOBJECT.as-raw-class, sel/alloc.as-raw-selector)
()
=> (obj :: <raw-machine-word>)
()
end);
objc/make-instance(raw-instance)
// There's a lot going on there! We have to deal with the boundary
// between high level Dylan types and the raw machine-level types
// in several places (both as-raw-* to drop from high level to raw
// machine level and using primitive-wrap-machine-word to go from
// a raw machine level type back up to the Dylan high level type.
// Also, we have to specify the type signature for the selector
// (method) each time.
// We'd like for it to look more like this:
objc-msgsend($NSOBJECT, sel/alloc)
// To do this, we'll want a macro that expands the simplified
// form into the complex form.
// Maybe we can do it like this:
define objc-selector sel/alloc
parameter target :: <machine-word>;
result objc-instance :: <machine-word>;
selector: "alloc";
end;
// This would expand into:
define constant sel/alloc = objc/register-selector ("alloc");
define inline-only function %send-sel/alloc
(class :: export-type-for (<machine-word>))
=> (objc-instance :: import-type-for (<machine-word>));
objc-msgsend-body sel/alloc
(selector "alloc"),
(options #"selector", "alloc"),
(result objc-instance :: <machine-word>),
(parameter class :: <machine-word>, #"call-discipline" #"input")
end
end;
// And now, objc-msgsend can be a simple that expands to:
%send-sel/alloc($NSOBJECT)
@BarAgent
Copy link

An additional benefit of a Dylan-side shadow class is that eventually you can use it as a way to override Objective C methods. Back in the day, my design for this kind of thing was like this:

// Sub-class of NSView with Dylan-specific instance variables. 
define class <my-subview> (<obj-c-bridge>) 
  slot my-slot; 
end class; 

// An overridden NSView method. 
define method draw-rect (view :: <my-subview>, rect :: <rect>) 
  view.frame := rect;          // "frame" is inherited Obj-C variable 
  view.super-set-background ($red); 
end method; 

// Link the <my-subview> class to the Objective C runtime. This macro 
// will add methods and slots to the Dylan class. I think the class 
// should contain <function> slots to mimic the method/variable combo 
// of Obj C classes and allow delegates. 

define obj-c-bridge <my-subview> ("NSView") 
  // Declare Obj C super-class functions to use, but not override. 
  inherit "- setBackground:(NSColor *)" as super-set-background; 
  // Exported Dylan functions     
  export draw-rect as "- drawRect:(NSRect)"; 
end; 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment