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