objc

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-macos/objc

CI Go Reference Go 1.26.4

A pure-Go (CGO_ENABLED=0) bridge to the macOS Objective-C runtime, reached entirely through ebitengine/puregodlopen + objc_msgSend, no cgo, no shelling out to osascript.

It is the single shared home for the Objective-C plumbing that the fleet's CGO=0 macOS code kept re-implementing: selector/class lookup, NSString↔Go bridging, framework dlopen, autorelease-pool handling and run loops.

Surface

Area API
Selector / class lookup (cached) Sel, RegisterName, GetClass, ClassID
Messaging ID.Send, generic Send[T] (scalars, CGFloat, structs)
Runtime classes RegisterClass, RegisterClassWithProtocols, GetProtocol, MethodDef
Strings NSString, GoString
Objects / dictionaries Stringify, DictToMap, MapToDict
Frameworks Load + Foundation / AppKit / WebKit / CoreFoundation / Security / LibSystem
Scopes AutoreleasePool
Run loops App, RunApp, Run(ctx, class, setup) + Runner
Validation ValidateName
import "github.com/go-macos/objc"

objc.Load(objc.Foundation, objc.AppKit)

s := objc.GoString(objc.NSString("héllo"))                 // "héllo"
n := objc.Send[float64](objc.NSString("3.5"), objc.Sel("doubleValue")) // 3.5

cls, _ := objc.RegisterClass("MyTarget", objc.GetClass("NSObject"),
    []objc.MethodDef{
        {Cmd: objc.Sel("fire:"), Fn: func(self objc.ID, _ objc.SEL, sender objc.ID) {
            // handle sender
        }},
    })

Design

  • On darwin the runtime types (ID, SEL, Class, IMP, MethodDef) are aliases of the corresponding purego/objc types, so method closures satisfy purego's strict NewIMP (id, SEL) receiver check and existing purego-based call sites can switch their import to this package and keep compiling.
  • On non-darwin the same symbols exist as uintptr-width types and stubs that report [ErrUnsupported], so consumers cross-compile. The OS-independent core (selector/class caches, name validation, the GoString marshalling logic and Load) stays fully functional and is covered 100% on every lane through injection seams.
  • The darwin C/objc calls sit behind fake-injection seams, so their error branches are reachable in tests; the real objc_msgSend / NSString / RegisterClass / run-loop paths are verified on-device in CI.

License

BSD-3-Clause © the go-macos/objc authors.

Documentation

Overview

Package objc is a pure-Go (CGO_ENABLED=0) bridge to the macOS Objective-C runtime. It reaches the OS entirely through github.com/ebitengine/purego — dlopen + objc_msgSend — so it links with no cgo and no shelling out to osascript.

It exists to end the copy-paste of the same Objective-C plumbing across the fleet's CGO=0 macOS code (go-macos/notify, go-widgets/tray, the go-news-reader and go-reddit readers, …): every one of them independently re-implemented selector/class lookup, NSString<->Go bridging, framework dlopen, autorelease-pool handling and a run loop. This package is the single shared home for those primitives.

