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