v0.12.0-alpha.1 9f4a5c8

objc_runtime_darwin.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4// Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best
5// to use a wrapper that provides the functionality you need in a safer way.
6package objc
7
8import (
9	"errors"
10	"fmt"
11	"math"
12	"reflect"
13	"regexp"
14	"runtime"
15	"slices"
16	stdstrings "strings"
17	"structs"
18	"unicode"
19	"unsafe"
20
21	"github.com/ebitengine/purego"
22	"github.com/ebitengine/purego/internal/strings"
23)
24
25var hostLayoutType = reflect.TypeFor[structs.HostLayout]()
26
27// TODO: support try/catch?
28// https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
29var (
30	objc_msgSend_fn                    uintptr
31	objc_msgSend_stret_fn              uintptr
32	objc_msgSend                       func(obj ID, cmd SEL, args ...any) ID
33	objc_msgSendSuper2_fn              uintptr
34	objc_msgSendSuper2_stret_fn        uintptr
35	objc_msgSendSuper2                 func(super *objc_super, cmd SEL, args ...any) ID
36	objc_getClass                      func(name string) Class
37	objc_getProtocol                   func(name string) *Protocol
38	objc_allocateProtocol              func(name string) *Protocol
39	objc_registerProtocol              func(protocol *Protocol)
40	objc_allocateClassPair             func(super Class, name string, extraBytes uintptr) Class
41	objc_registerClassPair             func(class Class)
42	sel_registerName                   func(name string) SEL
43	class_getSuperclass                func(class Class) Class
44	class_getInstanceVariable          func(class Class, name string) Ivar
45	class_getInstanceSize              func(class Class) uintptr
46	class_addMethod                    func(class Class, name SEL, imp IMP, types string) bool
47	class_addIvar                      func(class Class, name string, size uintptr, alignment uint8, types string) bool
48	class_addProtocol                  func(class Class, protocol *Protocol) bool
49	ivar_getOffset                     func(ivar Ivar) uintptr
50	ivar_getName                       func(ivar Ivar) string
51	object_getClass                    func(obj ID) Class
52	object_getIvar                     func(obj ID, ivar Ivar) ID
53	object_setIvar                     func(obj ID, ivar Ivar, value ID)
54	protocol_getName                   func(protocol *Protocol) string
55	protocol_isEqual                   func(p *Protocol, p2 *Protocol) bool
56	protocol_addMethodDescription      func(p *Protocol, name SEL, types string, isRequiredMethod bool, isInstanceMethod bool)
57	protocol_copyMethodDescriptionList func(p *Protocol, isRequiredMethod bool, isInstanceMethod bool, outCount *uint32) *MethodDescription
58	protocol_copyProtocolList          func(p *Protocol, outCount *uint32) **Protocol
59	protocol_copyPropertyList2         func(p *Protocol, outCount *uint32, isRequiredProperty, isInstanceProperty bool) *Property
60	protocol_addProtocol               func(p *Protocol, p2 *Protocol)
61	protocol_addProperty               func(p *Protocol, name string, attributes []PropertyAttribute, attributeCount uint32, isRequiredProperty bool, isInstanceProperty bool)
62	property_getName                   func(p Property) string
63	property_getAttributes             func(p Property) string
64
65	free           func(ptr unsafe.Pointer)
66	_Block_copy    func(Block) Block
67	_Block_release func(Block)
68)
69
70func init() {
71	objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL|purego.RTLD_NOW)
72	if err != nil {
73		panic(fmt.Errorf("objc: %w", err))
74	}
75	objc_msgSend_fn, err = purego.Dlsym(objc, "objc_msgSend")
76	if err != nil {
77		panic(fmt.Errorf("objc: %w", err))
78	}
79	if runtime.GOARCH == "amd64" {
80		objc_msgSend_stret_fn, err = purego.Dlsym(objc, "objc_msgSend_stret")
81		if err != nil {
82			panic(fmt.Errorf("objc: %w", err))
83		}
84		objc_msgSendSuper2_stret_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2_stret")
85		if err != nil {
86			panic(fmt.Errorf("objc: %w", err))
87		}
88	}
89	purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
90	objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")
91	if err != nil {
92		panic(fmt.Errorf("objc: %w", err))
93	}
94	purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn)
95	purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
96	purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
97	purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
98	purego.RegisterLibFunc(&objc_allocateProtocol, objc, "objc_allocateProtocol")
99	purego.RegisterLibFunc(&objc_registerProtocol, objc, "objc_registerProtocol")
100	purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair")
101	purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair")
102	purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName")
103	purego.RegisterLibFunc(&class_getSuperclass, objc, "class_getSuperclass")
104	purego.RegisterLibFunc(&class_getInstanceVariable, objc, "class_getInstanceVariable")
105	purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod")
106	purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar")
107	purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol")
108	purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize")
109	purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset")
110	purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName")
111	purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName")
112	purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual")
113	purego.RegisterLibFunc(&protocol_addMethodDescription, objc, "protocol_addMethodDescription")
114	purego.RegisterLibFunc(&protocol_copyMethodDescriptionList, objc, "protocol_copyMethodDescriptionList")
115	purego.RegisterLibFunc(&protocol_copyProtocolList, objc, "protocol_copyProtocolList")
116	purego.RegisterLibFunc(&protocol_addProtocol, objc, "protocol_addProtocol")
117	purego.RegisterLibFunc(&protocol_addProperty, objc, "protocol_addProperty")
118	purego.RegisterLibFunc(&protocol_copyPropertyList2, objc, "protocol_copyPropertyList2")
119	purego.RegisterLibFunc(&property_getName, objc, "property_getName")
120	purego.RegisterLibFunc(&property_getAttributes, objc, "property_getAttributes")
121	purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar")
122	purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar")
123	purego.RegisterLibFunc(&free, purego.RTLD_DEFAULT, "free")
124
125	purego.RegisterLibFunc(&_Block_copy, objc, "_Block_copy")
126	purego.RegisterLibFunc(&_Block_release, objc, "_Block_release")
127	theBlocksCache = newBlockCache()
128}
129
130// ID is an opaque pointer to some Objective-C object
131type ID uintptr
132
133// Class returns the class of the object.
134func (id ID) Class() Class {
135	return object_getClass(id)
136}
137
138// Send is a convenience method for sending messages to objects. This function takes a SEL
139// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
140// of RegisterName.
141func (id ID) Send(sel SEL, args ...any) ID {
142	return objc_msgSend(id, sel, args...)
143}
144
145// GetIvar reads the value of an instance variable in an object.
146func (id ID) GetIvar(ivar Ivar) ID {
147	return object_getIvar(id, ivar)
148}
149
150// SetIvar sets the value of an instance variable in an object.
151func (id ID) SetIvar(ivar Ivar, value ID) {
152	object_setIvar(id, ivar, value)
153}
154
155// keep in sync with func.go
156const maxRegAllocStructSize = 16
157
158// Send is a convenience method for sending messages to objects that can return any type.
159// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
160// It is best to cache the result of RegisterName.
161func Send[T any](id ID, sel SEL, args ...any) T {
162	var fn func(id ID, sel SEL, args ...any) T
163	var zero T
164	if runtime.GOARCH == "amd64" &&
165		reflect.ValueOf(zero).Kind() == reflect.Struct &&
166		reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
167		purego.RegisterFunc(&fn, objc_msgSend_stret_fn)
168	} else {
169		purego.RegisterFunc(&fn, objc_msgSend_fn)
170	}
171	return fn(id, sel, args...)
172}
173
174// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
175// as the receiver of a message.
176//
177// [SendSuper] calls objc_msgSendSuper2 instead of objc_msgSendSuper. Unlike objc_msgSendSuper, which expects
178// the superclass to message, objc_msgSendSuper2 expects the current class and begins looking the method up
179// in its superclass by itself, so the receiver's own class is what belongs in superClass.
180type objc_super struct {
181	_          structs.HostLayout
182	receiver   ID
183	superClass Class
184}
185
186// SendSuper is a convenience method for sending message to object's super. This function takes a SEL
187// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
188// of RegisterName.
189func (id ID) SendSuper(sel SEL, args ...any) ID {
190	super := &objc_super{
191		receiver:   id,
192		superClass: id.Class(),
193	}
194	return objc_msgSendSuper2(super, sel, args...)
195}
196
197// SendSuper is a convenience method for sending message to object's super that can return any type.
198// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
199// It is best to cache the result of RegisterName.
200func SendSuper[T any](id ID, sel SEL, args ...any) T {
201	super := &objc_super{
202		receiver:   id,
203		superClass: id.Class(),
204	}
205	var fn func(objcSuper *objc_super, sel SEL, args ...any) T
206	var zero T
207	if runtime.GOARCH == "amd64" &&
208		reflect.ValueOf(zero).Kind() == reflect.Struct &&
209		reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
210		purego.RegisterFunc(&fn, objc_msgSendSuper2_stret_fn)
211	} else {
212		purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
213	}
214	return fn(super, sel, args...)
215}
216
217// SEL is an opaque type that represents a method selector
218type SEL uintptr
219
220// RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
221// and returns the selector value. This function grabs the global Objective-c lock. It is best to cache the
222// result of this function.
223func RegisterName(name string) SEL {
224	return sel_registerName(name)
225}
226
227// Class is an opaque type that represents an Objective-C class.
228type Class uintptr
229
230// GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
231func GetClass(name string) Class {
232	return objc_getClass(name)
233}
234
235// MethodDef represents the Go function and the selector that ObjC uses to access that function.
236type MethodDef struct {
237	Cmd SEL
238	Fn  any
239}
240
241// IvarAttrib is the attribute that an ivar has. It affects if and which methods are automatically
242// generated when creating a class with RegisterClass. See [Apple Docs] for an understanding of these attributes.
243// The fields are still accessible using objc.GetIvar and objc.SetIvar regardless of the value of IvarAttrib.
244//
245// Take for example this Objective-C code:
246//
247//	@property (readwrite) float value;
248//
249// In Go, the functions can be accessed as follows:
250//
251//	var value = purego.Send[float32](id, purego.RegisterName("value"))
252//	id.Send(purego.RegisterName("setValue:"), 3.46)
253//
254// [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
255type IvarAttrib int
256
257const (
258	ReadOnly IvarAttrib = 1 << iota
259	ReadWrite
260)
261
262// FieldDef is a definition of a field to add to an Objective-C class.
263// The name of the field is what will be used to access it through the Ivar. If the type is bool
264// the name cannot start with `is` since a getter will be generated with the name `isBoolName`.
265// The name also cannot contain any spaces.
266// The type is the Go equivalent type of the Ivar.
267// Attribute determines if a getter and or setter method is generated for this field.
268type FieldDef struct {
269	Name      string
270	Type      reflect.Type
271	Attribute IvarAttrib
272}
273
274// ivarRegex checks to make sure the Ivar is correctly formatted
275var ivarRegex = regexp.MustCompile(`^[a-z_][a-zA-Z0-9_]*$`)
276
277// RegisterClass takes the name of the class to create, the superclass, a list of protocols this class
278// implements, a list of fields this class has and a list of methods. It returns the created class or an error
279// describing what went wrong.
280func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars []FieldDef, methods []MethodDef) (Class, error) {
281	class := objc_allocateClassPair(superClass, name, 0)
282	if class == 0 {
283		return 0, fmt.Errorf("objc: failed to create class with name '%s'", name)
284	}
285	// Add Protocols
286	for _, p := range protocols {
287		if !class.AddProtocol(p) {
288			return 0, fmt.Errorf("objc: couldn't add Protocol %s", protocol_getName(p))
289		}
290	}
291	// Add exported methods based on the selectors returned from ClassDef(string) SEL
292	for idx, def := range methods {
293		imp, err := func() (imp IMP, err error) {
294			defer func() {
295				if r := recover(); r != nil {
296					err = fmt.Errorf("objc: failed to create IMP: %s", r)
297				}
298			}()
299			return NewIMP(def.Fn), nil
300		}()
301		if err != nil {
302			return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err)
303		}
304		encoding, err := encodeFunc(def.Fn)
305		if err != nil {
306			return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err)
307		}
308		if !class.AddMethod(def.Cmd, imp, encoding) {
309			return 0, fmt.Errorf("objc: couldn't add Method at index %d", idx)
310		}
311	}
312	// Add Ivars
313	for _, instVar := range ivars {
314		ivar := instVar
315		if !ivarRegex.MatchString(ivar.Name) {
316			return 0, fmt.Errorf("objc: Ivar must start with a lowercase letter and only contain ASCII letters and numbers: '%s'", ivar.Name)
317		}
318		size := ivar.Type.Size()
319		alignment := uint8(math.Log2(float64(ivar.Type.Align())))
320		enc, err := encodeType(ivar.Type, false)
321		if err != nil {
322			return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", ivar.Name, err)
323		}
324		if !class_addIvar(class, ivar.Name, size, alignment, enc) {
325			return 0, fmt.Errorf("objc: couldn't add Ivar %s", ivar.Name)
326		}
327		offset := class.InstanceVariable(ivar.Name).Offset()
328		switch ivar.Attribute {
329		case ReadWrite:
330			ty := reflect.FuncOf(
331				[]reflect.Type{
332					reflect.TypeFor[ID](), reflect.TypeFor[SEL](), ivar.Type,
333				},
334				nil, false,
335			)
336			var encoding string
337			if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil {
338				return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err)
339			}
340			val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
341				// on entry the first and second arguments are ID and SEL followed by the value
342				if len(args) != 3 {
343					panic(fmt.Sprintf("objc: incorrect number of args. expected 3 got %d", len(args)))
344				}
345				// The following reflect code does the equivalent of this:
346				//
347				//	((*struct {
348				//		Padding [offset]byte
349				//		Value int
350				//	})(unsafe.Pointer(args[0].Interface().(ID)))).v = 123
351				//
352				// However, since the type of the variable is unknown reflection is used to actually assign the value
353				id, ok := reflect.TypeAssert[ID](args[0])
354				if !ok {
355					panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String()))
356				}
357				ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet
358				reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2])
359				return nil
360			}).Interface()
361			// this code only works for ascii but that shouldn't be a problem
362			selector := "set" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + ":\x00"
363			class.AddMethod(RegisterName(selector), NewIMP(val), encoding)
364			fallthrough // also implement the read method
365		case ReadOnly:
366			ty := reflect.FuncOf(
367				[]reflect.Type{
368					reflect.TypeFor[ID](), reflect.TypeFor[SEL](),
369				},
370				[]reflect.Type{ivar.Type}, false,
371			)
372			var encoding string
373			if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil {
374				return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err)
375			}
376			val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
377				// on entry the first and second arguments are ID and SEL
378				if len(args) != 2 {
379					panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args)))
380				}
381				id, ok := reflect.TypeAssert[ID](args[0])
382				if !ok {
383					panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String()))
384				}
385				ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet
386				// the variable is located at an offset from the id
387				return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()}
388			}).Interface()
389			if ivar.Type.Kind() == reflect.Bool {
390				// this code only works for ascii but that shouldn't be a problem
391				ivar.Name = "is" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:]
392			}
393			class.AddMethod(RegisterName(ivar.Name), NewIMP(val), encoding)
394		default:
395			return 0, fmt.Errorf("objc: unknown Ivar Attribute (%d)", ivar.Attribute)
396		}
397	}
398	objc_registerClassPair(class)
399	return class, nil
400}
401
402const (
403	encId          = "@"
404	encClass       = "#"
405	encSelector    = ":"
406	encChar        = "c"
407	encUChar       = "C"
408	encShort       = "s"
409	encUShort      = "S"
410	encInt         = "i"
411	encUInt        = "I"
412	encLongLong    = "q"
413	encULongLong   = "Q"
414	encFloat       = "f"
415	encDouble      = "d"
416	encBool        = "B"
417	encVoid        = "v"
418	encPtr         = "^"
419	encCharPtr     = "*"
420	encStructBegin = "{"
421	encStructEnd   = "}"
422	encUnsafePtr   = "^v"
423)
424
425// encodeType returns a string representing a type as if it was given to @encode(typ)
426// Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
427func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
428	switch typ {
429	case reflect.TypeFor[Class]():
430		return encClass, nil
431	case reflect.TypeFor[ID](), reflect.TypeFor[Block]():
432		return encId, nil
433	case reflect.TypeFor[SEL]():
434		return encSelector, nil
435	}
436
437	kind := typ.Kind()
438	switch kind {
439	case reflect.Bool:
440		return encBool, nil
441	case reflect.Int:
442		return encLongLong, nil
443	case reflect.Int8:
444		return encChar, nil
445	case reflect.Int16:
446		return encShort, nil
447	case reflect.Int32:
448		return encInt, nil
449	case reflect.Int64:
450		return encLongLong, nil
451	case reflect.Uint:
452		return encULongLong, nil
453	case reflect.Uint8:
454		return encUChar, nil
455	case reflect.Uint16:
456		return encUShort, nil
457	case reflect.Uint32:
458		return encUInt, nil
459	case reflect.Uint64:
460		return encULongLong, nil
461	case reflect.Uintptr:
462		return encPtr, nil
463	case reflect.Float32:
464		return encFloat, nil
465	case reflect.Float64:
466		return encDouble, nil
467	case reflect.Pointer:
468		enc, err := encodeType(typ.Elem(), true)
469		return encPtr + enc, err
470	case reflect.Struct:
471		if insidePtr {
472			return encStructBegin + typ.Name() + encStructEnd, nil
473		}
474		var encoding stdstrings.Builder
475		encoding.WriteString(encStructBegin)
476		encoding.WriteString(typ.Name())
477		encoding.WriteString("=")
478		for i := range typ.NumField() {
479			f := typ.Field(i)
480			if f.Type.ConvertibleTo(hostLayoutType) {
481				// The structs.HostLayout marker is a signal to the Go compiler
482				// with no counterpart in C, so it is not a member as far as
483				// @encode is concerned. Other zero-sized fields are: clang
484				// encodes a zero-length array member as [0c].
485				continue
486			}
487			tmp, err := encodeType(f.Type, false)
488			if err != nil {
489				return "", err
490			}
491			encoding.WriteString(tmp)
492		}
493		encoding.WriteString(encStructEnd)
494		return encoding.String(), nil
495	case reflect.UnsafePointer:
496		return encUnsafePtr, nil
497	case reflect.String:
498		return encCharPtr, nil
499	}
500
501	return "", errors.New(fmt.Sprintf("unhandled/invalid kind %v typed %v", kind, typ))
502}
503
504// encodeFunc returns a functions type as if it was given to @encode(fn)
505func encodeFunc(fn any) (string, error) {
506	typ := reflect.TypeOf(fn)
507	if typ.Kind() != reflect.Func {
508		return "", errors.New("not a func")
509	}
510
511	var encoding stdstrings.Builder
512	switch typ.NumOut() {
513	case 0:
514		encoding.WriteString(encVoid)
515	case 1:
516		tmp, err := encodeType(typ.Out(0), false)
517		if err != nil {
518			return "", err
519		}
520		encoding.WriteString(tmp)
521	default:
522		return "", errors.New("too many output parameters")
523	}
524
525	if typ.NumIn() < 2 {
526		return "", errors.New("func doesn't take ID and SEL as its first two parameters")
527	}
528
529	encoding.WriteString(encId)
530
531	for i := 1; i < typ.NumIn(); i++ {
532		tmp, err := encodeType(typ.In(i), false)
533		if err != nil {
534			return "", err
535		}
536		encoding.WriteString(tmp)
537	}
538	return encoding.String(), nil
539}
540
541// SuperClass returns the superclass of a class.
542// You should usually use NSObject‘s superclass method instead of this function.
543func (c Class) SuperClass() Class {
544	return class_getSuperclass(c)
545}
546
547// AddMethod adds a new method to a class with a given name and implementation.
548// The types argument is a string containing the mapping of parameters and return type.
549// Since the function must take at least two arguments—self and _cmd, the second and third
550// characters must be “@:” (the first character is the return type).
551func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
552	return class_addMethod(c, name, imp, types)
553}
554
555// AddProtocol adds a protocol to a class.
556// Returns true if the protocol was added successfully, otherwise false (for example,
557// the class already conforms to that protocol).
558func (c Class) AddProtocol(protocol *Protocol) bool {
559	return class_addProtocol(c, protocol)
560}
561
562// InstanceSize returns the size in bytes of instances of the class or 0 if cls is nil
563func (c Class) InstanceSize() uintptr {
564	return class_getInstanceSize(c)
565}
566
567// InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
568func (c Class) InstanceVariable(name string) Ivar {
569	return class_getInstanceVariable(c, name)
570}
571
572// Ivar an opaque type that represents an instance variable.
573type Ivar uintptr
574
575// Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
576//
577// For instance variables of type ID or other object types, call Ivar and SetIvar instead
578// of using this offset to access the instance variable data directly.
579func (i Ivar) Offset() uintptr {
580	return ivar_getOffset(i)
581}
582
583func (i Ivar) Name() string {
584	return ivar_getName(i)
585}
586
587// MethodDescription holds the name and type definition of a method.
588// It matches the Objective-C runtime's struct objc_method_description.
589type MethodDescription struct {
590	_           structs.HostLayout
591	name, types uintptr
592}
593
594// Name returns the name of this method.
595func (m MethodDescription) Name() string {
596	return strings.GoString(m.name)
597}
598
599// Types returns the OBJC runtime encoded type description.
600func (m MethodDescription) Types() string {
601	return strings.GoString(m.types)
602}
603
604// PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description.
605// It matches the Objective-C runtime's objc_property_attribute_t.
606type PropertyAttribute struct {
607	_           structs.HostLayout
608	Name, Value *byte
609}
610
611// Property is an opaque type for Objective-C property metadata.
612type Property uintptr
613
614// Name returns the name of this property.
615func (p Property) Name() string {
616	return property_getName(p)
617}
618
619// Attributes returns a comma separated list of PropertyAttribute
620func (p Property) Attributes() string {
621	return property_getAttributes(p)
622}
623
624// Protocol is a type that declares methods that can be implemented by any class.
625type Protocol [0]func()
626
627// GetProtocol returns the protocol for the given name or nil if there is no protocol by that name.
628func GetProtocol(name string) *Protocol {
629	return objc_getProtocol(name)
630}
631
632// AllocateProtocol creates a new protocol in the OBJC runtime or nil if the protocol already exists.
633func AllocateProtocol(name string) *Protocol {
634	return objc_allocateProtocol(name)
635}
636
637// Register registers the protocol created using AllocateProtocol with the runtime. This must be done
638// before it is used anywhere and can only be called once.
639func (p *Protocol) Register() {
640	objc_registerProtocol(p)
641}
642
643// CopyMethodDescriptionList returns a list of methods that this protocol has given it isRequiredMethod and isInstanceMethod.
644func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription {
645	count := uint32(0)
646	desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count)
647	methods := slices.Clone(unsafe.Slice(desc, count))
648	free(unsafe.Pointer(desc))
649	return methods
650}
651
652// CopyProtocolList returns a list of the protocols that this protocol inherits from.
653func (p *Protocol) CopyProtocolList() []*Protocol {
654	count := uint32(0)
655	desc := protocol_copyProtocolList(p, &count)
656	protocols := slices.Clone(unsafe.Slice(desc, count))
657	free(unsafe.Pointer(desc))
658	return protocols
659}
660
661// CopyPropertyList returns a list of properties that this protocol has given it isRequiredProperty and isInstanceProperty.
662func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property {
663	count := uint32(0)
664	desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty)
665	protocols := slices.Clone(unsafe.Slice(desc, count))
666	free(unsafe.Pointer(desc))
667	return protocols
668}
669
670// Name returns the name of this protocol.
671func (p *Protocol) Name() string {
672	return protocol_getName(p)
673}
674
675// Equals return true if the two protocols are the same.
676func (p *Protocol) Equals(p2 *Protocol) bool {
677	return protocol_isEqual(p, p2)
678}
679
680// AddMethodDescription adds a method to a protocol. This can only be called between AllocateProtocol and Protocol.Register.
681func (p *Protocol) AddMethodDescription(name SEL, types string, isRequiredMethod, isInstanceMethod bool) {
682	protocol_addMethodDescription(p, name, types, isRequiredMethod, isInstanceMethod)
683}
684
685// AddProtocol marks the protocol as inheriting from another. This can only be called between AllocateProtocol and Protocol.Register.
686func (p *Protocol) AddProtocol(protocol *Protocol) {
687	protocol_addProtocol(p, protocol)
688}
689
690// AddProperty adds a property to the protocol. This can only be called between AllocateProtocol and Protocol.Register.
691func (p *Protocol) AddProperty(name string, attributes []PropertyAttribute, isRequiredProperty, isInstanceProperty bool) {
692	protocol_addProperty(p, name, attributes, uint32(len(attributes)), isRequiredProperty, isInstanceProperty)
693}
694
695// IMP is a function pointer that can be called by Objective-C code.
696type IMP uintptr
697
698// NewIMP takes a Go function that takes (ID, SEL) as its first two arguments.
699// It returns an IMP function pointer that can be called by Objective-C code.
700// The function panics if an error occurs.
701// The function pointer is never deallocated.
702func NewIMP(fn any) IMP {
703	ty := reflect.TypeOf(fn)
704	if ty.Kind() != reflect.Func {
705		panic("objc: not a function")
706	}
707	// IMP is stricter than a normal callback
708	// id (*IMP)(id, SEL, ...)
709	switch {
710	case ty.NumIn() < 2:
711		fallthrough
712	case ty.In(0) != reflect.TypeFor[ID]():
713		fallthrough
714	case ty.In(1) != reflect.TypeFor[SEL]():
715		panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String())
716	}
717	return IMP(purego.NewCallback(fn))
718}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close