2019-05-29 00:03:51 -04:00
|
|
|
|
# NSWrap
|
|
|
|
|
|
|
|
|
|
Create Go language bindings for Objective-C.
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Using NSWrap, you can work with MacOS interfaces, subclasses,
|
2019-05-29 00:03:51 -04:00
|
|
|
|
library functions, protocols and delegates entirely in Go.
|
|
|
|
|
|
|
|
|
|
# Getting Started
|
|
|
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
NSWrap runs on MacOS and requires `clang` (from the XCode command line
|
|
|
|
|
tools) and the MacOS system header files.
|
|
|
|
|
|
|
|
|
|
```sh
|
2019-05-29 22:36:49 -04:00
|
|
|
|
go get git.wow.st/gmp/nswrap
|
2019-05-29 00:03:51 -04:00
|
|
|
|
```
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
The `nswrap` command line tool should now be installed in your `go/bin` path.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
Since NSWrap uses `clang` to generate an AST from Objective-C input files, you
|
|
|
|
|
will need to install XCode and its associated command line tools. Enter
|
2019-05-29 22:36:49 -04:00
|
|
|
|
`clang --version` from your terminal prompt to see if you have it installed.
|
|
|
|
|
You will also need the Objective-C header files for the
|
2019-05-29 00:03:51 -04:00
|
|
|
|
various frameworks you want to use. Look for them in
|
|
|
|
|
`/System/Library/Frameworks/*/Headers`.
|
|
|
|
|
|
|
|
|
|
## Try Out An Example
|
|
|
|
|
|
|
|
|
|
NSWrap is designed to be easy to use. To get started with an example, visit
|
|
|
|
|
your Go source directory in a terminal and enter:
|
|
|
|
|
|
|
|
|
|
```sh
|
2019-05-29 13:26:22 -04:00
|
|
|
|
cd git.wow.st/gmp/nswrap/examples/app
|
2019-05-29 00:03:51 -04:00
|
|
|
|
go generate
|
|
|
|
|
go build
|
|
|
|
|
./app
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# Basic Usage
|
|
|
|
|
|
|
|
|
|
## YAML configuration file
|
|
|
|
|
|
|
|
|
|
NSWrap takes no command line arguments. All configuration directives are
|
|
|
|
|
included in a file named `nswrap.yaml`, which must be found in the directory
|
|
|
|
|
from which NSWrap is invoked.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# nswrap.yaml example
|
|
|
|
|
|
|
|
|
|
package: MyWrapper
|
|
|
|
|
inputfiles:
|
|
|
|
|
- /System/Library/Frameworks/Foundation.framework/Headers/Foundation.h
|
|
|
|
|
|
|
|
|
|
classes:
|
|
|
|
|
- NSString
|
|
|
|
|
- NSArray
|
|
|
|
|
|
|
|
|
|
frameworks: [ Foundation ]
|
|
|
|
|
pragma [ clang diagnostic ignored "-Wformat-security" ]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Regular expressions are permitted in the names of classes, functions,
|
|
|
|
|
protocols and protocol methods, overridden superclass methods, and enums.
|
|
|
|
|
|
|
|
|
|
When invoked, NSWrap creates a subdirectory with the name of the package
|
|
|
|
|
as specified in `nswrap.yaml` or, by default, `ns` if a package name is not
|
|
|
|
|
specified.
|
|
|
|
|
In the output directory, a `main.go` file and, if required, `exports.go`,
|
|
|
|
|
will be created or overwritten.
|
|
|
|
|
|
|
|
|
|
To automatically invoke NSWrap, put a `//go:generate nswrap` comment at the
|
|
|
|
|
top of your go source file and use `go generate` to create your Objective-C
|
|
|
|
|
bindings.
|
|
|
|
|
|
|
|
|
|
NSWrap will look for Objective-C header files where directed under
|
|
|
|
|
`inputfiles` in your configuration file. CGo will also automatically
|
|
|
|
|
compile and link any Objective-C implementation (`.m`) files found in
|
|
|
|
|
this output directory, so put them in there if you are going to be
|
|
|
|
|
hand-crafting any Objective-C implementations that need to go in the same
|
|
|
|
|
package as your automatically generated bindings.
|
|
|
|
|
|
|
|
|
|
## Class and Instance Methods
|
|
|
|
|
|
|
|
|
|
NSWrap will create bindings for all classes identified in the `classes`
|
|
|
|
|
directive of the configuration file. All of the class and instance methods
|
|
|
|
|
are bound to Go and all types identified in the process are wrapped
|
2019-05-29 22:36:49 -04:00
|
|
|
|
in Go types (as described below), except for methods that contain unsupported
|
|
|
|
|
return types or paramater types such as blocks and function pointers.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
2019-06-06 00:30:21 -04:00
|
|
|
|
s1 := ns.NSStringAlloc() // allocate an instance of NSString
|
2019-05-29 00:03:51 -04:00
|
|
|
|
s2 := ns.NSStringWithSting(s1) // call a class method of NSString
|
|
|
|
|
class := ns.NSStringClass() // class method returning the class of NSString
|
|
|
|
|
fmt.Println(s2.UTF8String()) // call UTF8String, an NSString instance method
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
As seen above, generated class methods will have the same name as their
|
|
|
|
|
Objective-C method name, converted to the Go TitleCase convention, prefixed
|
|
|
|
|
with the class name, and, if necessary, disambiguated for overloaded
|
|
|
|
|
Objective-C methods. Any redundant initial
|
|
|
|
|
characters are elided (e.g. the Objective-C
|
|
|
|
|
`[NSString stringWithString:aString]` is shortened in Go to
|
2019-05-29 22:36:49 -04:00
|
|
|
|
`ns.NSStringWithString(aString)`). Instance methods are converted to
|
|
|
|
|
TitleCase and disambiguated for method overloading as described below.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
Note that while return types and parameter types needed for the binding will
|
|
|
|
|
be defined and wrapped for you in Go types,
|
|
|
|
|
you will not get any of their methods
|
|
|
|
|
unless those types also appear in your NSWrap configuration file.
|
2019-05-29 22:36:49 -04:00
|
|
|
|
For example, the `[NSDictionary WithObjects: forKeys:]` constructor takes two
|
|
|
|
|
`NSArray` parameters, so if you want to use it from Go you will probably want
|
2019-05-29 00:03:51 -04:00
|
|
|
|
to have `NSArray` in your configuration file in addition to `NSDictionary`.
|
|
|
|
|
|
|
|
|
|
## Overloaded Methods
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Because Go does not allow overloaded functions, NSWrap automatically
|
2019-05-29 00:03:51 -04:00
|
|
|
|
disambiguates overloaded method names as required.
|
|
|
|
|
This is done by successively adding parameter names onto the end of the Go
|
|
|
|
|
function name until a unique name is created.
|
|
|
|
|
|
|
|
|
|
For example, `NSString` provides the folowing `compare` methods:
|
|
|
|
|
|
|
|
|
|
```objective-c
|
|
|
|
|
- compare:
|
|
|
|
|
- compare:options:
|
|
|
|
|
- compare:options:range:
|
|
|
|
|
- compare:options:range:locale:
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
These are translated into Go as:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func (o NSString) Compare(string NSString) NSComparisonResult { }
|
|
|
|
|
|
|
|
|
|
func (o NSString) CompareOptions(string NSString, mask NSStringCompareOptions) NSComparisonResult { }
|
|
|
|
|
|
|
|
|
|
func (o NSString) CompareOptionsRange(string NSString, mask NSStringCompareOptions,
|
|
|
|
|
rangeOfReceiverToCompare NSRange) NSComparisonResult { }
|
|
|
|
|
|
|
|
|
|
func (o NSString) CompareOptionsRangeLocale(string NSString, mask NSStringCompareOptions,
|
|
|
|
|
rangeOfReceiverToCompare NSRange, locale NSObject) NSComparisonResult { }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## NSString Helpers
|
|
|
|
|
|
|
|
|
|
When NSWrap sees a class or instance method ending in `...WithString` (taking
|
|
|
|
|
an Objective-C `NSString` as a parameter), it will automatically create an
|
|
|
|
|
additional helper method ending in `WithGoString` that takes a Go string.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
str := ns.NSStringWithGoString("** your string goes here **")
|
|
|
|
|
fmt.Printf("%s\n",str)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
NSWrap creates a `Char` Go type that is equivalent to a C `char`. A pointer to
|
|
|
|
|
`Char` in Go code can therefore be used with Objective-C functions and methods
|
|
|
|
|
that take a `char*` parameter.
|
|
|
|
|
NSWrap provides the helper functions `CharWithGoString` and `CharWithBytes`
|
|
|
|
|
that take, respectively, Go strings and Go byte arrays (`[]byte`) and return
|
|
|
|
|
`*Char` in Go. As demonstrated above, NSWrap also provides a `String()`
|
|
|
|
|
methods so that the `*Char` and `NSString` types implement the `Stringer`
|
|
|
|
|
Go interface.
|
|
|
|
|
|
|
|
|
|
## Working With NSObject and its Descendants
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Objective-C objects are represented in Go by a type and an interface as
|
2019-05-29 00:03:51 -04:00
|
|
|
|
follows:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type Id struct {
|
|
|
|
|
ptr unsafe.Pointer
|
|
|
|
|
}
|
|
|
|
|
func (o Id) Ptr() unsafe.Pointer { return o.ptr }
|
|
|
|
|
|
|
|
|
|
type NSObject interface {
|
|
|
|
|
Ptr() unsafe.Pointer
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
Other object types in Go are structs that directly or indirectly embed `Id`
|
|
|
|
|
and therefore implement `NSObject`.
|
|
|
|
|
|
|
|
|
|
* The NSObject Interface
|
|
|
|
|
|
|
|
|
|
The `Id` type in Go represents the Objective-C type `id`, which is a pointer
|
|
|
|
|
to an Objective-C object. Because `cgo` does not understand this type,
|
|
|
|
|
NSWrap will always translate it to a `void*` on the C side.
|
2019-05-29 22:36:49 -04:00
|
|
|
|
The `NSObject` interface in Go allows any type that directly or indirectly
|
|
|
|
|
embeds `Id` to be used with generic Objective-C functions. For example:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
o1 := ns.NSStringWithGoString("my string")
|
|
|
|
|
s1 := ns.NSSetWithOBjects(o1)
|
|
|
|
|
a := ns.NSMutableArrayWithObjects(o1,s1)
|
|
|
|
|
```
|
|
|
|
|
Since `NSString` and `NSSet` in Go both implement the `NSObject` interface,
|
|
|
|
|
they can both be used as parameters to the `NSMutableArray` constructor.
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
This will help you, too, when working with delegates
|
2019-05-29 00:03:51 -04:00
|
|
|
|
(see below). Classes that accept delegates will generally accept any
|
2019-05-29 22:36:49 -04:00
|
|
|
|
`NSObject` in their `initWithDelegate()` or `setDelegate()` methods, and
|
2019-05-29 00:03:51 -04:00
|
|
|
|
may or may not test at runtime if the provided object actually
|
|
|
|
|
implements the required delegate protocol.
|
|
|
|
|
|
|
|
|
|
* Inheritance
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Objective-C only provides single inheritance. In Go, this is modeled using
|
2019-05-29 00:03:51 -04:00
|
|
|
|
embedding. Top level objects that inherit from `NSObject` in Objective-C
|
|
|
|
|
embed the Go type `Id` and therefore implement the `NSObject` Go interface.
|
|
|
|
|
Other objects embed their superclass. For example:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type NSArray struct { Id }
|
|
|
|
|
func (o NSArray) Ptr() unsafe.Pointer { return o.ptr }
|
|
|
|
|
func (o Id) NSArray() NSArray {
|
|
|
|
|
ret := NSArray{}
|
|
|
|
|
ret.ptr = o.ptr
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type NSMutableArray struct { NSArray }
|
|
|
|
|
func (o NSMutableArray) Ptr() unsafe.Pointer { return o.ptr }
|
|
|
|
|
func (o Id) NSMutableArray() NSMutableArray {...}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Observe:
|
|
|
|
|
```go
|
|
|
|
|
b := ns.NSButtonAlloc() // NSButton > NSControl > NSView > NSResponder > NSObject
|
|
|
|
|
b.InitWithFrame(ns.NSMakeRect(100,100,200,200)) // Method of NSView
|
|
|
|
|
b.SetTitle(nst("PUSH")) // Method of NSButton
|
|
|
|
|
vw := win.ContentView()
|
|
|
|
|
vw.AddSubview(b.NSView) // Pass the button's embedded NSView
|
|
|
|
|
```
|
|
|
|
|
In Go, `NSButtonAlloc` returns a Go object of type `ns.NSButton`. However,
|
|
|
|
|
there is no `InitWithFrame` method for receivers of this type. This is
|
|
|
|
|
not necessary because `NSButton` embeds `NSControl` which in turn embeds
|
|
|
|
|
`NSView`. The `InitWithFrame` method only needs to be implemented for `NSView`
|
|
|
|
|
receivers. Go will automatically find the indirectly embedded `NSView` and
|
|
|
|
|
call the right method.
|
|
|
|
|
|
2019-06-04 11:27:46 -04:00
|
|
|
|
Note that, since `InitWithFrame()` is defined only for `NSView` and returns
|
|
|
|
|
an `NSView` type,
|
|
|
|
|
the following will not work. Look out for this if you like to chain your
|
2019-05-29 22:36:49 -04:00
|
|
|
|
`Alloc` and `Init` methods and are getting type errors:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
2019-06-04 11:27:46 -04:00
|
|
|
|
//DO NOT DO THIS -- InitWithFrame returns NSView, not NSButton
|
2019-05-29 00:03:51 -04:00
|
|
|
|
b := ns.NSButtonAlloc().InitWithFrame(ns.MakeRect(100,100,200,200))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Go has no trouble finding embedded methods for your `NSButton` and will
|
|
|
|
|
happily search up the chain through `NSControl`, `NSView`, `NSResponder` and
|
|
|
|
|
`NSObject` and all of their associated protocols and categories. As of this
|
|
|
|
|
writing, on MacOS 10.13.6, NSWrap binds 90 instance methods for `NSObject`,
|
2019-05-29 22:36:49 -04:00
|
|
|
|
so things like `Hash()`, `IsEqualTo()`, `ClassName()`, `RespondsToSelector`
|
|
|
|
|
and many many others are available and can be called on any object directly
|
|
|
|
|
from Go.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
Go does not perform the same type
|
|
|
|
|
magic when you use variables as function or method parameters.
|
|
|
|
|
If you want to pass your `NSButton` as a parameter to a method that accepts
|
|
|
|
|
an `NSView` type, you need to explicitly pass the embedded `NSView`
|
|
|
|
|
(`b.NSView` in the example above).
|
|
|
|
|
|
|
|
|
|
NSWrap creates a method for `Id` allowing objects to be converted
|
|
|
|
|
at run-time to any other class. You will need this for Enumerators, which
|
|
|
|
|
always return `Id`. See below under Enumerators for an example, but make
|
2019-05-29 22:36:49 -04:00
|
|
|
|
sure you know (or test) what type your objects are before converting them.
|
|
|
|
|
You can
|
|
|
|
|
implement a somewhat less convenient version of a Go type switch this way.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
Because `Id` can be converted to any type, and every object in the Foundation
|
|
|
|
|
classes inherits from `Id`, it is possible to send any message to any
|
2019-05-29 22:36:49 -04:00
|
|
|
|
object, if you are feeling lucky. If you are not lucky you will get an
|
|
|
|
|
exception from the Objective-C runtime. You are going to have to explicitly
|
2019-05-29 00:03:51 -04:00
|
|
|
|
convert your object to the wrong type before the compiler will let you do this.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
a := ns.NSArrayWithObjects(o1,o2) // NSArray embeds Id
|
|
|
|
|
fmt.Println(a.NSString().UTF8String()) // DON'T!
|
2019-05-29 22:36:49 -04:00
|
|
|
|
// | | \-method of NSString, returns *Char, a "Stringer"
|
2019-05-29 00:03:51 -04:00
|
|
|
|
// | \-method of Id returning NSString
|
|
|
|
|
// \-calls "String()" on its parameters
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The above code will compile, but you will get an exception at runtime:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
|
|
|
|
|
'-[__NSArrayM UTF8String]: unrecognized selector sent to instance 0x4608940'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Variadic Functions
|
|
|
|
|
|
|
|
|
|
As seen above with the `NSMutableArrayWithObjects()` constructor example,
|
|
|
|
|
NSWrap supports variadic
|
|
|
|
|
functions. Because of the limitations of `cgo`, there is a numerical limit
|
|
|
|
|
to the number of parameters in a variadic function call, which defaults to
|
2019-05-29 22:36:49 -04:00
|
|
|
|
16 but can be set with the `vaargs` configuration directive. NSWrap will
|
2019-05-29 22:57:10 -04:00
|
|
|
|
automatically include a `nil` sentinel when calling any Objective-C
|
2019-05-29 22:36:49 -04:00
|
|
|
|
methods with variadic parameter lists. The direct types `va_list` and
|
|
|
|
|
`va_list_tag` are not currently supported.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
## Pointers to Pointers
|
|
|
|
|
|
|
|
|
|
When NSWrap encounters a pointer to a pointer to an Objective-C object, it
|
|
|
|
|
treats it as an array of objects and translates it into a pointer to a
|
|
|
|
|
Go slice. If you are passing empty slices into these functions, be sure to
|
2019-05-29 22:36:49 -04:00
|
|
|
|
pre-allocate them to a sufficient capacity. Ssee below for an
|
|
|
|
|
example. These Go slices can be used for input and output of methods and
|
2019-05-29 00:03:51 -04:00
|
|
|
|
functions.
|
|
|
|
|
|
|
|
|
|
Pointers to pointers are sometimes passed to Objective-C methods or functions
|
2019-05-29 22:36:49 -04:00
|
|
|
|
as a way of receiving output from those functions, especially because
|
|
|
|
|
Objective-C does not allow for multiple return values. In those cases, after
|
|
|
|
|
the CGo call, the method parameter will be treated as a nil-terminated array of
|
|
|
|
|
object pointers that may have been modified by the Objective-C function or
|
|
|
|
|
method. NSWrap will copy the object pointers back into the input Go slice, up
|
|
|
|
|
to its capacity (which will never be changed). The input Go slice is then
|
|
|
|
|
truncated to the appropriate length.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
An example in Core Foundation is the `getObjects:andKeys:count` method for
|
|
|
|
|
`NSDictionary`:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
nst := ns.NSStringWithGoString
|
|
|
|
|
dict := ns.NSDictionaryWithObjectsForKeys(
|
|
|
|
|
ns.NSArrayWithObjects(nst("obj1"),nst("obj2")),
|
|
|
|
|
ns.NSArrayWithObjects(nst("key1"),nst("key2")),
|
|
|
|
|
)
|
|
|
|
|
os,ks := make([]ns.Id,0,5), make([]ns.Id,0,5) // length 0, capacity 5 slices
|
2019-05-29 22:36:49 -04:00
|
|
|
|
dict.GetObjects(&os,&ks,5)
|
|
|
|
|
// last parameter is the count, must be less than or equal to the input slice capacity
|
2019-05-29 00:03:51 -04:00
|
|
|
|
fmt.Printf("Length of os is now %d\n",len(os)) // os and ks slices are now length = 2
|
|
|
|
|
for i,k := range ks {
|
|
|
|
|
fmt.Printf("-- %s -> %s\n",k.NSString(),os[i].NSString())
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
NSWrap will never check the "count" parameter, so the user will always need
|
2019-05-29 22:57:10 -04:00
|
|
|
|
to make sure it is less than or equal to the capacity of the input
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Go slices.
|
|
|
|
|
|
2019-05-29 00:03:51 -04:00
|
|
|
|
Using pointers to pointers is necessary in many Core Foundation situations
|
2019-05-29 22:36:49 -04:00
|
|
|
|
where you need to get an error message out of a function or method.
|
|
|
|
|
Here is an example using `[NSString stringWithContentsOfURL...]`:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
err := make([]ns.NSError,1)
|
|
|
|
|
n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("htttypo://example.com"),0,&err)
|
|
|
|
|
fmt.Printf("err: %s\n",err[0].LocalizedDescription())
|
|
|
|
|
//err: The file couldn’t be opened because URL type htttypo isn’t supported.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Selectors
|
|
|
|
|
|
|
|
|
|
You can specify selectors using a Go string. The `Selector()` function
|
|
|
|
|
returns a Go type `SEL` which corresponds to a pointer to
|
|
|
|
|
`struct objc_selector` in C.
|
|
|
|
|
Among other things, this lets you set actions on `NSControls` and `NSMenuItems`:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
appMenu.AddItemWithTitle(
|
|
|
|
|
ns.NSStringWithGoString("Quit"),
|
|
|
|
|
ns.Selector("terminate:"),
|
|
|
|
|
ns.NSStringWithGoString("q"))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Enumerators
|
|
|
|
|
|
|
|
|
|
NSWrap provides a `ForIn` method for the `NSEnumerator` type. Call it with a
|
|
|
|
|
`func(ns.Id) bool` parameter that returns `true` to continue and `false` to
|
|
|
|
|
stop the enumeration.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
a := ns.NSArrayWithObjects(o1,o2,o3)
|
|
|
|
|
a.ObjectEnumerator().ForIn(func (o ns.Id) bool {
|
|
|
|
|
switch {
|
|
|
|
|
case o.IsKindOfClass(ns.NSStringClass()):
|
|
|
|
|
fmt.Println(o.NSString().UTF8String())
|
|
|
|
|
return true // continue enumeration
|
|
|
|
|
default:
|
|
|
|
|
fmt.Println("Unknown class")
|
|
|
|
|
return false // terminate enumeration
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
As seen above, you can do the usual Objective-C thing for runtime type
|
|
|
|
|
identification.
|
|
|
|
|
|
|
|
|
|
## Enum Definitions
|
|
|
|
|
|
|
|
|
|
NSWrap translates C `enum` values into Go constants. The enums you need are
|
|
|
|
|
specified in `nswrap.yaml` by regular expression, which, in the case of named
|
|
|
|
|
enums, must match the name of the `enum` itself, or in the case of anonymous
|
|
|
|
|
enums, must match the name of the constant(s) you are looking for as declared
|
|
|
|
|
within the `enum`.
|
|
|
|
|
The generated constants receive Go types associated with their underlying C
|
|
|
|
|
types, which are automatically declared by NSWrap as needed.
|
|
|
|
|
|
|
|
|
|
The following configuration:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# nswrap.yaml
|
|
|
|
|
inputfiles: [/System/Library/Frameworks/AppKit.framework/Headers/AppKit.h]
|
|
|
|
|
enums:
|
|
|
|
|
- _CLOCK.* # match constants in an anonymous enum
|
|
|
|
|
- NSWindowOrdering.* # match a named enum
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
results in:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
//ns/main.go
|
|
|
|
|
...
|
|
|
|
|
type NSWindowOrderingMode C.enum_NSWindowOrderingMode
|
|
|
|
|
const NSWindowAbove NSWindowOrderingMode = C.NSWindowAbove
|
|
|
|
|
const NSWindowBelow NSWindowOrderingMode = C.NSWindowBelow
|
|
|
|
|
const NSWindowOut NSWindowOrderingMode = C.NSWindowOut
|
|
|
|
|
|
|
|
|
|
const _CLOCK_REALTIME = C._CLOCK_REALTIME
|
|
|
|
|
const _CLOCK_MONOTONIC = C._CLOCK_MONOTONIC
|
|
|
|
|
const _CLOCK_MONOTONIC_RAW = C._CLOCK_MONOTONIC_RAW
|
|
|
|
|
...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Memory management
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Objective-C objects are always allocated and returned from CGo code, and
|
|
|
|
|
therefore these pointers are not garbage collected by Go. You can use any
|
|
|
|
|
of the standard Objective-C memory management techniques for those pointers,
|
|
|
|
|
which seem to work but have not been extensively tested.
|
|
|
|
|
|
|
|
|
|
Since everything inherits methods from `NSObject`, you can call `Retain()`,
|
|
|
|
|
`Release()` and `Autorelease()` on any object.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
2019-06-06 00:30:21 -04:00
|
|
|
|
If the autorelease configuration directive is set to "true", all allocation functions
|
|
|
|
|
created by NSWrap (i.e. those ending in `Alloc`) will call `autorelease` before they
|
|
|
|
|
return an object. Alternately, objects can be manually sent an autorelease message.
|
2019-05-29 22:36:49 -04:00
|
|
|
|
If you are not working in an environment (such as an
|
2019-05-29 00:03:51 -04:00
|
|
|
|
Application Delegate callback) that provides an autorelease pool, you can
|
|
|
|
|
create your own:
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
* Work directly with `NSAutoreleasePool` objects
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
swamp := ns.NSAutoreleasePoolAlloc().Init()
|
|
|
|
|
del := ns.AppDelegateAlloc()
|
2019-06-06 00:30:21 -04:00
|
|
|
|
//del.Autorelease() // if autorelease: true is not set in nswrap.yaml
|
2019-05-29 00:03:51 -04:00
|
|
|
|
menu := ns.NSMenuAlloc().InitWithTitle(nst("Main"))
|
2019-06-06 00:30:21 -04:00
|
|
|
|
//menu.Autorelease()
|
2019-05-29 00:03:51 -04:00
|
|
|
|
str := ns.NSStringWithGoString("these objects will be automatically deallocated when swamp is drained.")
|
|
|
|
|
...
|
|
|
|
|
swamp.Drain()
|
|
|
|
|
```
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
* ...or use the `AutoreleasePool()` helper function
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
NSWrap provides a helper function that can be passed a `func()` with no
|
|
|
|
|
parameters or return value. It is conventient to give it an anonymous function
|
|
|
|
|
and write your code in line, just like you would if you were using an
|
|
|
|
|
`@autoreleasepool { }` block.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
ns.AutoreleasePool(func() {
|
|
|
|
|
a := MyObjectAlloc().Init()
|
|
|
|
|
b := MyOtherObjectAlloc().Init()
|
|
|
|
|
...
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You will need to make sure `NSAutoreleasePool` is included in the `classes`
|
|
|
|
|
directive in your configuration file before working with
|
|
|
|
|
`NSAutoreleasePool` objects or the `AutoreleasePool` helper function.
|
|
|
|
|
|
2019-06-06 00:30:21 -04:00
|
|
|
|
* Pitfalls
|
|
|
|
|
|
|
|
|
|
Go concurrency does not play well with Objective-C memory management. In particular,
|
|
|
|
|
an AutoreleasePool needs to be allocated and drained from the same thread, and
|
|
|
|
|
only objects allocated within that thread will be drained. Objects allocated and
|
|
|
|
|
autoreleased from a different goroutine in the same thread are at risk of being
|
|
|
|
|
prematurely drained. Therefore, you should only work with one AutoreleasePool at
|
|
|
|
|
a time, and only within a thread that is locked to the OS thread
|
|
|
|
|
(by calling `runtime.LockOSThread()`). If you will be allocating Objective-C objects
|
|
|
|
|
from multiple goroutines, it is best not to use the `autorelease: true` directive
|
|
|
|
|
as that will cause all objects to receive an `autorelease` message even if they are
|
|
|
|
|
created outside the thread where are using your autorelease pool.
|
|
|
|
|
See `examples/memory` for some basic tests and read the comments to larn what to avoid.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
## Delegates
|
|
|
|
|
|
|
|
|
|
The `delegates` directive in `nswrap.yaml` creates a new Objective-C
|
|
|
|
|
class and associated Go wrapper functions. For example, the following
|
|
|
|
|
configuration file creates a class called `CBDelegate` that implements
|
2019-05-29 22:57:10 -04:00
|
|
|
|
the `CBCentralManagerDelegate` and `CBPeripheralDelegate`
|
|
|
|
|
protocols from Core Bluetooth, along with the Go code you need to allocate
|
|
|
|
|
and use instances of the new class.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# nswrap.yaml
|
|
|
|
|
inputfiles:
|
|
|
|
|
- /System/Library/Frameworks/CoreBluetooth.framework/Headers/CoreBluetooth.h
|
|
|
|
|
|
|
|
|
|
classes:
|
|
|
|
|
- CBCentralManager
|
|
|
|
|
|
|
|
|
|
delegates:
|
|
|
|
|
CBDelegate: # a name for your delegate class
|
|
|
|
|
CBCentralManagerDelegate: # a protocol to implement
|
|
|
|
|
- centralManagerDidUpdateState # messages you want to respond to
|
|
|
|
|
- centralManagerDidDiscoverPeripheral
|
|
|
|
|
- centralManagerDidConnectPeripheral
|
|
|
|
|
CBPeripheralDelegate: # another protocol to implement
|
|
|
|
|
- peripheralDidDiscoverServices
|
|
|
|
|
- peripheralDidDiscoverCharacteristicsForService
|
|
|
|
|
- peripheralDidUpdateValueForCharacteristic
|
|
|
|
|
...
|
|
|
|
|
```
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
The generated delegate inherits from `NSObject` and, in its interface
|
|
|
|
|
declaration, is advertised as implementing the protocols specified in
|
|
|
|
|
`nswrap.yaml`.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
When a delegate is activated and one of the callback methods named in the
|
2019-05-29 22:36:49 -04:00
|
|
|
|
configuration file is called, the delegate will call back into a Go
|
|
|
|
|
function exported by NSWrap. If a user-defined callback function has been
|
2019-05-29 22:57:10 -04:00
|
|
|
|
registered,
|
2019-05-29 00:03:51 -04:00
|
|
|
|
it will be called with all of its parameters converted to their Go type
|
|
|
|
|
equivalents. User-defined callbacks are registered by calling a function
|
|
|
|
|
with the method name in TitleCase + `Callback`, so in the example above,
|
2019-05-29 22:36:49 -04:00
|
|
|
|
you would call `ns.CentralManagerDidUpdateStateCallback(...)` with the name of
|
|
|
|
|
your callback function to register to receive notifications when your central
|
2019-05-29 00:03:51 -04:00
|
|
|
|
manager updates its state.
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
The example in `examples/bluetooth` implements a working Bluetooth Low-Energy
|
2019-05-29 00:03:51 -04:00
|
|
|
|
heart rate monitor entirely in Go.
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
The following Go code instantiates a `CBDelegate` object,
|
2019-05-29 00:03:51 -04:00
|
|
|
|
registers a callback for `centralManagerDidUpdateState`, allocates
|
2019-05-29 22:36:49 -04:00
|
|
|
|
a `CBCentralManager` object, and installs our delegate:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func cb(c ns.CBCentralManager) {
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
...
|
|
|
|
|
del := ns.CBDelegateAlloc()
|
|
|
|
|
del.CentralManagerDidUpdateStateCallback(cb)
|
|
|
|
|
cm := ns.CBCentralManagerAlloc().InitWithDelegateQueue(del,queue)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When you provide user-defined callback functions, you will need to specify
|
|
|
|
|
them with exactly the right type,
|
|
|
|
|
matching NSWrap's generated Go wrapper types for the callback function and
|
|
|
|
|
the Go types for all of its parameters. If `go build` fails, the error
|
|
|
|
|
messages will point you in the right direction.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ go build
|
|
|
|
|
./main.go:127:43: cannot use didFinishLaunching (type func(ns.NSNotification, bool)) as type
|
|
|
|
|
func(ns.NSNotification) in argument to del.ApplicationDidFinishLaunchingCallback
|
|
|
|
|
```
|
2019-05-29 22:57:10 -04:00
|
|
|
|
In the above example, the build failed because an extra `bool` parameter was
|
2019-05-29 00:03:51 -04:00
|
|
|
|
included in the callback function. The compiler is telling you that the right
|
|
|
|
|
type for the callback is `func(ns.NSNotification)` with no return value.
|
|
|
|
|
|
|
|
|
|
## Working with AppKit
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
You can wrap the AppKit framework classes and create an `NSApplication`
|
|
|
|
|
Delegate. This allows you to build a Cocoa application entirely in Go.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
Because AppKit uses thread local storage, you will need to make sure all
|
|
|
|
|
calls into it are done from the main OS thread. This can be a challenge in
|
|
|
|
|
Go even though runtime.LockOSThread() is supposed to provide
|
2019-05-29 22:36:49 -04:00
|
|
|
|
this functionality.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
This is actually a full working Cocoa application:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# nswrap.yaml
|
|
|
|
|
inputfiles:
|
|
|
|
|
- /System/Library/Frameworks/AppKit.framework/Headers/AppKit.h
|
|
|
|
|
|
|
|
|
|
classes:
|
|
|
|
|
- NSApplication
|
|
|
|
|
- NSWindow
|
|
|
|
|
- NSString
|
|
|
|
|
- NSMenu
|
|
|
|
|
|
|
|
|
|
enums:
|
|
|
|
|
- NSApplication.*
|
|
|
|
|
- NSBackingStore.*
|
|
|
|
|
- NSWindowStyleMask.*
|
|
|
|
|
|
|
|
|
|
functions:
|
|
|
|
|
- NSMakeRect
|
|
|
|
|
|
|
|
|
|
delegates:
|
|
|
|
|
AppDelegate:
|
|
|
|
|
NSApplicationDelegate:
|
|
|
|
|
- applicationDidFinishLaunching
|
|
|
|
|
- applicationShouldTerminateAfterLastWindowClosed
|
|
|
|
|
frameworks: [ Foundation, AppKit, CoreGraphics ]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
//go:generate nswrap
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"runtime"
|
2019-05-29 22:36:49 -04:00
|
|
|
|
"git.wow.st/gmp/nswrap/examples/app/ns" // point to your own NSWrap output directory
|
2019-05-29 00:03:51 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func didFinishLaunching(n ns.NSNotification) {
|
|
|
|
|
fmt.Println("Go: did finish launching!")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func shouldTerminate(s ns.NSApplication) ns.BOOL {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
runtime.LockOSThread()
|
|
|
|
|
a := ns.NSApplicationSharedApplication()
|
|
|
|
|
a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular)
|
|
|
|
|
del := ns.AppDelegateAlloc()
|
|
|
|
|
del.ApplicationDidFinishLaunchingCallback(didFinishLaunching)
|
|
|
|
|
del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminate)
|
|
|
|
|
a.SetDelegate(del)
|
|
|
|
|
|
|
|
|
|
win := ns.NSWindowAlloc().InitWithContentRectStyleMask(
|
|
|
|
|
ns.NSMakeRect(200,200,600,600),
|
|
|
|
|
ns.NSWindowStyleMaskTitled | ns.NSWindowStyleMaskClosable,
|
|
|
|
|
ns.NSBackingStoreBuffered,
|
|
|
|
|
0,
|
|
|
|
|
)
|
|
|
|
|
win.SetTitle(ns.NSStringWithGoString("Hi World"))
|
|
|
|
|
win.MakeKeyAndOrderFront(win)
|
|
|
|
|
a.Run()
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Pretty simple right? Not really, NSWrap just generated almost 15,000 lines of
|
|
|
|
|
code. See `examples/app` for a slightly more complex example with working
|
2019-05-29 22:36:49 -04:00
|
|
|
|
menus, visual format-based auto layout, and a custom button class.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
## Subclasses
|
|
|
|
|
|
|
|
|
|
NSWrap includes functionality to generate subclasses as specified in
|
|
|
|
|
`nswrap.yaml`.
|
|
|
|
|
|
|
|
|
|
You can override existing methods or create new methods with any type
|
|
|
|
|
signature you specify using Objective-C method signature syntax.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# nswrap.yaml
|
|
|
|
|
...
|
|
|
|
|
subclasses:
|
|
|
|
|
myClass: # the name of the new class
|
|
|
|
|
yourClass: # the superclass to inherit from
|
|
|
|
|
- init.* # what methods to override
|
|
|
|
|
- -(void)hi_there:(int)x # Objective-C prototype of your new method(s)
|
2019-05-29 22:36:49 -04:00
|
|
|
|
# |--this hyphen indicates that this is an instance method
|
2019-05-29 00:03:51 -04:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In the example above, your new class will be named `myClass` in Objective-C
|
|
|
|
|
and `MyClass` in Go. It will override any `init` methods found in `yourClass`
|
|
|
|
|
(which must be defined in one of the header files included in the
|
|
|
|
|
`inputfiles` directive of `nswrap.yaml`). In addition, because the second
|
|
|
|
|
entry under `yourClass` starts with a `-`, it will be treated as a new
|
|
|
|
|
instance method definition for `myClass`. The remainder of the line will
|
|
|
|
|
be parsed as an Objective-C method prototype in order to determine the method
|
|
|
|
|
name, its return type, and the names and types of its parameters if any.
|
|
|
|
|
|
|
|
|
|
Since multiple inheritance is not permitted in Objective-C, it is not possible
|
|
|
|
|
to specify more than one superclass in a `subclasses` entry.
|
|
|
|
|
|
2019-05-29 22:57:10 -04:00
|
|
|
|
Go callbacks for overridden methods are passed a special struct
|
|
|
|
|
as their first parameter. This struct is filled with superclass methods, which
|
2019-05-29 22:36:49 -04:00
|
|
|
|
allows you to do things like this:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func methodCallback(super ns.MyClassSupermethods, param NSString) {
|
|
|
|
|
...
|
|
|
|
|
super.Method(param)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-05-29 00:03:51 -04:00
|
|
|
|
You can use subclasses to define new AppKit controls with configurable
|
2019-05-29 22:57:10 -04:00
|
|
|
|
callbacks. For example, let's make an `NSButton` that calls back into Go when
|
2019-05-29 00:03:51 -04:00
|
|
|
|
you press it:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# nswrap.yaml
|
|
|
|
|
...
|
|
|
|
|
subclasses:
|
|
|
|
|
GButton:
|
|
|
|
|
NSButton:
|
|
|
|
|
- -(void)pressed
|
|
|
|
|
...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func pressed() {
|
|
|
|
|
fmt.Println("Button pressed!")
|
|
|
|
|
}
|
|
|
|
|
...
|
|
|
|
|
func didFinishLaunching(n ns.NSNotification) {
|
|
|
|
|
...
|
|
|
|
|
button := ns.GButtonAlloc()
|
|
|
|
|
button.Init()
|
|
|
|
|
button.PressedCallback(pressed) # register user-defined callback
|
|
|
|
|
button.SetAction(ns.Selector("pressed"))
|
|
|
|
|
button.SetTarget(button)
|
|
|
|
|
button.SetTitle(ns.NSStringWithGoString("PUSH"))
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Later on you can add your new button to a view and tell Cocoa where to lay
|
2019-05-29 00:03:51 -04:00
|
|
|
|
it out. It's all a little verbose, but that's because for some reason you
|
|
|
|
|
decided to write Objective-C code in Go.
|
|
|
|
|
|
|
|
|
|
# Limitations
|
|
|
|
|
|
|
|
|
|
## Blocks
|
|
|
|
|
|
|
|
|
|
NSWrap does not support methods or functions that take C functions or blocks
|
|
|
|
|
as parameters or return values.
|
|
|
|
|
|
|
|
|
|
# Why?
|
|
|
|
|
|
|
|
|
|
Um, I was trying to make a nice modern Go binding for CoreBluetooth on MacOS
|
|
|
|
|
and got carried away.
|
|
|
|
|
|
|
|
|
|
# Acknowledgements
|
|
|
|
|
|
|
|
|
|
This work was inspired by Maxim's excellent
|
|
|
|
|
[c-for-go](https://github.com/xlab/c-for-go). Much of the
|
|
|
|
|
infrastructure was lifted from Elliot Chance's equally excellent
|
|
|
|
|
[c2go](https://github.com/elliotchance/c2go). Kiyoshi Murata's
|
|
|
|
|
post on [coderwall.com](https://coderwall.com/p/l9jr5a/accessing-cocoa-objective-c-from-go-with-cgo)
|
|
|
|
|
was an essential piece of inspiration.
|
|
|
|
|
|
|
|
|
|
The combinatorial Objective-C type parsers are mine as are the
|
|
|
|
|
Objective-C and Go code generators, so this is where you will find
|
|
|
|
|
all of the bugs.
|