569 lines
20 KiB
Go
569 lines
20 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
||
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
|
||
|
||
// Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best
|
||
// to use a wrapper that provides the functionality you need in a safer way.
|
||
package objc
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"math"
|
||
"reflect"
|
||
"regexp"
|
||
"unicode"
|
||
"unsafe"
|
||
|
||
"github.com/ebitengine/purego"
|
||
)
|
||
|
||
// TODO: support try/catch?
|
||
// https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
|
||
var (
|
||
objc_msgSend_fn uintptr
|
||
objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
|
||
objc_msgSendSuper2_fn uintptr
|
||
objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
|
||
objc_getClass func(name string) Class
|
||
objc_getProtocol func(name string) *Protocol
|
||
objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
|
||
objc_registerClassPair func(class Class)
|
||
sel_registerName func(name string) SEL
|
||
class_getSuperclass func(class Class) Class
|
||
class_getInstanceVariable func(class Class, name string) Ivar
|
||
class_getInstanceSize func(class Class) uintptr
|
||
class_addMethod func(class Class, name SEL, imp IMP, types string) bool
|
||
class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
|
||
class_addProtocol func(class Class, protocol *Protocol) bool
|
||
ivar_getOffset func(ivar Ivar) uintptr
|
||
ivar_getName func(ivar Ivar) string
|
||
object_getClass func(obj ID) Class
|
||
object_getIvar func(obj ID, ivar Ivar) ID
|
||
object_setIvar func(obj ID, ivar Ivar, value ID)
|
||
protocol_getName func(protocol *Protocol) string
|
||
protocol_isEqual func(p *Protocol, p2 *Protocol) bool
|
||
)
|
||
|
||
func init() {
|
||
objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
|
||
if err != nil {
|
||
panic(fmt.Errorf("objc: %w", err))
|
||
}
|
||
objc_msgSend_fn, err = purego.Dlsym(objc, "objc_msgSend")
|
||
if err != nil {
|
||
panic(fmt.Errorf("objc: %w", err))
|
||
}
|
||
purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
|
||
objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")
|
||
if err != nil {
|
||
panic(fmt.Errorf("objc: %w", err))
|
||
}
|
||
purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn)
|
||
purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
|
||
purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
|
||
purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
|
||
purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair")
|
||
purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair")
|
||
purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName")
|
||
purego.RegisterLibFunc(&class_getSuperclass, objc, "class_getSuperclass")
|
||
purego.RegisterLibFunc(&class_getInstanceVariable, objc, "class_getInstanceVariable")
|
||
purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod")
|
||
purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar")
|
||
purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol")
|
||
purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize")
|
||
purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset")
|
||
purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName")
|
||
purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName")
|
||
purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual")
|
||
purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar")
|
||
purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar")
|
||
}
|
||
|
||
// ID is an opaque pointer to some Objective-C object
|
||
type ID uintptr
|
||
|
||
// Class returns the class of the object.
|
||
func (id ID) Class() Class {
|
||
return object_getClass(id)
|
||
}
|
||
|
||
// Send is a convenience method for sending messages to objects. This function takes a SEL
|
||
// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
|
||
// of RegisterName.
|
||
func (id ID) Send(sel SEL, args ...interface{}) ID {
|
||
return objc_msgSend(id, sel, args...)
|
||
}
|
||
|
||
// GetIvar reads the value of an instance variable in an object.
|
||
func (id ID) GetIvar(ivar Ivar) ID {
|
||
return object_getIvar(id, ivar)
|
||
}
|
||
|
||
// SetIvar sets the value of an instance variable in an object.
|
||
func (id ID) SetIvar(ivar Ivar, value ID) {
|
||
object_setIvar(id, ivar, value)
|
||
}
|
||
|
||
// Send is a convenience method for sending messages to objects that can return any type.
|
||
// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
|
||
// It is best to cache the result of RegisterName.
|
||
func Send[T any](id ID, sel SEL, args ...any) T {
|
||
var fn func(id ID, sel SEL, args ...any) T
|
||
purego.RegisterFunc(&fn, objc_msgSend_fn)
|
||
return fn(id, sel, args...)
|
||
}
|
||
|
||
// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
|
||
// as the receiver of a message. It specifies the class definition of the particular superclass that should
|
||
// be messaged.
|
||
type objc_super struct {
|
||
receiver ID
|
||
superClass Class
|
||
}
|
||
|
||
// SendSuper is a convenience method for sending message to object's super. This function takes a SEL
|
||
// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
|
||
// of RegisterName.
|
||
func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
|
||
super := &objc_super{
|
||
receiver: id,
|
||
superClass: id.Class(),
|
||
}
|
||
return objc_msgSendSuper2(super, sel, args...)
|
||
}
|
||
|
||
// SendSuper is a convenience method for sending message to object's super that can return any type.
|
||
// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
|
||
// It is best to cache the result of RegisterName.
|
||
func SendSuper[T any](id ID, sel SEL, args ...any) T {
|
||
super := &objc_super{
|
||
receiver: id,
|
||
superClass: id.Class(),
|
||
}
|
||
var fn func(objcSuper *objc_super, sel SEL, args ...any) T
|
||
purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
|
||
return fn(super, sel, args...)
|
||
}
|
||
|
||
// SEL is an opaque type that represents a method selector
|
||
type SEL uintptr
|
||
|
||
// RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
|
||
// and returns the selector value. This function grabs the global Objective-c lock. It is best the cache the
|
||
// result of this function.
|
||
func RegisterName(name string) SEL {
|
||
return sel_registerName(name)
|
||
}
|
||
|
||
// Class is an opaque type that represents an Objective-C class.
|
||
type Class uintptr
|
||
|
||
// GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
|
||
func GetClass(name string) Class {
|
||
return objc_getClass(name)
|
||
}
|
||
|
||
// AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
|
||
//
|
||
// Deprecated: use RegisterClass instead
|
||
func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
|
||
return objc_allocateClassPair(super, name, extraBytes)
|
||
}
|
||
|
||
// MethodDef represents the Go function and the selector that ObjC uses to access that function.
|
||
type MethodDef struct {
|
||
Cmd SEL
|
||
Fn any
|
||
}
|
||
|
||
// IvarAttrib is the attribute that an ivar has. It affects if and which methods are automatically
|
||
// generated when creating a class with RegisterClass. See [Apple Docs] for an understanding of these attributes.
|
||
// The fields are still accessible using objc.GetIvar and objc.SetIvar regardless of the value of IvarAttrib.
|
||
//
|
||
// Take for example this Objective-C code:
|
||
//
|
||
// @property (readwrite) float value;
|
||
//
|
||
// In Go, the functions can be accessed as followed:
|
||
//
|
||
// var value = purego.Send[float32](id, purego.RegisterName("value"))
|
||
// id.Send(purego.RegisterName("setValue:"), 3.46)
|
||
//
|
||
// [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
|
||
type IvarAttrib int
|
||
|
||
const (
|
||
ReadOnly IvarAttrib = 1 << iota
|
||
ReadWrite
|
||
)
|
||
|
||
// FieldDef is a definition of a field to add to an Objective-C class.
|
||
// The name of the field is what will be used to access it through the Ivar. If the type is bool
|
||
// the name cannot start with `is` since a getter will be generated with the name `isBoolName`.
|
||
// The name also cannot contain any spaces.
|
||
// The type is the Go equivalent type of the Ivar.
|
||
// Attribute determines if a getter and or setter method is generated for this field.
|
||
type FieldDef struct {
|
||
Name string
|
||
Type reflect.Type
|
||
Attribute IvarAttrib
|
||
}
|
||
|
||
// ivarRegex checks to make sure the Ivar is correctly formatted
|
||
var ivarRegex = regexp.MustCompile("[a-z_][a-zA-Z0-9_]*")
|
||
|
||
// RegisterClass takes the name of the class to create, the superclass, a list of protocols this class
|
||
// implements, a list of fields this class has and a list of methods. It returns the created class or an error
|
||
// describing what went wrong.
|
||
func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars []FieldDef, methods []MethodDef) (Class, error) {
|
||
class := objc_allocateClassPair(superClass, name, 0)
|
||
if class == 0 {
|
||
return 0, fmt.Errorf("objc: failed to create class with name '%s'", name)
|
||
}
|
||
// Add Protocols
|
||
for _, p := range protocols {
|
||
if !class.AddProtocol(p) {
|
||
return 0, fmt.Errorf("objc: couldn't add Protocol %s", protocol_getName(p))
|
||
}
|
||
}
|
||
// Add exported methods based on the selectors returned from ClassDef(string) SEL
|
||
for idx, def := range methods {
|
||
imp, err := func() (imp IMP, err error) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
err = fmt.Errorf("objc: failed to create IMP: %s", r)
|
||
}
|
||
}()
|
||
return NewIMP(def.Fn), nil
|
||
}()
|
||
if err != nil {
|
||
return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err)
|
||
}
|
||
encoding, err := encodeFunc(def.Fn)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err)
|
||
}
|
||
if !class.AddMethod(def.Cmd, imp, encoding) {
|
||
return 0, fmt.Errorf("objc: couldn't add Method at index %d", idx)
|
||
}
|
||
}
|
||
// Add Ivars
|
||
for _, instVar := range ivars {
|
||
ivar := instVar
|
||
if !ivarRegex.MatchString(ivar.Name) {
|
||
return 0, fmt.Errorf("objc: Ivar must start with a lowercase letter and only contain ASCII letters and numbers: '%s'", ivar.Name)
|
||
}
|
||
size := ivar.Type.Size()
|
||
alignment := uint8(math.Log2(float64(ivar.Type.Align())))
|
||
enc, err := encodeType(ivar.Type, false)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", ivar.Name, err)
|
||
}
|
||
if !class_addIvar(class, ivar.Name, size, alignment, enc) {
|
||
return 0, fmt.Errorf("objc: couldn't add Ivar %s", ivar.Name)
|
||
}
|
||
offset := class.InstanceVariable(ivar.Name).Offset()
|
||
switch ivar.Attribute {
|
||
case ReadWrite:
|
||
ty := reflect.FuncOf(
|
||
[]reflect.Type{
|
||
reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type,
|
||
},
|
||
nil, false,
|
||
)
|
||
var encoding string
|
||
if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil {
|
||
return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err)
|
||
}
|
||
val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
|
||
// on entry the first and second arguments are ID and SEL followed by the value
|
||
if len(args) != 3 {
|
||
panic(fmt.Sprintf("objc: incorrect number of args. expected 3 got %d", len(args)))
|
||
}
|
||
// The following reflect code does the equivalent of this:
|
||
//
|
||
// ((*struct {
|
||
// Padding [offset]byte
|
||
// Value int
|
||
// })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123
|
||
//
|
||
// However, since the type of the variable is unknown reflection is used to actually assign the value
|
||
id := args[0].Interface().(ID)
|
||
ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet
|
||
reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2])
|
||
return nil
|
||
}).Interface()
|
||
// this code only works for ascii but that shouldn't be a problem
|
||
selector := "set" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + ":\x00"
|
||
class.AddMethod(RegisterName(selector), NewIMP(val), encoding)
|
||
fallthrough // also implement the read method
|
||
case ReadOnly:
|
||
ty := reflect.FuncOf(
|
||
[]reflect.Type{
|
||
reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)),
|
||
},
|
||
[]reflect.Type{ivar.Type}, false,
|
||
)
|
||
var encoding string
|
||
if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil {
|
||
return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err)
|
||
}
|
||
val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
|
||
// on entry the first and second arguments are ID and SEL
|
||
if len(args) != 2 {
|
||
panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args)))
|
||
}
|
||
id := args[0].Interface().(ID)
|
||
ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet
|
||
// the variable is located at an offset from the id
|
||
return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()}
|
||
}).Interface()
|
||
if ivar.Type.Kind() == reflect.Bool {
|
||
// this code only works for ascii but that shouldn't be a problem
|
||
ivar.Name = "is" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:]
|
||
}
|
||
class.AddMethod(RegisterName(ivar.Name), NewIMP(val), encoding)
|
||
default:
|
||
return 0, fmt.Errorf("objc: unknown Ivar Attribute (%d)", ivar.Attribute)
|
||
}
|
||
}
|
||
objc_registerClassPair(class)
|
||
return class, nil
|
||
}
|
||
|
||
const (
|
||
encId = "@"
|
||
encClass = "#"
|
||
encSelector = ":"
|
||
encChar = "c"
|
||
encUChar = "C"
|
||
encShort = "s"
|
||
encUShort = "S"
|
||
encInt = "i"
|
||
encUInt = "I"
|
||
encLong = "l"
|
||
encULong = "L"
|
||
encFloat = "f"
|
||
encDouble = "d"
|
||
encBool = "B"
|
||
encVoid = "v"
|
||
encPtr = "^"
|
||
encCharPtr = "*"
|
||
encStructBegin = "{"
|
||
encStructEnd = "}"
|
||
encUnsafePtr = "^v"
|
||
)
|
||
|
||
// encodeType returns a string representing a type as if it was given to @encode(typ)
|
||
// Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
|
||
func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
|
||
switch typ {
|
||
case reflect.TypeOf(Class(0)):
|
||
return encClass, nil
|
||
case reflect.TypeOf(ID(0)):
|
||
return encId, nil
|
||
case reflect.TypeOf(SEL(0)):
|
||
return encSelector, nil
|
||
}
|
||
|
||
kind := typ.Kind()
|
||
switch kind {
|
||
case reflect.Bool:
|
||
return encBool, nil
|
||
case reflect.Int:
|
||
return encLong, nil
|
||
case reflect.Int8:
|
||
return encChar, nil
|
||
case reflect.Int16:
|
||
return encShort, nil
|
||
case reflect.Int32:
|
||
return encInt, nil
|
||
case reflect.Int64:
|
||
return encULong, nil
|
||
case reflect.Uint:
|
||
return encULong, nil
|
||
case reflect.Uint8:
|
||
return encUChar, nil
|
||
case reflect.Uint16:
|
||
return encUShort, nil
|
||
case reflect.Uint32:
|
||
return encUInt, nil
|
||
case reflect.Uint64:
|
||
return encULong, nil
|
||
case reflect.Uintptr:
|
||
return encPtr, nil
|
||
case reflect.Float32:
|
||
return encFloat, nil
|
||
case reflect.Float64:
|
||
return encDouble, nil
|
||
case reflect.Ptr:
|
||
enc, err := encodeType(typ.Elem(), true)
|
||
return encPtr + enc, err
|
||
case reflect.Struct:
|
||
if insidePtr {
|
||
return encStructBegin + typ.Name() + encStructEnd, nil
|
||
}
|
||
encoding := encStructBegin
|
||
encoding += typ.Name()
|
||
encoding += "="
|
||
for i := 0; i < typ.NumField(); i++ {
|
||
f := typ.Field(i)
|
||
tmp, err := encodeType(f.Type, false)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
encoding += tmp
|
||
}
|
||
encoding = encStructEnd
|
||
return encoding, nil
|
||
case reflect.UnsafePointer:
|
||
return encUnsafePtr, nil
|
||
case reflect.String:
|
||
return encCharPtr, nil
|
||
}
|
||
|
||
return "", errors.New(fmt.Sprintf("unhandled/invalid kind %v typed %v", kind, typ))
|
||
}
|
||
|
||
// encodeFunc returns a functions type as if it was given to @encode(fn)
|
||
func encodeFunc(fn interface{}) (string, error) {
|
||
typ := reflect.TypeOf(fn)
|
||
if typ.Kind() != reflect.Func {
|
||
return "", errors.New("not a func")
|
||
}
|
||
|
||
encoding := ""
|
||
switch typ.NumOut() {
|
||
case 0:
|
||
encoding += encVoid
|
||
case 1:
|
||
tmp, err := encodeType(typ.Out(0), false)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
encoding += tmp
|
||
default:
|
||
return "", errors.New("too many output parameters")
|
||
}
|
||
|
||
if typ.NumIn() < 2 {
|
||
return "", errors.New("func doesn't take ID and SEL as its first two parameters")
|
||
}
|
||
|
||
encoding += encId
|
||
|
||
for i := 1; i < typ.NumIn(); i++ {
|
||
tmp, err := encodeType(typ.In(i), false)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
encoding += tmp
|
||
}
|
||
return encoding, nil
|
||
}
|
||
|
||
// SuperClass returns the superclass of a class.
|
||
// You should usually use NSObject‘s superclass method instead of this function.
|
||
func (c Class) SuperClass() Class {
|
||
return class_getSuperclass(c)
|
||
}
|
||
|
||
// AddMethod adds a new method to a class with a given name and implementation.
|
||
// The types argument is a string containing the mapping of parameters and return type.
|
||
// Since the function must take at least two arguments—self and _cmd, the second and third
|
||
// characters must be “@:” (the first character is the return type).
|
||
func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
|
||
return class_addMethod(c, name, imp, types)
|
||
}
|
||
|
||
// AddIvar adds a new instance variable to a class.
|
||
// It may only be called after AllocateClassPair and before Register.
|
||
// Adding an instance variable to an existing class is not supported.
|
||
// The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
|
||
// It takes the instance of the type of the Ivar and a string representing the type.
|
||
//
|
||
// Deprecated: use RegisterClass instead
|
||
func (c Class) AddIvar(name string, ty interface{}, types string) bool {
|
||
typeOf := reflect.TypeOf(ty)
|
||
size := typeOf.Size()
|
||
alignment := uint8(math.Log2(float64(typeOf.Align())))
|
||
return class_addIvar(c, name, size, alignment, types)
|
||
}
|
||
|
||
// AddProtocol adds a protocol to a class.
|
||
// Returns true if the protocol was added successfully, otherwise false (for example,
|
||
// the class already conforms to that protocol).
|
||
func (c Class) AddProtocol(protocol *Protocol) bool {
|
||
return class_addProtocol(c, protocol)
|
||
}
|
||
|
||
// InstanceSize returns the size in bytes of instances of the class or 0 if cls is nil
|
||
func (c Class) InstanceSize() uintptr {
|
||
return class_getInstanceSize(c)
|
||
}
|
||
|
||
// InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
|
||
func (c Class) InstanceVariable(name string) Ivar {
|
||
return class_getInstanceVariable(c, name)
|
||
}
|
||
|
||
// Register registers a class that was allocated using AllocateClassPair.
|
||
// It can now be used to make objects by sending it either alloc and init or new.
|
||
//
|
||
// Deprecated: use RegisterClass instead
|
||
func (c Class) Register() {
|
||
objc_registerClassPair(c)
|
||
}
|
||
|
||
// Ivar an opaque type that represents an instance variable.
|
||
type Ivar uintptr
|
||
|
||
// Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
|
||
//
|
||
// For instance variables of type ID or other object types, call Ivar and SetIvar instead
|
||
// of using this offset to access the instance variable data directly.
|
||
func (i Ivar) Offset() uintptr {
|
||
return ivar_getOffset(i)
|
||
}
|
||
|
||
func (i Ivar) Name() string {
|
||
return ivar_getName(i)
|
||
}
|
||
|
||
// Protocol is a type that declares methods that can be implemented by any class.
|
||
type Protocol [0]func()
|
||
|
||
// GetProtocol returns the protocol for the given name or nil if there is no protocol by that name.
|
||
func GetProtocol(name string) *Protocol {
|
||
return objc_getProtocol(name)
|
||
}
|
||
|
||
// Equals return true if the two protocols are the same.
|
||
func (p *Protocol) Equals(p2 *Protocol) bool {
|
||
return protocol_isEqual(p, p2)
|
||
}
|
||
|
||
// IMP is a function pointer that can be called by Objective-C code.
|
||
type IMP uintptr
|
||
|
||
// NewIMP takes a Go function that takes (ID, SEL) as its first two arguments.
|
||
// It returns an IMP function pointer that can be called by Objective-C code.
|
||
// The function panics if an error occurs.
|
||
// The function pointer is never deallocated.
|
||
func NewIMP(fn interface{}) IMP {
|
||
ty := reflect.TypeOf(fn)
|
||
if ty.Kind() != reflect.Func {
|
||
panic("objc: not a function")
|
||
}
|
||
// IMP is stricter than a normal callback
|
||
// id (*IMP)(id, SEL, ...)
|
||
switch {
|
||
case ty.NumIn() < 2:
|
||
fallthrough
|
||
case ty.In(0) != reflect.TypeOf(ID(0)):
|
||
fallthrough
|
||
case ty.In(1) != reflect.TypeOf(SEL(0)):
|
||
panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String())
|
||
}
|
||
return IMP(purego.NewCallback(fn))
|
||
}
|