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