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.
|
2019-06-27 11:41:58 -04:00
|
|
|
|
Since the `NSObject` class is necessary for memory management, NSWrap will
|
|
|
|
|
automatically include it if it is encountered in an input header file.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *NSString) Compare(string *NSString) NSComparisonResult { }
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *NSString) CompareOptions(string *NSString, mask NSStringCompareOptions) NSComparisonResult { }
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *NSString) CompareOptionsRange(string *NSString, mask NSStringCompareOptions,
|
2019-05-29 00:03:51 -04:00
|
|
|
|
rangeOfReceiverToCompare NSRange) NSComparisonResult { }
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *NSString) CompareOptionsRangeLocale(string *NSString, mask NSStringCompareOptions,
|
2019-05-29 00:03:51 -04:00
|
|
|
|
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.
|
2019-06-27 11:41:58 -04:00
|
|
|
|
|
|
|
|
|
When NSWrap binds a method that returns `*Char` (and is in garbage collected mode,
|
|
|
|
|
the default), it first calls `strdup`
|
|
|
|
|
on the output of the underlying Objective-C method. Therefore, the returned
|
|
|
|
|
pointer is manually allocated and will need to be freed later from Go. NSWrap
|
|
|
|
|
creates a
|
|
|
|
|
`(*Char).Free()` method for use when these pointers are no longer needed.
|
|
|
|
|
This copying is necessary because the Objective-C runtime will sometimes
|
|
|
|
|
return pointers to internal objects that are impossible to manage from the
|
|
|
|
|
Go side. NSWrap aims to cause any internal objects to be deallocated as soon
|
|
|
|
|
as possible so they do not cause memory leaks. This means that any returned
|
|
|
|
|
C strings need to be copied and memory managed manually from the Go side.
|
|
|
|
|
|
2019-05-29 00:03:51 -04:00
|
|
|
|
NSWrap provides the helper functions `CharWithGoString` and `CharWithBytes`
|
|
|
|
|
that take, respectively, Go strings and Go byte arrays (`[]byte`) and return
|
2019-06-27 11:41:58 -04:00
|
|
|
|
`*Char` in Go. As demonstrated above, NSWrap also provides `String()`
|
|
|
|
|
methods so that the `*Char` and `*NSString` types implement the `Stringer`
|
|
|
|
|
Go interface and therefore can be sent directly to functions like `fmt.Printf`.
|
|
|
|
|
The `String()` method on `*NSString` creates a temporary `*Char` internally
|
|
|
|
|
but frees it for you before returning. Since methods returning
|
|
|
|
|
`*Char` return a pointer that needs to be manually freed, it is important
|
|
|
|
|
to use these properly in order to avoid leaks:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
nst := ns.NSStringWithGoString("one string")
|
|
|
|
|
|
|
|
|
|
// NO: the next line leaks a *Char (UTF8String)
|
|
|
|
|
//mygostring := nst.UTF8String().String()
|
|
|
|
|
|
|
|
|
|
// OK: NSWrap creates a temporary *Char and frees it for you:
|
|
|
|
|
mygostring := nst.String()
|
|
|
|
|
|
|
|
|
|
// ALSO OK: manually free your own temporary *Char:
|
|
|
|
|
mytmpchar := nst.UTF8String()
|
|
|
|
|
mygostring = mytmpchar.String()
|
|
|
|
|
mytmpchar.Free()
|
|
|
|
|
```
|
|
|
|
|
In most cases it will be more convenient to convert directly to Go strings instead
|
|
|
|
|
of `*Char`.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
}
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *Id) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
type NSObject interface {
|
|
|
|
|
Ptr() unsafe.Pointer
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
Other object types in Go are structs that directly or indirectly embed `Id`
|
2019-06-27 11:41:58 -04:00
|
|
|
|
and therefore contain an `unsafe.Pointer` to an Objective-C object, and
|
|
|
|
|
implement `NSObject` by inheriting the `Ptr()` method.
|
|
|
|
|
|
|
|
|
|
Because of this implementation, you will note that every Objective-C object
|
|
|
|
|
is represented by at least two pointers -- an underlying pointer to the
|
|
|
|
|
Objective-C object
|
|
|
|
|
in CGo memory (allocated by the Objective-C runtime), as well as a pointer
|
|
|
|
|
allocated by the Go runtime to an `Id` type, or to another type that directly
|
|
|
|
|
or indirectly embeds `Id`. This "dual pointer" approach is necessary to ensure
|
|
|
|
|
that memory management can be made to work correctly (see below for details).
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
* 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")
|
2019-06-27 11:41:58 -04:00
|
|
|
|
s1 := ns.NSSetWithObjects(o1)
|
2019-05-29 00:03:51 -04:00
|
|
|
|
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-06-27 11:41:58 -04:00
|
|
|
|
Objective-C allows single inheritance. NSWrap automatically adds
|
|
|
|
|
inherited methods to classes that are includled in your binding.
|
|
|
|
|
|
|
|
|
|
Types created by NSWrap also "embed" their parent class. For example, top
|
|
|
|
|
level objects that inherit from `NSObject` in Objective-C
|
2019-05-29 00:03:51 -04:00
|
|
|
|
embed the Go type `Id` and therefore implement the `NSObject` Go interface.
|
2019-06-27 11:41:58 -04:00
|
|
|
|
Other objects embed their direct superclass. For example:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type NSArray struct { Id }
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *NSArray) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
|
|
|
|
|
func (o *Id) NSArray() *NSArray {
|
|
|
|
|
return (*NSArray)(unsafe.Pointer(o))
|
2019-05-29 00:03:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type NSMutableArray struct { NSArray }
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func (o *NSMutableArray) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
|
|
|
|
|
func (o *Id) NSMutableArray() *NSMutableArray {
|
|
|
|
|
return (*NSMutableArray)(unsafe.Pointer(o))
|
|
|
|
|
}
|
2019-05-29 00:03:51 -04:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Observe:
|
|
|
|
|
```go
|
|
|
|
|
b := ns.NSButtonAlloc() // NSButton > NSControl > NSView > NSResponder > NSObject
|
2019-06-27 11:41:58 -04:00
|
|
|
|
b.InitWithFrame(ns.NSMakeRect(100,100,200,200))
|
|
|
|
|
b.SetTitle(nst("PUSH"))
|
2019-05-29 00:03:51 -04:00
|
|
|
|
vw := win.ContentView()
|
2019-06-27 11:41:58 -04:00
|
|
|
|
vw.AddSubview(&b.NSView) // Pass the button's embedded NSView
|
2019-05-29 00:03:51 -04:00
|
|
|
|
```
|
|
|
|
|
In Go, `NSButtonAlloc` returns a Go object of type `ns.NSButton`. However,
|
2019-06-27 11:41:58 -04:00
|
|
|
|
the `initWithFrame` method is defined in AppKit for `NSView`. NSWrap will find this
|
|
|
|
|
method and add it to the Go `NSButton` type when creating your wrapper because
|
|
|
|
|
`NSButton` inherits from `NSControl` which inherits from `NSView`.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
As of this
|
|
|
|
|
writing, on MacOS 10.13.6, NSWrap binds 115 instance methods for `NSObject`,
|
|
|
|
|
so things like `Hash()`, `IsEqualTo()`, `ClassName()`, `RespondsToSelector()`
|
2019-05-29 22:36:49 -04:00
|
|
|
|
and many many others are available and can be called on any object directly
|
|
|
|
|
from Go.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
All objects implement the `NSObject` interface, but from time to time you
|
|
|
|
|
will encounter a method that takes a parameter of a different type that may
|
|
|
|
|
not exactly match the type you have. For example, if you want to pass your
|
|
|
|
|
`NSButton` as a parameter to a method that accepts an `NSView` type, you need
|
|
|
|
|
to explicitly pass its embedded `NSView` (`&b.NSView` in the example above).
|
|
|
|
|
This approach is safer than "converting" the button to an `NSView` (see below)
|
|
|
|
|
because it will only work on objects that directly or indirectly embed an
|
|
|
|
|
`NSView` Go type.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
NSWrap creates a method for `Id` allowing objects to be converted
|
2019-06-27 11:41:58 -04:00
|
|
|
|
at run-time to any other class. You will need this for Enumerators and
|
|
|
|
|
functions like `NSArray`'s `GetObjects`, for example,
|
|
|
|
|
which always return `*Id`. Make
|
2019-05-29 22:36:49 -04:00
|
|
|
|
sure you know (or test) what type your objects are before converting them.
|
2019-06-27 11:41:58 -04:00
|
|
|
|
You can implement a version of a Go type switch this way:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
switch {
|
|
|
|
|
case o.IsKindOfClass(ns.NSStringClass()):
|
|
|
|
|
// do something with o.NSString()
|
|
|
|
|
case o.IsKindOfClass(ns.NSSetClass()):
|
|
|
|
|
// do something with o.NSSet()
|
|
|
|
|
default:
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
```
|
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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
the CGo call, the method parameter will be treated as an array of
|
2019-05-29 22:36:49 -04:00
|
|
|
|
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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
truncated to the appropriate length. If there is no output, the length will
|
|
|
|
|
be set to 0.
|
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")),
|
|
|
|
|
)
|
2019-06-27 11:41:58 -04:00
|
|
|
|
va,ka := make([]*ns.Id,0,5), make([]*ns.Id,0,5) // length 0, capacity 5 slices
|
|
|
|
|
dict.GetObjects(&va,&ka,5)
|
|
|
|
|
// last parameter to GetObjects is the count, must be less than or equal to the input slice capacity
|
|
|
|
|
fmt.Printf("Length of va is now %d\n",len(va)) // va and ka slices are now length = 2
|
|
|
|
|
for i,k := range ka {
|
|
|
|
|
fmt.Printf("-- %s -> %s\n",k.NSString(),va[i].NSString())
|
2019-05-29 00:03:51 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
NSWrap will not 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-06-27 11:41:58 -04:00
|
|
|
|
where you need to get an error message out of a function or method, or in other
|
|
|
|
|
cases where an Objective-C method wants to provide multiple return values.
|
2019-05-29 22:36:49 -04:00
|
|
|
|
Here is an example using `[NSString stringWithContentsOfURL...]`:
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
```go
|
2019-06-27 11:41:58 -04:00
|
|
|
|
err := make([]*ns.NSError,1)
|
|
|
|
|
n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("htttypo://example.com"), 0, &err)
|
|
|
|
|
if len(err) > 0 {
|
|
|
|
|
fmt.Printf("err: %s\n",err[0].LocalizedDescription())
|
2019-05-29 00:03:51 -04:00
|
|
|
|
//err: The file couldn’t be opened because URL type htttypo isn’t supported.
|
2019-06-27 11:41:58 -04:00
|
|
|
|
}
|
2019-05-29 00:03:51 -04:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
`func(*ns.Id) bool` parameter that returns `true` to continue and `false` to
|
2019-05-29 00:03:51 -04:00
|
|
|
|
stop the enumeration.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
a := ns.NSArrayWithObjects(o1,o2,o3)
|
2019-06-27 11:41:58 -04:00
|
|
|
|
i := 0
|
|
|
|
|
a.ObjectEnumerator().ForIn(func (o *ns.Id) bool {
|
2019-05-29 00:03:51 -04:00
|
|
|
|
switch {
|
|
|
|
|
case o.IsKindOfClass(ns.NSStringClass()):
|
2019-06-27 11:41:58 -04:00
|
|
|
|
fmt.Printf("%d: %s\n", i, o.NSString())
|
|
|
|
|
i++
|
2019-05-29 00:03:51 -04:00
|
|
|
|
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
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
NSWrap translates C `enum` values into Go constants. The enums you want are
|
2019-05-29 00:03:51 -04:00
|
|
|
|
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
|
|
|
|
|
...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 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-06-27 11:41:58 -04:00
|
|
|
|
if your delegate was named `del`, you would call
|
|
|
|
|
`del.CentralManagerDidUpdateStateCallback(...)` with the name of
|
2019-05-29 22:36:49 -04:00
|
|
|
|
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) {
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
var (
|
|
|
|
|
del *ns.CBDelegate // use global variables so these don't get garbage collected
|
|
|
|
|
cm *ns.CBCentralManager
|
|
|
|
|
)
|
|
|
|
|
|
2019-05-29 00:03:51 -04:00
|
|
|
|
func main() {
|
|
|
|
|
...
|
2019-06-27 11:41:58 -04:00
|
|
|
|
del = ns.CBDelegateAlloc()
|
2019-05-29 00:03:51 -04:00
|
|
|
|
del.CentralManagerDidUpdateStateCallback(cb)
|
2019-06-27 11:41:58 -04:00
|
|
|
|
cm = ns.CBCentralManagerAlloc().InitWithDelegateQueue(del,queue)
|
2019-05-29 00:03:51 -04:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
./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 00:03:51 -04:00
|
|
|
|
```
|
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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
type for the callback is `func(*ns.NSNotification)` with no return value.
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
## 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
|
2019-06-27 11:41:58 -04:00
|
|
|
|
Go and you will want to make use of `runtime.LockOSThread()`.
|
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
|
|
|
|
)
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func didFinishLaunching(n *ns.NSNotification) {
|
2019-05-29 00:03:51 -04:00
|
|
|
|
fmt.Println("Go: did finish launching!")
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
func shouldTerminate(s *ns.NSApplication) ns.BOOL {
|
2019-05-29 00:03:51 -04:00
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
var (
|
|
|
|
|
a *ns.NSApplication // global vars so these are not garbage collected
|
|
|
|
|
del *ns.AppDelegate
|
|
|
|
|
win *ns.NSWindow
|
|
|
|
|
)
|
|
|
|
|
|
2019-05-29 00:03:51 -04:00
|
|
|
|
func main() {
|
|
|
|
|
runtime.LockOSThread()
|
2019-06-27 11:41:58 -04:00
|
|
|
|
a = ns.NSApplicationSharedApplication()
|
2019-05-29 00:03:51 -04:00
|
|
|
|
a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular)
|
2019-06-27 11:41:58 -04:00
|
|
|
|
del = ns.AppDelegateAlloc()
|
2019-05-29 00:03:51 -04:00
|
|
|
|
del.ApplicationDidFinishLaunchingCallback(didFinishLaunching)
|
|
|
|
|
del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminate)
|
|
|
|
|
a.SetDelegate(del)
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
win = ns.NSWindowAlloc().InitWithContentRectStyleMask(
|
2019-05-29 00:03:51 -04:00
|
|
|
|
ns.NSMakeRect(200,200,600,600),
|
|
|
|
|
ns.NSWindowStyleMaskTitled | ns.NSWindowStyleMaskClosable,
|
|
|
|
|
ns.NSBackingStoreBuffered,
|
|
|
|
|
0,
|
|
|
|
|
)
|
|
|
|
|
win.SetTitle(ns.NSStringWithGoString("Hi World"))
|
|
|
|
|
win.MakeKeyAndOrderFront(win)
|
|
|
|
|
a.Run()
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
Pretty simple right? Not really, NSWrap just generated over 39,000 lines of
|
2019-05-29 00:03:51 -04:00
|
|
|
|
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-06-27 11:41:58 -04:00
|
|
|
|
# \--the initial 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.
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
## Memory management
|
|
|
|
|
|
|
|
|
|
As mentioned above, NSWrap is designed for there to be at least one Go pointer
|
|
|
|
|
associated with each underlying Objective-C object pointer.
|
|
|
|
|
Since Objective-C memory
|
|
|
|
|
is always allocated by the Objective-C runtime, it is not possible for the Go
|
|
|
|
|
runtime to have visibility into these memory regions or to directly manage memory
|
|
|
|
|
used by the CGo code. However, Go will keep track of the associated Go pointer
|
|
|
|
|
that was created the first time the corresponding Objective-C object was passed
|
|
|
|
|
over to the Go side and an `Id` or other NSWrap struct type was allocated.
|
|
|
|
|
Because of this,
|
|
|
|
|
it is possible to hook into the Go garbage collection system in an attempt to
|
|
|
|
|
manage Objective-C memory strictly from the Go side. When there are no remaining Go
|
|
|
|
|
pointers to an NSWrap `Id` struct, it will be deallocated by the Go garbage collector
|
|
|
|
|
and a finalizer will be called that `release`es the corresponding Objective-C
|
|
|
|
|
object.
|
|
|
|
|
|
|
|
|
|
The memory management rules work as follows:
|
|
|
|
|
|
|
|
|
|
* Objects in Go are represented by pointers to types that implement the `NSObject`
|
|
|
|
|
interface
|
|
|
|
|
* NSObject has one method, `Ptr()`, which returns an `unsafe.Pointer` to an
|
|
|
|
|
Objective-C object.
|
|
|
|
|
* All methods that return objects to Go call `retain` except for `new`, `init`,
|
|
|
|
|
`alloc`, `copy` and `mutableCopy`, which already return retained objects from the
|
|
|
|
|
Objective-C runtime.
|
|
|
|
|
* Go wrappers for Objective-C methods call `runtime.SetFinalizer()`, which calls
|
|
|
|
|
`release` when the associated Go struct is garbage collected.
|
|
|
|
|
* All Objective-C methods are run inside an `@autoreleasepool {}` block to prevent
|
|
|
|
|
internal memory leaks within the Objective-C libraries and frameworks.
|
|
|
|
|
* Objects sent to you in callback functions are not memory managed by Go
|
|
|
|
|
and must be manually
|
|
|
|
|
managed using `Retain()` and `Release()` methods if you need to take ownership of them.
|
|
|
|
|
A rule of thumb is that if you assign such an object to a persistent Go variable for
|
|
|
|
|
use outside of the callback, call `Retain()`.
|
|
|
|
|
|
|
|
|
|
Because of the linkage with the Go garbage collector described above, there should be
|
|
|
|
|
no need for any memory management code to be written from the Go side, except in the
|
|
|
|
|
case mentioned above where your Go delegate receives objects that need to be kept
|
|
|
|
|
around outside of the callback.
|
|
|
|
|
|
|
|
|
|
Since everything in Objective C inherits methods from `NSObject`, you can call
|
|
|
|
|
`Retain()`, `Release()` and `Autorelease()` on any object. You can technically bind
|
|
|
|
|
the `NSAutoreleasePool` class and create and drain instances of it from the Go side,
|
|
|
|
|
but this is not recommended in the default, garbage collected mode and can run into
|
|
|
|
|
problems because the Go runtime is inherently multithreaded. See `examples/memory`
|
|
|
|
|
for an example of manual memory management, which should be possible to do reliably
|
|
|
|
|
but I'm not sure why you would go through the trouble.
|
|
|
|
|
|
|
|
|
|
NSWrap is doing a number of things behind the scenes to make garbage collection work.
|
|
|
|
|
As mentioned,
|
|
|
|
|
all Objective-C methods are called within an `@autorelease {}` block. This is
|
|
|
|
|
necessary because some foundation classes (notably `NSString`) create internal
|
|
|
|
|
objects that are `autoreleased` but never returned to the caller. These objects can
|
|
|
|
|
never be deallocated unless the method in question was called within an autorelease
|
|
|
|
|
pool.
|
|
|
|
|
|
|
|
|
|
NSWrap assumes you
|
|
|
|
|
are going to take ownership of every Objective-C object returned by a method, either
|
|
|
|
|
directly as a return value or through a pointer to a pointer given as a parameter.
|
|
|
|
|
Therefore, NSWrap calls `retain` on all of these objects before going back to the
|
|
|
|
|
Go side, unless the object is either `nil` or equivalent to the input object.
|
|
|
|
|
NSWrap also will not call `retain` on the return values of `init`, `new`, `copy`,
|
|
|
|
|
`mutableCopy` or `alloc` methods. If you do not want ownership of the object,
|
|
|
|
|
simply assign it to a local varable and the garbage collector will take care of
|
|
|
|
|
releasing it.
|
|
|
|
|
|
|
|
|
|
In
|
|
|
|
|
order for this to work on a pointer to a pointer parameter, NSWrap treats the
|
|
|
|
|
input
|
|
|
|
|
parameter as an array with a length specified by either a `range` parameter (of
|
|
|
|
|
type `NSRange`) or a `count` parameter of an integer type. If there is neither a
|
|
|
|
|
`range` or `count` parameter, NSWrap assumes the array is length 1.
|
|
|
|
|
|
|
|
|
|
As an example, in Objective-C, if you were to take an object out of an `NSArray`
|
|
|
|
|
and the array was later deallocated, there is no guarantee that the object you
|
|
|
|
|
obtained is still around unless you called `retain` on it. This is not necessary
|
|
|
|
|
with NSWrap, which automatically retains objects returned by methods like
|
|
|
|
|
`objectAtIndex:` and `getObjects:range` and manages them with the Go garbage
|
|
|
|
|
collector.
|
|
|
|
|
|
|
|
|
|
The methods described above work for methods that return Objective-C
|
|
|
|
|
objects, which can be `retain`ed, but not with methods that return other types of
|
|
|
|
|
pointers such as C strings. NSWrap has a special case for C strings (`*Char`
|
|
|
|
|
in Go), calling
|
|
|
|
|
`strdup` on the return value within the `@autoreleasepool` block. This
|
|
|
|
|
ensures that the string is preserved even if it points to a termporary
|
|
|
|
|
autoreleased
|
|
|
|
|
object. Since this behavior results in newly allocated memory, these pointers will
|
|
|
|
|
need to be freed from Go later on. Since these are pointers to C memory,
|
|
|
|
|
it is not possible to set a finalizer on these pointers for garbage collection
|
|
|
|
|
by Go.
|
|
|
|
|
|
|
|
|
|
Note that the Go garbage collector is lazy and will not activate unless your
|
|
|
|
|
application is running low on heap space. That means in practice that Objective-C
|
|
|
|
|
objects are going to stick around a lot longer than they might in a pure
|
|
|
|
|
Objective-C application. If this is an issue, simply run the Go GC manually with
|
|
|
|
|
`runtime.GC()`.
|
|
|
|
|
|
|
|
|
|
|
2019-05-29 00:03:51 -04:00
|
|
|
|
# Limitations
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
## Blocks and Function Pointers
|
2019-05-29 00:03:51 -04:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2019-06-27 11:41:58 -04:00
|
|
|
|
This work was inspired by Maxim Kupriianov's excellent
|
2019-05-29 00:03:51 -04:00
|
|
|
|
[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.
|