Surface

  • Sel / GetClass / ClassID: cached selector and class lookup.
  • ID.Send and the generic Send: typed objc_msgSend helpers (the generic form carries float64/CGFloat and struct returns through the correct calling convention).
  • RegisterClass: define an Objective-C class at runtime from Go method closures.
  • NSString / GoString: NSString<->Go string conversion. GoString copies via -getCString:maxLength:encoding: (never a raw -UTF8String pointer deref, which trips go vet's unsafeptr check).
  • Stringify / DictToMap / MapToDict: NSObject/NSDictionary helpers.
  • Load plus the framework-path constants (Foundation, AppKit, WebKit, CoreFoundation, LibSystem): idempotent dlopen.
  • AutoreleasePool: run a closure inside an NSAutoreleasePool scope.
  • App / RunApp: the shared NSApplication and its [NSApp run] loop.
  • Run: a Foundation run-loop runner pinned to a locked OS thread, with a task queue serviced on that thread — for observer-driven Foundation work (e.g. NSDistributedNotificationCenter).

Portability

Every exported symbol is defined on all platforms so consumers cross-compile. On non-darwin GOOS the runtime entry points report ErrUnsupported (or return a zero ID); the OS-independent logic — the selector/class caches, name validation and the string-marshalling core — stays fully functional and testable there.

Index

Constants

View Source
const (
	Foundation     = "/System/Library/Frameworks/Foundation.framework/Foundation"
	AppKit         = "/System/Library/Frameworks/AppKit.framework/AppKit"
	WebKit         = "/System/Library/Frameworks/WebKit.framework/WebKit"
	CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
	Security       = "/System/Library/Frameworks/Security.framework/Security"
	LibSystem      = "/usr/lib/libSystem.B.dylib"
)

Standard framework and dylib paths for Load.

Variables

View Source
var (
	// ErrUnsupported is returned by the runtime entry points on non-darwin
	// platforms (the whole runtime is macOS-only).
	ErrUnsupported = errors.New("objc: unsupported on this platform (darwin only)")
	// ErrEmptyName is returned when a selector or class name is empty.
	ErrEmptyName = errors.New("objc: empty name")
	// ErrNameHasNUL is returned when a selector or class name contains a NUL
	// byte (the C runtime terminates names at the first NUL, so an embedded one
	// would silently truncate).
	ErrNameHasNUL = errors.New("objc: name contains NUL byte")
	// ErrDlopen wraps a framework dlopen failure from [Load].
	ErrDlopen = errors.New("objc: dlopen failed")
)

Errors reported by the package. They are stable and may be tested with errors.Is.

Functions

func AutoreleasePool

func AutoreleasePool(fn func())

AutoreleasePool runs fn directly on non-darwin platforms (there is no pool to establish), so callers can wrap code unconditionally.

func DictToMap

func DictToMap(dict ID) map[string]string

DictToMap reports an empty map on non-darwin platforms.

func GoString

func GoString(id ID) string

GoString copies an NSString's UTF-8 bytes into a Go-owned buffer and returns them. It returns "" for the nil object or an empty string. It fills a Go slice via -getCString:maxLength:encoding: rather than dereferencing the ObjC-owned -UTF8String pointer, so it stays free of any uintptr->Pointer arithmetic (the buffer handed to ObjC is a live Go pointer the collector tracks).

func Load

func Load(paths ...string) error

Load dlopens each of paths (idempotently — dlopen refcounts, and a repeat open of an already-resident framework is cheap). It returns the first failure wrapped in ErrDlopen. Use the framework-path constants, e.g. Load(Foundation, AppKit).

func Run

func Run(ctx context.Context, runnerClass Class, setup func(r *Runner)) error

Run reports ErrUnsupported on non-darwin platforms.

func RunApp

func RunApp(policy int)

RunApp is a no-op on non-darwin platforms.

func Send

func Send[T any](id ID, sel SEL, args ...any) T

Send returns the zero value of T on non-darwin platforms.

func Stringify

func Stringify(v ID) string

Stringify reports "" on non-darwin platforms.

func ValidateName

func ValidateName(name string) error

ValidateName rejects names the C runtime cannot carry: empty (ErrEmptyName) or containing a NUL byte (ErrNameHasNUL, since objc_registerName / objc_getClass terminate at the first NUL and would silently truncate). It is a convenience for consumers that build selector or class names from untrusted input before passing them to Sel, GetClass or RegisterClass.

Types

type Class

type Class uintptr

Class is an Objective-C class object.

func GetClass

func GetClass(name string) Class

GetClass returns the class named name, caching the result. An unknown class (or the zero name) yields the zero class.

func RegisterClass

func RegisterClass(name string, super Class, methods []MethodDef) (Class, error)

RegisterClass reports ErrUnsupported on non-darwin platforms.

func RegisterClassWithProtocols added in v0.1.1

func RegisterClassWithProtocols(name string, super Class, protocols []*Protocol, methods []MethodDef) (Class, error)

RegisterClassWithProtocols reports ErrUnsupported on non-darwin platforms.

type ID

type ID uintptr

ID is an Objective-C object pointer; the zero value is nil.

func App

func App() ID

App reports the zero ID on non-darwin platforms.

func ClassID

func ClassID(name string) ID

ClassID returns the class named name as an ID, so class ("factory") messages (alloc, sharedApplication, …) can be sent to it directly.

func MapToDict

func MapToDict(m map[string]string) ID

MapToDict reports the zero ID on non-darwin platforms.

func NSString

func NSString(s string) ID

NSString reports the zero ID on non-darwin platforms.

func (ID) Send

func (id ID) Send(sel SEL, args ...any) ID

Send reports the zero ID on non-darwin platforms (there is no runtime to message). On darwin the equivalent method is inherited from purego's objc.ID.

type IMP

type IMP uintptr

IMP is an Objective-C method implementation pointer.

type MethodDef

type MethodDef struct {
	Cmd SEL
	Fn  any
}

MethodDef binds a selector to a Go closure for RegisterClass. On darwin this is an alias of purego's objc.MethodDef; here it is the equivalent shape.

type Protocol added in v0.1.1

type Protocol struct{}

Protocol is an Objective-C protocol. On darwin it is an alias of purego's objc.Protocol; here it is a placeholder so the signatures compile.

func GetProtocol added in v0.1.1

func GetProtocol(name string) *Protocol

GetProtocol reports a nil protocol on non-darwin platforms.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner is the non-darwin stand-in for the Run handle. Its methods are no-ops (Submit runs fn directly) so consumer code that references them still compiles.

func (*Runner) Object

func (r *Runner) Object() ID

Object reports the zero ID on non-darwin platforms.

func (*Runner) Submit

func (r *Runner) Submit(fn func())

Submit runs fn directly on non-darwin platforms.

type SEL

type SEL uintptr

SEL is an Objective-C selector.

func RegisterName

func RegisterName(name string) SEL

RegisterName is an alias for Sel. It carries purego's spelling so a call site migrated from github.com/ebitengine/purego/objc compiles unchanged while gaining the selector cache.

func Sel

func Sel(name string) SEL

Sel returns the selector named name, caching the result so repeated lookups are a map hit. An empty name yields the zero selector.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL