DarwinKit Documentation

repository·main·Indexed 26 days ago

https://github.com/progrium/darwinkit

DarwinKit provides native Apple framework bindings for Golang, enabling the development of macOS applications using Go instead of Objective-C or Swift. It allows direct interaction with frameworks such as AppKit, Foundation, and WebKit. The library includes specific API conventions for mapping Objective-C patterns to Go, tools for generating framework bindings, and guidelines for managing Objective-C memory and threading within the Go runtime.

Tokens
3.4K
Snippets
5
Records
13
Agent score
90%

What's inside DarwinKit

  1. Understand DarwinKit memory management policy

    main

    DarwinKit follows the Objective-C policy: "the code that allocates is the code responsible for releasing."

    • Go-style Constructors (New...): These functions perform an alloc and init followed by an autorelease. You do not need to manually release these unless you explicitly called Alloc().
    • Ownership: If you use an object returned by a function that you did not explicitly allocate, you do not own it. You only take ownership if you explicitly call Retain().
    • Segfaults: If you encounter unexplained segmentation faults, it is often because an object (like an appkit.Window) was not retained and was deallocated by the autorelease pool before it was used again.
  2. Quickstart: Build a macOS application with DarwinKit

    main

    To build a native macOS application using Go, ensure you have XCode and Go 1.18+ installed. DarwinKit allows you to interact with Apple frameworks like AppKit, Foundation, and WebKit directly from Go.

    1. Create your project

    Initialize a new Go module and fetch the DarwinKit dependency:

    go mod init helloworld
    go get github.com/progrium/darwinkit@main

    2. Write your application

    Create a main.go file. The following example demonstrates how to launch a macOS application event loop and display a WKWebView window:

    package main
    
    import (
    	"github.com/progrium/darwinkit/objc"
    	"github.com/progrium/darwinkit/macos"
    	"github.com/progrium/darwinkit/macos/appkit"
    	"github.com/progrium/darwinkit/macos/foundation"
    	"github.com/progrium/darwinkit/macos/webkit"
    )
    
    func main() {
    	// runs macOS application event loop with a callback on success
    	macos.RunApp(func(app appkit.Application, delegate *appkit.ApplicationDelegate) {
    		app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular)
    		app.ActivateIgnoringOtherApps(true)
    
    		url := foundation.URL_URLWithString("https://github.com/sponsors/darwinkitdev")
    		req := foundation.NewURLRequestWithURL(url)
    		frame := foundation.Rect{Size: foundation.Size{1440, 900}}
    
    		config := webkit.NewWebViewConfiguration()
    		wv := webkit.NewWebViewWithFrameConfiguration(frame, config)
    		wv.LoadRequest(req)
    
    		w := appkit.NewWindowWithContentRectStyleMaskBackingDefer(frame,
    			appkit.ClosableWindowMask|appkit.TitledWindowMask,
    			appkit.BackingStoreBuffered, false)
    		objc.Retain(&w)
    		w.SetContentView(wv)
    		w.MakeKeyAndOrderFront(w)
    		w.Center()
    
    		delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool {
    			return true
    		})
    	})
    }

    3. Run or Build

    Run the application directly:

    go run main.go

    Or build an executable:

    go build
    go mod init helloworld
    go get github.com/progrium/darwinkit@main
    go run main.go
  3. Understand DarwinKit Bindings API conventions

    main

    DarwinKit provides bindings to Apple frameworks using specific naming conventions to map Objective-C patterns to Go. When using the bindings, expect the following transformations:

    Framework Packages

    Frameworks are accessed via lowercase packages. If a framework name is exceptionally long, it may use a lowercase prefix (e.g., uniformtypeidentifiers becomes uti).

    Symbol Naming

    • Prefixes: Symbol prefixes are removed (e.g., CGPoint becomes Point).
    • Constants/Enums: These are 1:1 mappings. If a constant has a k prefix, it is preserved but capitalized (e.g., kCGImageStatusInvalidData becomes KImageStatusInvalidData).

    Class Mapping

    For a class like NSWindow, DarwinKit provides several representations:

    • Struct: Window (embeds superclass structs).
    • Interface: IWindow (prefixed with I, embeds superclass interfaces).
    • Unexported Struct: _WindowClass.
    • Singleton: A variable named WindowClass.

    Method and Function Mapping

    • Instance Methods: Mapped 1:1 with selector names converted to PascalCase (e.g., setFrame:display: becomes SetFrameDisplay).
    • Overlapping Selectors: If selectors overlap, the one with arguments gets a _ suffix (e.g., reload becomes Reload, while reload: becomes Reload_).
    • Protocol Arguments: Methods accepting protocols provide alternative methods where the argument is passed as an object.
    • Lifecycle/Initialization:
      • alloc/init/autorelease are replaced by a New function (e.g., NewWindow).
      • Init methods like initWithFrame: become NewWindowWithFrame(...).
    • Class Methods: Mapped 1:1 on the class type, but also available as function variants using the pattern ClassName_MethodName (e.g., Window_WindowNumbersWithOptions(...)).
  4. Manage Objective-C object lifetimes in Go using objc.Retain

    main

    Because DarwinKit uses Manual Reference Counting (MRR) instead of Automatic Reference Counting (ARC), objects created via Go-style constructors (prefixed with New) are automatically marked for Autorelease(). This means they are only valid for the duration of the current autorelease pool (typically the current AppKit event loop cycle).

    To keep an object alive beyond the current event loop cycle—for example, if you are storing it in a global variable, a struct field, a slice, or using it within a new goroutine—you must take ownership of it.

    Use objc.Retain() instead of calling the object's Retain() method directly. objc.Retain() increments the retain count and attaches a Go finalizer so that the Go Garbage Collector will automatically release the Objective-C object when the Go reference is cleaned up.

  5. Wrap code in an autorelease pool with objc.WithAutoreleasePool

    main

    Most DarwinKit code runs within the AppKit event loop, which provides an autorelease pool for every cycle. However, if you are running code outside of this loop—such as code executed before appkit.Application is run, or code running inside a goroutine—you must manually manage the autorelease pool to ensure objects marked for deferred release are properly cleaned up.

    Use objc.WithAutoreleasePool(fn func()) to execute a block of code within a new autorelease pool. The pool will be drained (and all autoreleased objects released) once the function returns.

  6. Download the symbolsdb resource

    main

    The symbolsdb is an immutable external resource consisting of a zip file containing JSON documents for every symbol across Apple frameworks. You must download this resource before running generation tasks.

    Run the following command to download it into the generate package directory:

    make generate/symbols.zip
  7. Decouple frameworks to avoid circular imports

    main

    When adding frameworks, you may encounter circular imports. To resolve this:

    1. Use the imports tool to inspect all Go imports across the package/directory.
    2. Identify dependency chains that lead back to the current package.
    3. Add selected dependent packages to the appropriate list in the modules package (in modules.go) to decouple them.
    4. When choosing which framework to decouple, consider picking the one that is lower-level or has fewer dependency points.
    ./generate/tools/imports.sh ./macos/appkit
  8. Important Caveats and Best Practices

    main

    When using DarwinKit, be aware of the following technical constraints and requirements:

    • Framework Knowledge: You must understand Apple's native frameworks and be able to translate Objective-C documentation/examples into DarwinKit Go code.
    • Dependencies: DarwinKit links against Apple frameworks using cgo, so XCode must be installed to provide the necessary framework headers.
    • Memory Management: You are managing two memory systems simultaneously. Framework objects are managed by Objective-C memory management. Refer to the memory management documentation for specific DarwinKit details.
    • Error Handling: Exceptions within Apple frameworks will cause a segfault, resulting in both an Objective-C stacktrace and a Go panic stacktrace.
    • Threading: Goroutines that interact with GUI objects must use dispatch to perform operations on the main thread; otherwise, the application will segfault.
  9. Add a new framework to DarwinKit

    main

    To add a new framework, follow these steps in order:

    1. Prepare: Run make generate/symbols.zip before starting.
    2. Register Module: Add the framework to the known modules list in generate/modules/modules.go. If the framework depends on another framework you aren't ready to add, add its name to the CanIgnoreNotFound list.
    3. Generate Constants: Use enumexport to get constants and enums. Run it with your framework as an argument, inspect the output, and then pipe it to the appropriate file under ./generate/modules/enums/macos/.
    4. Initialize Package: Run initmod to create the initial non-generated files for the package.
    5. Generate Structs: Use the structs tool to generate documented Go structs. This is done out-of-band so they can be manually tweaked.
    6. Run Generation: Execute go generate ./macos/your-framework. If it panics, it means you need to handle a specific type or module manually.
    7. Verify: Run go test ./macos/your-framework. You may need to manually add struct types that were not generated or handle pointers to unknown structs as unsafe.Pointer.
  10. Generate Go structs for a framework

    main

    Use the structs tool to generate documented Go structs for a specific framework. This is an out-of-band process intended to allow for manual tweaking. If the tool cannot generate a specific struct, it will list them in comments at the bottom of the output. If a field uses a type that couldn't be generated, the field will start with _Ctype_struct_, which you should replace or comment out.

    go run ./generate/tools/structs.go foundation > ./macos/foundation/foundation_structs.go
  11. Reference: Generation Tools

    main

    The following tools are used for the DarwinKit generation process. Note: All tools must be run from the project root.

    # Removes all files under the path that have the auto-generated banner
    clobbergen [path]
    go run ./generate/tools/clobbergen.go ./macos/appkit
    
    # Shows the value for a constant from ./generate/modules/enums
    constant [platform] [framework] [constant]
    go run ./generate/tools/constant.go macos appkit NSWindowBelow
    
    # Runs declparse against symbol declarations for a given framework in symbolsdb
    declcheck [framework]
    go run ./generate/tools/declcheck.go appkit
    
    # Generates a program that outputs constants and enum values for a framework
    enumexport [framework]
    go run ./generate/tools/enumexport.go appkit > ./generate/modules/enums/macos/appkit
    
    # Runs generation for a framework package (invoked by go generate)
    genmod
    go generate ./macos/appkit
    
    # Creates the framework package directory and starting non-generated files
    initmod [platform] [framework]
    go run ./generate/tools/initmod.go macos appkit
    
    # Shows all the Go imports for a package path
    imports [path]
    ./generate/tools/imports.sh ./macos/appkit
    
    # Finds all symbols in symbolsdb with a path prefixed with the given prefix
    lookup [prefix]
    go run ./generate/tools/lookup.go appkit | jq 'select(.Kind == "Framework")'
    
    # Finds type symbol(s) in symbolsdb with the given type name
    type [symbol]
    go run ./generate/tools/type.go NSWindow
    
    # Re-generates frameworks that have been generated (those with .gen.go files)
    regen [platform]
    ./generate/tools/regen.sh macos
    
    # Generates documented Go structs for a framework
    structs [framework]
    go run ./generate/tools/structs.go foundation > ./macos/foundation/foundation_structs.go
  12. Check declaration parsing coverage with declcheck

    main
    The declparse package parses raw header declarations into structured representations. If you encounter symbols that cannot be parsed, use the declcheck tool to run through all symbols for a specific framework. It will provide a report showing the percentage of coverage and any errors encountered.