You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* objc: docs and api tweaks. add Retain
* dispatch: docs and api tweaks
* macos: update examples and helpers to api tweaks
* Makefile: use helloworld as main example
* docs: start basic docs
> Aug 15, 2023: **MacDriver is becoming DarwinKit**, which increases API coverage by an order of magnitude and is an overall upgrade in quality and scope. It has been rewritten, reorganized, and definitely has breaking API changes. The [legacy branch](https://github.com/progrium/macdriver/tree/legacy) and [previous releases](https://github.com/progrium/macdriver/releases) are still available for existing code to work against. We're working towards a [0.5.0-preview release](https://github.com/progrium/macdriver/issues/177) followed by a [0.5.0 release](https://github.com/progrium/macdriver/milestone/4) finalizing the new API and rename to DarwinKit.
12
13
13
14
------
14
15
15
-
DarwinKit lets you work with [supported Apple frameworks](https://pkg.go.dev/github.com/progrium/macdriver/macos@main#section-directories) and build native applications using Go. It makes developing simple applications simple. With XCode and Go 1.18+ installed, you can write this program in a `main.go` file:
16
+
DarwinKit lets you work with [supported Apple frameworks](https://pkg.go.dev/github.com/progrium/macdriver/macos@main#section-directories) and build native applications using Go. With XCode and Go 1.18+ installed, you can write this program in a `main.go` file:
@@ -74,9 +77,9 @@ Although currently outside the scope of this project, if you wanted you could pu
74
77
75
78
* You still need to know or learn how Apple frameworks work, so you'll have to use Apple documentation and understand how to translate Objective-C example code to the equivalent Go with DarwinKit.
76
79
* Your programs link against the actual Apple frameworks, so XCode needs to be installed for the framework headers and any program built will use [cgo](https://pkg.go.dev/cmd/cgo).
80
+
* You will be using two memory management systems. Framework objects are managed by the [Objective-C memory manager](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011-SW1), so be sure to read our docs on [memory management](docs/memorymanagement.md).
77
81
* Exceptions in frameworks will segfault, giving you both an Objective-C stacktrace and a Go panic stacktrace. You will be debugging a hybrid Go and Objective-C program.
78
-
* Goroutines that interact with GUI objects need to dispatch operations on the main thread otherwise it will segfault.
79
-
* You will be using two memory management systems. Framework objects are managed by the [Objective-C memory manager](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011-SW1) and you will use Retain, Release, or Autorelease on them on top of considering Go memory management.
82
+
* Goroutines that interact with GUI objects need to [dispatch](https://pkg.go.dev/github.com/progrium/macdriver@main/dispatch) operations on the main thread otherwise it will segfault.
80
83
* This is all tenable for simple programs, but these are the reasons we don't *recommend* large/complex programs using DarwinKit.
81
84
82
85
## Examples
@@ -90,13 +93,16 @@ Although currently outside the scope of this project, if you wanted you could pu
90
93
91
94
## How it works
92
95
93
-
After acquiring NeXT Computer in the 90s, Apple used [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) as the basis of their software stack, which was written in Objective-C. Unlike most systems languages with object orientation, especially of the C lineage, Objective-C implements OOP as a runtime library. The weird OOP specific *syntax* is effectively rewritten into C calls to [libobjc](https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc), which is a normal C library implementing the Objective-C runtime. This runtime could be used to bring OOP to any language that can make calls to C code. It also lets you interact with objects and classes registered by other libraries, such as the Apple frameworks.
96
+
<details>
97
+
<summary>Brief background on Objective-C</summary>
98
+
Ever since acquiring NeXT Computer in the 90s, Apple has used [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) as the basis of their software stack, which is written in Objective-C. Unlike most systems languages with object orientation, Objective-C implements OOP as a runtime library. In fact, Objective-C is just C with the weird OOP specific syntax rewritten into C calls to [libobjc](https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc), which is a normal C library implementing an object runtime. This runtime could be used to bring OOP to any language that can make calls to C code. It also lets you interact with objects and classes registered by other libraries, such as the Apple frameworks.
99
+
</details>
94
100
95
101
At the heart of DarwinKit is a package wrapping the Objective-C runtime using cgo and libffi. This is actually all you need to interact with Objective-C objects and classes, it'll just look like this:
So we wrap these calls in a Go API that lets us write code like this:
@@ -110,12 +116,12 @@ These bindings are great, but we need to define them for every API we want to us
110
116
Apple has around 200 frameworks of nearly 5000 classes with 77k combined methods and properties. Not to
111
117
mention all the constants, functions, structs, unions, and enums we need to work with those objects.
112
118
113
-
So DarwinKit generates its bindings. This is the hard part. Making sure the generation pipeline accurately produces usable bindings for all possible symbols is quite an arduous, iterative, manual process. Then since we're moving symbols that lived in a single namespace into Go packages, we have to manually decouple dependencies between them enough to avoid circular imports. If you want to help add frameworks, this whole process is documented here.
119
+
So DarwinKit generates its bindings. This is the hard part. Making sure the generation pipeline accurately produces usable bindings for all possible symbols is quite an arduous, iterative, manual process. Then since we're moving symbols that lived in a single namespace into Go packages, we have to manually decouple dependencies between them enough to avoid circular imports. If you want to help add frameworks, read our documentation on [generation](docs/generation.md).
114
120
115
121
Objects in Objective-C are passed around as typed pointer values. When we receive an object from a method
116
122
call in Go, the `objc` package receives it as a pointer, which it first puts into an `unsafe.Pointer`. The
117
123
bindings for a class define a struct type that embeds an `objc.Object` struct, which contains a single
118
-
field to hold the `unsafe.Pointer`. So unless working with a primitive type, you're working with an `unsafe.Pointer` wrapped in an `objc.Object` wrapped in a struct type that has the methods for the class of the object of the pointer.
124
+
field to hold the `unsafe.Pointer`. So unless working with a primitive type, you're working with an `unsafe.Pointer` wrapped in an `objc.Object` wrapped in a struct type that has the methods for the class of the object of the pointer. Be sure to read our documentation on [memory management](docs/memorymanagement.md).
119
125
120
126
If you have questions, feel free to ask in the [discussion forums](https://github.com/progrium/macdriver/discussions).
Working with Objective-C from Go requires understanding how [Objective-C memory management works](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011-SW1) and how DarwinKit facilitates the co-existance of the two memory management systems.
4
+
5
+
Objective-C uses reference counting or a "retain and release" model where objects are deallocated when their internal retain count reaches zero. When an object is allocated, its retain count is set to 1. It is the responsibility of the allocating code to release the object when finished, decrementing its retain count by 1. This would deallocate the object unless other code has retained the object, incrementing the retain count by 1. Every alloc or retain must have a subsequent release.
6
+
7
+
It should be noted that modern Objective-C and Swift code don't have to do this manual retain and release process because of Automatic Reference Counting, or ARC, which is a feature of their compiler that figures out where to insert retains and releases for you. Our code is compiled by Go so we don't get to take advantage of this, but we do have access to the underlying retain and release methods.
8
+
9
+
That said, even without ARC there is a way to ease retain and release by using autorelease pools. Along with retain and release methods on all objects, there is also an autorelease method. This marks the object for a deferred release, somewhat like using Go defer to cleanup a resource. Autoreleased objects will be released when the last created autorelease pool on the stack is drained.
10
+
11
+
In DarwinKit we have `objc.WithAutoreleasePool()` which takes a function that is immediately executed with a new autorelease pool that is drained when the function is finished. In other words, any objects that have autorelease called from the given function will get released after the function returns. This is equivalent to the `@autoreleasepool`[block syntax](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-CJBFBEDI) in Objective-C.
12
+
13
+
Luckily, most of the code we write using Apple frameworks lives in delegate methods or callbacks that are called from an event loop managed by the AppKit framework, and each iteration of the loop has its own autorelease pool. So the common scenario for objects is simply making sure autorelease is called on them after allocating so they will be released at the end of the current cycle of the event loop.
14
+
15
+
The Objective-C [memory management policy](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH) specifies basic rules to know how to use retain and release. The basic idea is that if you allocate an object (using `alloc`, `new`, `copy`, or `mutableCopy`), you own it and are responsible for releasing or autoreleasing it. When an object that you did not explicitly allocate is returned by a function, you do not need to release it unless you take ownership of it by retaining it. This also applies to objects returned by class methods that create new objects like `NSString#stringWithFormat`. Since they are allocating we can assume they are calling autorelease before returning.
16
+
17
+
As it turns out, DarwinKit generates Go idiomatic New functions for all classes that does an `alloc` and `init` followed by an `autorelease` before returning. You can always do your own explicit allocation if this is not the desired behavior, but this means in the common case where you are writing code in a delegate or callback that will be called from the application event loop, or are otherwise in an autorelease pool, you can basically write Go code as usual and not have to do anything special. UNLESS you *do* need to take ownership of an object.
18
+
19
+
If you need the object to live longer than this event loop iteration, you will need to take ownership. So if you assign it to a variable declared outside the event loop, like a global variable, or assign to a struct field or append to a slice that was declared outside the loop, or pass by reference to anything that will need it to stick around, you will probably want to retain the object and have the Go garbage collector be responsible for releasing the object. Another situation you will need to take ownership like this is using the object in a new goroutine.
20
+
21
+
DarwinKit provides `objc.Retain()`, which calls retain on the object and creates a [Go finalizer](https://pkg.go.dev/runtime#SetFinalizer) that will release the object when Go garbage collects it. We recommend this instead of calling retain and release directly on the object, but you have that option if you know what you're doing.
22
+
23
+
Long story short, there's only one special thing you have to do in your Go code to accomodate the Objective-C memory management system, which is use `objc.Retain()` when you need to keep an object around or use it from a goroutine. If you work with objects outside the application event loop, either in programs that don't start it or in the code before starting it, you can just wrap it all with an `objc.WithAutoreleasePool()`.
24
+
25
+
That's it. The majority of the concern is dealing with Objective-C objects in "Go space". Go values in "Objective-C space" are not so much a problem because values are either passed by value, are already Objective-C objects, or are delegates or callbacks that are kept from Go garbage collection by the `objc` package.
0 commit comments