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