Last active
August 29, 2015 13:55
-
-
Save waywardmonkeys/8728860 to your computer and use it in GitHub Desktop.
Design notes for type-safe and easier Objective C / Dylan bridging
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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: