v0.9.0-alpha.7 1a51bed

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

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