Revert "Implement safe ObjC classes (#157)"
This reverts commit 469fcb91e836003463c0002935c4a383441f3283. Reason: TotallyGamerJet found an issue (calling the runtime from a different thread?)
- commit
- 0f25ec68926151c8611e07b052cf27417d9a3313
- author
- Hajime Hoshi · · 2023-09-07 15:11 UTC
- parent
- 469fcb9
- signature
- unsigned
- refs
- tag: v0.5.0-alpha.2
3 files · +231 -241
M examples/objc/main_darwin.go +32 -31
| @@ -5,7 +5,7 @@ package main | |||
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | 7 | "fmt" | |
| 8 | - | "reflect" | |
| 8 | + | "unsafe" | |
| 9 | 9 | ||
| 10 | 10 | "github.com/ebitengine/purego/objc" | |
| 11 | 11 | ) | |
| @@ -17,44 +17,45 @@ var ( | |||
| 17 | 17 | sel_bar = objc.RegisterName("bar") | |
| 18 | 18 | ) | |
| 19 | 19 | ||
| 20 | - | func BarInit(id objc.ID, cmd objc.SEL) objc.ID { | |
| 21 | - | return id.SendSuper(cmd) | |
| 20 | + | type barObject struct { | |
| 21 | + | isa objc.Class `objc:"BarObject : NSObject <NSDelegateWindow>"` | |
| 22 | + | bar int | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func (b *barObject) Init(_cmd objc.SEL) objc.ID { | |
| 26 | + | return objc.ID(unsafe.Pointer(b)).SendSuper(_cmd) | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | func (b *barObject) Bar(_cmd objc.SEL) int { | |
| 30 | + | return b.bar | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | func (b *barObject) SetBar(_cmd objc.SEL, bar int) { | |
| 34 | + | b.bar = bar | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | func (_ *barObject) Selector(metName string) objc.SEL { | |
| 38 | + | switch metName { | |
| 39 | + | case "Init": | |
| 40 | + | return sel_init | |
| 41 | + | case "SetBar": | |
| 42 | + | return sel_setBar | |
| 43 | + | case "Bar": | |
| 44 | + | return sel_bar | |
| 45 | + | default: | |
| 46 | + | return 0 | |
| 47 | + | } | |
| 22 | 48 | } | |
| 23 | 49 | ||
| 24 | 50 | func main() { | |
| 25 | - | // This struct is equivalent to the following Objective-C definition. | |
| 26 | - | // | |
| 27 | - | // @interface BarObject : NSObject <NSDelegateWindow> | |
| 28 | - | // @property (readwrite) bar int | |
| 29 | - | // @end | |
| 30 | - | // | |
| 31 | - | class, err := objc.RegisterClass( | |
| 32 | - | "BarObject", | |
| 33 | - | objc.GetClass("NSObject"), | |
| 34 | - | []*objc.Protocol{ | |
| 35 | - | objc.GetProtocol("NSDelegateWindow"), | |
| 36 | - | }, | |
| 37 | - | []objc.FieldDef{ | |
| 38 | - | { | |
| 39 | - | Name: "bar", | |
| 40 | - | Type: reflect.TypeOf(int(0)), | |
| 41 | - | Attribute: objc.ReadWrite, | |
| 42 | - | }, | |
| 43 | - | }, | |
| 44 | - | []objc.MethodDef{ | |
| 45 | - | { | |
| 46 | - | Cmd: sel_init, | |
| 47 | - | Fn: BarInit, | |
| 48 | - | }, | |
| 49 | - | }, | |
| 50 | - | ) | |
| 51 | + | class, err := objc.RegisterClass(&barObject{}) | |
| 51 | 52 | if err != nil { | |
| 52 | 53 | panic(err) | |
| 53 | 54 | } | |
| 54 | 55 | ||
| 55 | - | object := objc.ID(class).Send(sel_new) | |
| 56 | + | var object = objc.ID(class).Send(sel_new) | |
| 56 | 57 | object.Send(sel_setBar, 123) | |
| 57 | - | bar := int(object.Send(sel_bar)) | |
| 58 | + | var bar = int(object.Send(sel_bar)) | |
| 58 | 59 | fmt.Println(bar) | |
| 59 | 60 | // Output: 123 | |
| 60 | 61 | } | |
| @@ -5,7 +5,7 @@ package main | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | - | "reflect" | 8 | + | "unsafe" |
| 9 | 9 | ||||
| 10 | "github.com/ebitengine/purego/objc" | 10 | "github.com/ebitengine/purego/objc" | ||
| 11 | ) | 11 | ) | ||
| @@ -17,44 +17,45 @@ var ( | |||||
| 17 | sel_bar = objc.RegisterName("bar") | 17 | sel_bar = objc.RegisterName("bar") | ||
| 18 | ) | 18 | ) | ||
| 19 | 19 | ||||
| 20 | - | func BarInit(id objc.ID, cmd objc.SEL) objc.ID { | 20 | + | type barObject struct { |
| 21 | - | return id.SendSuper(cmd) | 21 | + | isa objc.Class `objc:"BarObject : NSObject <NSDelegateWindow>"` |
| 22 | + | bar int | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func (b *barObject) Init(_cmd objc.SEL) objc.ID { | |||
| 26 | + | return objc.ID(unsafe.Pointer(b)).SendSuper(_cmd) | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | func (b *barObject) Bar(_cmd objc.SEL) int { | |||
| 30 | + | return b.bar | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | func (b *barObject) SetBar(_cmd objc.SEL, bar int) { | |||
| 34 | + | b.bar = bar | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | func (_ *barObject) Selector(metName string) objc.SEL { | |||
| 38 | + | switch metName { | |||
| 39 | + | case "Init": | |||
| 40 | + | return sel_init | |||
| 41 | + | case "SetBar": | |||
| 42 | + | return sel_setBar | |||
| 43 | + | case "Bar": | |||
| 44 | + | return sel_bar | |||
| 45 | + | default: | |||
| 46 | + | return 0 | |||
| 47 | + | } | |||
| 22 | } | 48 | } | ||
| 23 | 49 | ||||
| 24 | func main() { | 50 | func main() { | ||
| 25 | - | // This struct is equivalent to the following Objective-C definition. | 51 | + | class, err := objc.RegisterClass(&barObject{}) |
| 26 | - | // | |||
| 27 | - | // @interface BarObject : NSObject <NSDelegateWindow> | |||
| 28 | - | // @property (readwrite) bar int | |||
| 29 | - | // @end | |||
| 30 | - | // | |||
| 31 | - | class, err := objc.RegisterClass( | |||
| 32 | - | "BarObject", | |||
| 33 | - | objc.GetClass("NSObject"), | |||
| 34 | - | []*objc.Protocol{ | |||
| 35 | - | objc.GetProtocol("NSDelegateWindow"), | |||
| 36 | - | }, | |||
| 37 | - | []objc.FieldDef{ | |||
| 38 | - | { | |||
| 39 | - | Name: "bar", | |||
| 40 | - | Type: reflect.TypeOf(int(0)), | |||
| 41 | - | Attribute: objc.ReadWrite, | |||
| 42 | - | }, | |||
| 43 | - | }, | |||
| 44 | - | []objc.MethodDef{ | |||
| 45 | - | { | |||
| 46 | - | Cmd: sel_init, | |||
| 47 | - | Fn: BarInit, | |||
| 48 | - | }, | |||
| 49 | - | }, | |||
| 50 | - | ) | |||
| 51 | if err != nil { | 52 | if err != nil { | ||
| 52 | panic(err) | 53 | panic(err) | ||
| 53 | } | 54 | } | ||
| 54 | 55 | ||||
| 55 | - | object := objc.ID(class).Send(sel_new) | 56 | + | var object = objc.ID(class).Send(sel_new) |
| 56 | object.Send(sel_setBar, 123) | 57 | object.Send(sel_setBar, 123) | ||
| 57 | - | bar := int(object.Send(sel_bar)) | 58 | + | var bar = int(object.Send(sel_bar)) |
| 58 | fmt.Println(bar) | 59 | fmt.Println(bar) | ||
| 59 | // Output: 123 | 60 | // Output: 123 | ||
| 60 | } | 61 | } | ||
M objc/objc_runtime_darwin.go +180 -169
| @@ -10,15 +10,13 @@ import ( | |||
| 10 | 10 | "fmt" | |
| 11 | 11 | "math" | |
| 12 | 12 | "reflect" | |
| 13 | - | "regexp" | |
| 14 | - | "unicode" | |
| 15 | - | "unsafe" | |
| 16 | 13 | ||
| 17 | 14 | "github.com/ebitengine/purego" | |
| 18 | 15 | ) | |
| 19 | 16 | ||
| 20 | - | // TODO: support try/catch? | |
| 21 | - | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | |
| 17 | + | //TODO: support try/catch? | |
| 18 | + | //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | |
| 19 | + | ||
| 22 | 20 | var ( | |
| 23 | 21 | objc_msgSend_fn uintptr | |
| 24 | 22 | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | |
| @@ -37,10 +35,6 @@ var ( | |||
| 37 | 35 | class_addProtocol func(class Class, protocol *Protocol) bool | |
| 38 | 36 | ivar_getOffset func(ivar Ivar) uintptr | |
| 39 | 37 | object_getClass func(obj ID) Class | |
| 40 | - | object_getIvar func(obj ID, ivar Ivar) ID | |
| 41 | - | object_setIvar func(obj ID, ivar Ivar, value ID) | |
| 42 | - | protocol_getName func(protocol *Protocol) string | |
| 43 | - | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | |
| 44 | 38 | ) | |
| 45 | 39 | ||
| 46 | 40 | func init() { | |
| @@ -71,10 +65,6 @@ func init() { | |||
| 71 | 65 | purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") | |
| 72 | 66 | purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") | |
| 73 | 67 | purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") | |
| 74 | - | purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") | |
| 75 | - | purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") | |
| 76 | - | purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") | |
| 77 | - | purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") | |
| 78 | 68 | } | |
| 79 | 69 | ||
| 80 | 70 | // ID is an opaque pointer to some Objective-C object | |
| @@ -92,16 +82,6 @@ func (id ID) Send(sel SEL, args ...interface{}) ID { | |||
| 92 | 82 | return objc_msgSend(id, sel, args...) | |
| 93 | 83 | } | |
| 94 | 84 | ||
| 95 | - | // GetIvar reads the value of an instance variable in an object. | |
| 96 | - | func (id ID) GetIvar(ivar Ivar) ID { | |
| 97 | - | return object_getIvar(id, ivar) | |
| 98 | - | } | |
| 99 | - | ||
| 100 | - | // SetIvar sets the value of an instance variable in an object. | |
| 101 | - | func (id ID) SetIvar(ivar Ivar, value ID) { | |
| 102 | - | object_setIvar(id, ivar, value) | |
| 103 | - | } | |
| 104 | - | ||
| 105 | 85 | // Send is a convenience method for sending messages to objects that can return any type. | |
| 106 | 86 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |
| 107 | 87 | // It is best to cache the result of RegisterName. | |
| @@ -123,7 +103,7 @@ type objc_super struct { | |||
| 123 | 103 | // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result | |
| 124 | 104 | // of RegisterName. | |
| 125 | 105 | func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | |
| 126 | - | super := &objc_super{ | |
| 106 | + | var super = &objc_super{ | |
| 127 | 107 | receiver: id, | |
| 128 | 108 | superClass: id.Class(), | |
| 129 | 109 | } | |
| @@ -134,7 +114,7 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | |||
| 134 | 114 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |
| 135 | 115 | // It is best to cache the result of RegisterName. | |
| 136 | 116 | func SendSuper[T any](id ID, sel SEL, args ...any) T { | |
| 137 | - | super := &objc_super{ | |
| 117 | + | var super = &objc_super{ | |
| 138 | 118 | receiver: id, | |
| 139 | 119 | superClass: id.Class(), | |
| 140 | 120 | } | |
| @@ -168,171 +148,203 @@ func AllocateClassPair(super Class, name string, extraBytes uintptr) Class { | |||
| 168 | 148 | return objc_allocateClassPair(super, name, extraBytes) | |
| 169 | 149 | } | |
| 170 | 150 | ||
| 171 | - | // MethodDef represents the Go function and the selector that ObjC uses to access that function. | |
| 172 | - | type MethodDef struct { | |
| 173 | - | Cmd SEL | |
| 174 | - | Fn any | |
| 151 | + | // Selector is an interface that takes a Go method name | |
| 152 | + | // and returns the selector equivalent name. | |
| 153 | + | // If it returns a nil SEL then that method | |
| 154 | + | // is not added to the class object. | |
| 155 | + | type Selector interface { | |
| 156 | + | Selector(string) SEL | |
| 175 | 157 | } | |
| 176 | 158 | ||
| 177 | - | // IvarAttrib is the attribute that an ivar has. It affects if and which methods are automatically | |
| 178 | - | // generated when creating a class with RegisterClass. See [Apple Docs] for an understanding of these attributes. | |
| 179 | - | // The fields are still accessible using objc.GetIvar and objc.SetIvar regardless of the value of IvarAttrib. | |
| 180 | - | // | |
| 181 | - | // Take for example this Objective-C code: | |
| 182 | - | // | |
| 183 | - | // @property (readwrite) float value; | |
| 184 | - | // | |
| 185 | - | // In Go, the functions can be accessed as followed: | |
| 186 | - | // | |
| 187 | - | // var value = purego.Send[float32](id, purego.RegisterName("value")) | |
| 188 | - | // id.Send(purego.RegisterName("setValue:"), 3.46) | |
| 189 | - | // | |
| 190 | - | // [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html | |
| 191 | - | type IvarAttrib int | |
| 159 | + | // TagFormatError occurs when the parser fails to parse the objc tag in a Selector object | |
| 160 | + | var TagFormatError = errors.New(`objc tag doesn't match "ClassName : SuperClassName <Protocol, ...>""`) | |
| 192 | 161 | ||
| 193 | - | const ( | |
| 194 | - | ReadOnly IvarAttrib = 1 << iota | |
| 195 | - | ReadWrite | |
| 196 | - | ) | |
| 162 | + | // MismatchError occurs when the Go struct definition doesn't match that of Objective-C | |
| 163 | + | var MismatchError = errors.New("go struct doesn't match objective-c struct") | |
| 197 | 164 | ||
| 198 | - | // FieldDef is a definition of a field to add to an Objective-C class. | |
| 199 | - | // The name of the field is what will be used to access it through the Ivar. If the type is bool | |
| 200 | - | // the name cannot start with `is` since a getter will be generated with the name `isBoolName`. | |
| 201 | - | // The name also cannot contain any spaces. | |
| 202 | - | // The type is the Go equivalent type of the Ivar. | |
| 203 | - | // Attribute determines if a getter and or setter method is generated for this field. | |
| 204 | - | type FieldDef struct { | |
| 205 | - | Name string | |
| 206 | - | Type reflect.Type | |
| 207 | - | Attribute IvarAttrib | |
| 208 | - | } | |
| 165 | + | // RegisterClass takes a pointer to a struct that implements the Selector interface. | |
| 166 | + | // It will register the structs fields and pointer receiver methods in the Objective-C | |
| 167 | + | // runtime using the SEL returned from Selector. Any errors that occur trying to add | |
| 168 | + | // a Method or Ivar is returned as an error. Such errors may occur in parsing or because | |
| 169 | + | // the size of the struct does not match the size in Objective-C. If no errors occur | |
| 170 | + | // then the returned Class has been registered successfully. | |
| 171 | + | // | |
| 172 | + | // The struct's first field must be of type Class and have a tag that matches the format | |
| 173 | + | // `objc:"ClassName : SuperClassName <Protocol, ...>`. This tag is equal to how the class | |
| 174 | + | // would be defined in Objective-C. | |
| 175 | + | func RegisterClass(object Selector) (Class, error) { | |
| 176 | + | ptr := reflect.TypeOf(object) | |
| 177 | + | strct := ptr.Elem() | |
| 178 | + | if strct.NumField() == 0 || strct.Field(0).Type != reflect.TypeOf(Class(0)) { | |
| 179 | + | return 0, fmt.Errorf("objc: need objc.Class as first field: %w", MismatchError) | |
| 180 | + | } | |
| 181 | + | isa := strct.Field(0) | |
| 182 | + | tag := isa.Tag.Get("objc") | |
| 183 | + | if tag == "" { | |
| 184 | + | return 0, fmt.Errorf("objc: missing objc tag: %w", TagFormatError) | |
| 185 | + | } | |
| 186 | + | // split contains the class name and super class name followed by all the Protocols | |
| 187 | + | // start with two for ClassName : SuperClassName | |
| 188 | + | var split = make([]string, 2) | |
| 189 | + | { | |
| 190 | + | // This is a simple parser for the objc tag that looks for the format | |
| 191 | + | // "ClassName : SuperClassName <Protocol, ...>" | |
| 192 | + | // It appends to the split variable with the [ClassName, SuperClassName, Protocol, ...] | |
| 193 | + | ||
| 194 | + | var i int // from tag[0:i] is whatever identifier is next | |
| 195 | + | var r rune // r is the current rune | |
| 196 | + | skipSpace := func() { | |
| 197 | + | for _, c := range tag { | |
| 198 | + | if c == ' ' { | |
| 199 | + | tag = tag[1:] | |
| 200 | + | continue | |
| 201 | + | } | |
| 202 | + | break | |
| 203 | + | } | |
| 204 | + | } | |
| 205 | + | skipSpace() | |
| 206 | + | // get ClassName | |
| 207 | + | for i, r = range tag { | |
| 208 | + | if r == ' ' || r == ':' { | |
| 209 | + | break | |
| 210 | + | } | |
| 211 | + | } | |
| 212 | + | split[0] = tag[0:i] // store ClassName | |
| 213 | + | tag = tag[i:] | |
| 209 | 214 | ||
| 210 | - | // ivarRegex checks to make sure the Ivar is correctly formatted | |
| 211 | - | var ivarRegex = regexp.MustCompile("[a-z][a-zA-Z0-9]*") | |
| 215 | + | skipSpace() | |
| 212 | 216 | ||
| 213 | - | // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class | |
| 214 | - | // implements, a list of fields this class has and a list of methods. It returns the created class or an error | |
| 215 | - | // describing what went wrong. | |
| 216 | - | func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars []FieldDef, methods []MethodDef) (Class, error) { | |
| 217 | - | class := objc_allocateClassPair(superClass, name, 0) | |
| 217 | + | // check for ':' | |
| 218 | + | if len(tag) > 0 && tag[0] != ':' { | |
| 219 | + | return 0, fmt.Errorf("objc: missing ':': %w", TagFormatError) | |
| 220 | + | } | |
| 221 | + | tag = tag[1:] // skip ':' | |
| 222 | + | skipSpace() | |
| 223 | + | ||
| 224 | + | // get SuperClassName | |
| 225 | + | for i, r = range tag { | |
| 226 | + | if r == ' ' { | |
| 227 | + | break | |
| 228 | + | } else if i+1 == len(tag) { | |
| 229 | + | // if this is the last character in the string | |
| 230 | + | // make sure to increment i so that tag[:i] | |
| 231 | + | // includes the last character | |
| 232 | + | i++ | |
| 233 | + | break | |
| 234 | + | } | |
| 235 | + | } | |
| 236 | + | if len(tag) < i { | |
| 237 | + | return 0, fmt.Errorf("objc: missing SuperClassName: %w", TagFormatError) | |
| 238 | + | } | |
| 239 | + | split[1] = tag[:i] // store SuperClassName | |
| 240 | + | tag = tag[i:] // drop SuperClassName | |
| 241 | + | skipSpace() | |
| 242 | + | if len(tag) > 0 { | |
| 243 | + | if tag[0] != '<' { | |
| 244 | + | return 0, fmt.Errorf("objc: expected '<': %w", TagFormatError) | |
| 245 | + | } | |
| 246 | + | tag = tag[1:] // drop '<' | |
| 247 | + | // get Protocols | |
| 248 | + | outer: | |
| 249 | + | for { | |
| 250 | + | skipSpace() | |
| 251 | + | for i, r = range tag { | |
| 252 | + | switch r { | |
| 253 | + | case ' ': | |
| 254 | + | split = append(split, tag[:i]) | |
| 255 | + | tag = tag[i:] | |
| 256 | + | continue outer | |
| 257 | + | case ',': | |
| 258 | + | // If there is actually an identifier - add it. | |
| 259 | + | if i > 0 { | |
| 260 | + | split = append(split, tag[:i]) | |
| 261 | + | tag = tag[i:] | |
| 262 | + | } else { | |
| 263 | + | // Otherwise, drop ',' | |
| 264 | + | tag = tag[1:] | |
| 265 | + | } | |
| 266 | + | continue outer | |
| 267 | + | case '>': | |
| 268 | + | // If there is actually an identifier - add it. | |
| 269 | + | if i > 0 { | |
| 270 | + | split = append(split, tag[:i]) | |
| 271 | + | tag = tag[i:] | |
| 272 | + | } | |
| 273 | + | break outer | |
| 274 | + | } | |
| 275 | + | } | |
| 276 | + | return 0, fmt.Errorf("objc: expected '>': %w", TagFormatError) | |
| 277 | + | } | |
| 278 | + | } | |
| 279 | + | } | |
| 280 | + | class := objc_allocateClassPair(GetClass(split[1]), split[0], 0) | |
| 218 | 281 | if class == 0 { | |
| 219 | - | return 0, fmt.Errorf("objc: failed to create class with name '%s'", name) | |
| 282 | + | return 0, fmt.Errorf("objc: failed to create class with name '%s'", split[0]) | |
| 220 | 283 | } | |
| 221 | - | // Add Protocols | |
| 222 | - | for _, p := range protocols { | |
| 223 | - | if !class.AddProtocol(p) { | |
| 224 | - | return 0, fmt.Errorf("objc: couldn't add Protocol %s", protocol_getName(p)) | |
| 284 | + | if len(split) > 2 { | |
| 285 | + | // Add Protocols | |
| 286 | + | for _, n := range split[2:] { | |
| 287 | + | if !class.AddProtocol(GetProtocol(n)) { | |
| 288 | + | return 0, fmt.Errorf("objc: couldn't add Protocol %s", n) | |
| 289 | + | } | |
| 225 | 290 | } | |
| 226 | 291 | } | |
| 227 | - | // Add exported methods based on the selectors returned from ClassDef(string) SEL | |
| 228 | - | for idx, def := range methods { | |
| 292 | + | // Add exported methods based on the selectors returned from Selector(string) SEL | |
| 293 | + | for i := 0; i < ptr.NumMethod(); i++ { | |
| 294 | + | met := ptr.Method(i) | |
| 295 | + | // we know this method is the interface one since RegisterClass | |
| 296 | + | // requires that the struct implement Selector. | |
| 297 | + | if met.Name == "Selector" { | |
| 298 | + | continue | |
| 299 | + | } | |
| 300 | + | sel := object.Selector(met.Name) | |
| 301 | + | if sel == 0 { | |
| 302 | + | continue | |
| 303 | + | } | |
| 304 | + | fn := met.Func.Interface() | |
| 229 | 305 | imp, err := func() (imp IMP, err error) { | |
| 230 | 306 | defer func() { | |
| 231 | 307 | if r := recover(); r != nil { | |
| 232 | 308 | err = fmt.Errorf("objc: failed to create IMP: %s", r) | |
| 233 | 309 | } | |
| 234 | 310 | }() | |
| 235 | - | return NewIMP(def.Fn), nil | |
| 311 | + | return NewIMP(fn), nil | |
| 236 | 312 | }() | |
| 237 | 313 | if err != nil { | |
| 238 | - | return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) | |
| 314 | + | return 0, fmt.Errorf("objc: couldn't add Method %s: %w", met.Name, err) | |
| 239 | 315 | } | |
| 240 | - | encoding, err := encodeFunc(def.Fn) | |
| 316 | + | encoding, err := encodeFunc(fn) | |
| 241 | 317 | if err != nil { | |
| 242 | - | return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) | |
| 318 | + | return 0, fmt.Errorf("objc: couldn't add Method %s: %w", met.Name, err) | |
| 243 | 319 | } | |
| 244 | - | if !class.AddMethod(def.Cmd, imp, encoding) { | |
| 245 | - | return 0, fmt.Errorf("objc: couldn't add Method at index %d", idx) | |
| 320 | + | if !class.AddMethod(sel, imp, encoding) { | |
| 321 | + | return 0, fmt.Errorf("objc: couldn't add Method %s", met.Name) | |
| 246 | 322 | } | |
| 247 | 323 | } | |
| 248 | 324 | // Add Ivars | |
| 249 | - | for _, ivar := range ivars { | |
| 250 | - | if !ivarRegex.MatchString(ivar.Name) { | |
| 251 | - | return 0, fmt.Errorf("objc: Ivar must start with a lowercase letter and only contain ASCII letters and numbers: '%s'", ivar.Name) | |
| 325 | + | // Start at 1 because we skip the class object which is first | |
| 326 | + | for i := 1; i < strct.NumField(); i++ { | |
| 327 | + | f := strct.Field(i) | |
| 328 | + | if f.Name == "_" { | |
| 329 | + | continue | |
| 252 | 330 | } | |
| 253 | - | size := ivar.Type.Size() | |
| 254 | - | alignment := uint8(math.Log2(float64(ivar.Type.Align()))) | |
| 255 | - | enc, err := encodeType(ivar.Type, false) | |
| 331 | + | size := f.Type.Size() | |
| 332 | + | alignment := uint8(math.Log2(float64(f.Type.Align()))) | |
| 333 | + | enc, err := encodeType(f.Type, false) | |
| 256 | 334 | if err != nil { | |
| 257 | - | return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", ivar.Name, err) | |
| 335 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", f.Name, err) | |
| 258 | 336 | } | |
| 259 | - | if !class_addIvar(class, ivar.Name, size, alignment, enc) { | |
| 260 | - | return 0, fmt.Errorf("objc: couldn't add Ivar %s", ivar.Name) | |
| 337 | + | if !class_addIvar(class, f.Name, size, alignment, enc) { | |
| 338 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s", f.Name) | |
| 261 | 339 | } | |
| 262 | - | switch ivar.Attribute { | |
| 263 | - | case ReadWrite: | |
| 264 | - | ty := reflect.FuncOf( | |
| 265 | - | []reflect.Type{ | |
| 266 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, | |
| 267 | - | }, | |
| 268 | - | nil, false, | |
| 269 | - | ) | |
| 270 | - | var encoding string | |
| 271 | - | if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { | |
| 272 | - | return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) | |
| 273 | - | } | |
| 274 | - | instanceVariable := class.InstanceVariable(ivar.Name) | |
| 275 | - | val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |
| 276 | - | // on entry the first and second arguments are ID and SEL followed by the value | |
| 277 | - | if len(args) != 3 { | |
| 278 | - | panic(fmt.Errorf("objc: incorrect number of arguments")) | |
| 279 | - | } | |
| 280 | - | // Grab the pointer from using the Ivar and dereference it | |
| 281 | - | var value ID | |
| 282 | - | switch args[2].Kind() { | |
| 283 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 284 | - | value = ID(args[2].Int()) | |
| 285 | - | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |
| 286 | - | value = ID(args[2].Uint()) | |
| 287 | - | case reflect.Float32, reflect.Float64: | |
| 288 | - | value = ID(math.Float64bits(args[2].Float())) | |
| 289 | - | case reflect.Bool: | |
| 290 | - | if args[2].Bool() { | |
| 291 | - | value = 1 | |
| 292 | - | } | |
| 293 | - | case reflect.Pointer, reflect.UnsafePointer: | |
| 294 | - | value = ID(args[2].Pointer()) | |
| 295 | - | default: | |
| 296 | - | panic(fmt.Errorf("objc: unsupported kind %s", args[2].Kind())) | |
| 297 | - | } | |
| 298 | - | object_setIvar(args[0].Interface().(ID), instanceVariable, value) | |
| 299 | - | return nil | |
| 300 | - | }).Interface() | |
| 301 | - | // this code only works for ascii but that shouldn't be a problem | |
| 302 | - | selector := "set" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + ":\x00" | |
| 303 | - | class.AddMethod(RegisterName(selector), NewIMP(val), encoding) | |
| 304 | - | fallthrough // also implement the read method | |
| 305 | - | case ReadOnly: | |
| 306 | - | ty := reflect.FuncOf( | |
| 307 | - | []reflect.Type{ | |
| 308 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), | |
| 309 | - | }, | |
| 310 | - | []reflect.Type{ivar.Type}, false, | |
| 311 | - | ) | |
| 312 | - | var encoding string | |
| 313 | - | if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { | |
| 314 | - | return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) | |
| 315 | - | } | |
| 316 | - | instanceVariable := class.InstanceVariable(ivar.Name) | |
| 317 | - | val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |
| 318 | - | // on entry the first and second arguments are ID and SEL | |
| 319 | - | if len(args) != 2 { | |
| 320 | - | panic(fmt.Errorf("objc: incorrect number of arguments")) | |
| 321 | - | } | |
| 322 | - | // Grab the pointer from using the Ivar and dereference it | |
| 323 | - | variable := object_getIvar(args[0].Interface().(ID), instanceVariable) | |
| 324 | - | return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Pointer(&variable)).Elem()} | |
| 325 | - | }).Interface() | |
| 326 | - | if ivar.Type.Kind() == reflect.Bool { | |
| 327 | - | // this code only works for ascii but that shouldn't be a problem | |
| 328 | - | ivar.Name = "is" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] | |
| 329 | - | } | |
| 330 | - | class.AddMethod(RegisterName(ivar.Name), NewIMP(val), encoding) | |
| 331 | - | default: | |
| 332 | - | return 0, fmt.Errorf("objc: unknown Ivar Attribute (%d)", ivar.Attribute) | |
| 340 | + | if offset := class.InstanceVariable(f.Name).Offset(); offset != f.Offset { | |
| 341 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s because offset (%d != %d)", f.Name, offset, f.Offset) | |
| 333 | 342 | } | |
| 334 | 343 | } | |
| 335 | 344 | objc_registerClassPair(class) | |
| 345 | + | if size1, size2 := class.InstanceSize(), strct.Size(); size1 != size2 { | |
| 346 | + | return 0, fmt.Errorf("objc: sizes don't match %d != %d: %w", size1, size2, MismatchError) | |
| 347 | + | } | |
| 336 | 348 | return class, nil | |
| 337 | 349 | } | |
| 338 | 350 | ||
| @@ -408,7 +420,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||
| 408 | 420 | if insidePtr { | |
| 409 | 421 | return encStructBegin + typ.Name() + encStructEnd, nil | |
| 410 | 422 | } | |
| 411 | - | encoding := encStructBegin | |
| 423 | + | var encoding = encStructBegin | |
| 412 | 424 | encoding += typ.Name() | |
| 413 | 425 | encoding += "=" | |
| 414 | 426 | for i := 0; i < typ.NumField(); i++ { | |
| @@ -532,24 +544,19 @@ func (i Ivar) Offset() uintptr { | |||
| 532 | 544 | } | |
| 533 | 545 | ||
| 534 | 546 | // Protocol is a type that declares methods that can be implemented by any class. | |
| 535 | - | type Protocol [0]func() | |
| 547 | + | type Protocol uintptr | |
| 536 | 548 | ||
| 537 | 549 | // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name. | |
| 538 | 550 | func GetProtocol(name string) *Protocol { | |
| 539 | 551 | return objc_getProtocol(name) | |
| 540 | 552 | } | |
| 541 | 553 | ||
| 542 | - | // Equals return true if the two protocols are the same. | |
| 543 | - | func (p *Protocol) Equals(p2 *Protocol) bool { | |
| 544 | - | return protocol_isEqual(p, p2) | |
| 545 | - | } | |
| 546 | - | ||
| 547 | 554 | // IMP is a function pointer that can be called by Objective-C code. | |
| 548 | 555 | type IMP uintptr | |
| 549 | 556 | ||
| 550 | 557 | // NewIMP takes a Go function that takes (ID, SEL) as its first two arguments. | |
| 558 | + | // ID may instead be a pointer to a struct whose first field has type Class. | |
| 551 | 559 | // It returns an IMP function pointer that can be called by Objective-C code. | |
| 552 | - | // The function panics if an error occurs. | |
| 553 | 560 | // The function pointer is never deallocated. | |
| 554 | 561 | func NewIMP(fn interface{}) IMP { | |
| 555 | 562 | ty := reflect.TypeOf(fn) | |
| @@ -561,9 +568,13 @@ func NewIMP(fn interface{}) IMP { | |||
| 561 | 568 | switch { | |
| 562 | 569 | case ty.NumIn() < 2: | |
| 563 | 570 | fallthrough | |
| 564 | - | case ty.In(0) != reflect.TypeOf(ID(0)): | |
| 571 | + | case ty.In(0).Kind() != reflect.Uintptr && // checks if it's objc.ID | |
| 572 | + | // or that it's a pointer to a struct | |
| 573 | + | (ty.In(0).Kind() != reflect.Pointer || ty.In(0).Elem().Kind() != reflect.Struct || | |
| 574 | + | // and that the structs first field is an objc.Class | |
| 575 | + | ty.In(0).Elem().Field(0).Type != reflect.TypeOf(Class(0))): | |
| 565 | 576 | fallthrough | |
| 566 | - | case ty.In(1) != reflect.TypeOf(SEL(0)): | |
| 577 | + | case ty.In(1).Kind() != reflect.Uintptr: | |
| 567 | 578 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | |
| 568 | 579 | } | |
| 569 | 580 | return IMP(purego.NewCallback(fn)) | |
| @@ -10,15 +10,13 @@ import ( | |||||
| 10 | "fmt" | 10 | "fmt" | ||
| 11 | "math" | 11 | "math" | ||
| 12 | "reflect" | 12 | "reflect" | ||
| 13 | - | "regexp" | |||
| 14 | - | "unicode" | |||
| 15 | - | "unsafe" | |||
| 16 | 13 | ||||
| 17 | "github.com/ebitengine/purego" | 14 | "github.com/ebitengine/purego" | ||
| 18 | ) | 15 | ) | ||
| 19 | 16 | ||||
| 20 | - | // TODO: support try/catch? | 17 | + | //TODO: support try/catch? |
| 21 | - | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | 18 | + | //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime |
| 19 | + | ||||
| 22 | var ( | 20 | var ( | ||
| 23 | objc_msgSend_fn uintptr | 21 | objc_msgSend_fn uintptr | ||
| 24 | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | 22 | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | ||
| @@ -37,10 +35,6 @@ var ( | |||||
| 37 | class_addProtocol func(class Class, protocol *Protocol) bool | 35 | class_addProtocol func(class Class, protocol *Protocol) bool | ||
| 38 | ivar_getOffset func(ivar Ivar) uintptr | 36 | ivar_getOffset func(ivar Ivar) uintptr | ||
| 39 | object_getClass func(obj ID) Class | 37 | object_getClass func(obj ID) Class | ||
| 40 | - | object_getIvar func(obj ID, ivar Ivar) ID | |||
| 41 | - | object_setIvar func(obj ID, ivar Ivar, value ID) | |||
| 42 | - | protocol_getName func(protocol *Protocol) string | |||
| 43 | - | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | |||
| 44 | ) | 38 | ) | ||
| 45 | 39 | ||||
| 46 | func init() { | 40 | func init() { | ||
| @@ -71,10 +65,6 @@ func init() { | |||||
| 71 | purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") | 65 | purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") | ||
| 72 | purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") | 66 | purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") | ||
| 73 | purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") | 67 | purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") | ||
| 74 | - | purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") | |||
| 75 | - | purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") | |||
| 76 | - | purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") | |||
| 77 | - | purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") | |||
| 78 | } | 68 | } | ||
| 79 | 69 | ||||
| 80 | // ID is an opaque pointer to some Objective-C object | 70 | // ID is an opaque pointer to some Objective-C object | ||
| @@ -92,16 +82,6 @@ func (id ID) Send(sel SEL, args ...interface{}) ID { | |||||
| 92 | return objc_msgSend(id, sel, args...) | 82 | return objc_msgSend(id, sel, args...) | ||
| 93 | } | 83 | } | ||
| 94 | 84 | ||||
| 95 | - | // GetIvar reads the value of an instance variable in an object. | |||
| 96 | - | func (id ID) GetIvar(ivar Ivar) ID { | |||
| 97 | - | return object_getIvar(id, ivar) | |||
| 98 | - | } | |||
| 99 | - | ||||
| 100 | - | // SetIvar sets the value of an instance variable in an object. | |||
| 101 | - | func (id ID) SetIvar(ivar Ivar, value ID) { | |||
| 102 | - | object_setIvar(id, ivar, value) | |||
| 103 | - | } | |||
| 104 | - | ||||
| 105 | // Send is a convenience method for sending messages to objects that can return any type. | 85 | // Send is a convenience method for sending messages to objects that can return any type. | ||
| 106 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | 86 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | ||
| 107 | // It is best to cache the result of RegisterName. | 87 | // It is best to cache the result of RegisterName. | ||
| @@ -123,7 +103,7 @@ type objc_super struct { | |||||
| 123 | // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result | 103 | // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result | ||
| 124 | // of RegisterName. | 104 | // of RegisterName. | ||
| 125 | func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | 105 | func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | ||
| 126 | - | super := &objc_super{ | 106 | + | var super = &objc_super{ |
| 127 | receiver: id, | 107 | receiver: id, | ||
| 128 | superClass: id.Class(), | 108 | superClass: id.Class(), | ||
| 129 | } | 109 | } | ||
| @@ -134,7 +114,7 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | |||||
| 134 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | 114 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | ||
| 135 | // It is best to cache the result of RegisterName. | 115 | // It is best to cache the result of RegisterName. | ||
| 136 | func SendSuper[T any](id ID, sel SEL, args ...any) T { | 116 | func SendSuper[T any](id ID, sel SEL, args ...any) T { | ||
| 137 | - | super := &objc_super{ | 117 | + | var super = &objc_super{ |
| 138 | receiver: id, | 118 | receiver: id, | ||
| 139 | superClass: id.Class(), | 119 | superClass: id.Class(), | ||
| 140 | } | 120 | } | ||
| @@ -168,171 +148,203 @@ func AllocateClassPair(super Class, name string, extraBytes uintptr) Class { | |||||
| 168 | return objc_allocateClassPair(super, name, extraBytes) | 148 | return objc_allocateClassPair(super, name, extraBytes) | ||
| 169 | } | 149 | } | ||
| 170 | 150 | ||||
| 171 | - | // MethodDef represents the Go function and the selector that ObjC uses to access that function. | 151 | + | // Selector is an interface that takes a Go method name |
| 172 | - | type MethodDef struct { | 152 | + | // and returns the selector equivalent name. |
| 173 | - | Cmd SEL | 153 | + | // If it returns a nil SEL then that method |
| 174 | - | Fn any | 154 | + | // is not added to the class object. |
| 155 | + | type Selector interface { | |||
| 156 | + | Selector(string) SEL | |||
| 175 | } | 157 | } | ||
| 176 | 158 | ||||
| 177 | - | // IvarAttrib is the attribute that an ivar has. It affects if and which methods are automatically | 159 | + | // TagFormatError occurs when the parser fails to parse the objc tag in a Selector object |
| 178 | - | // generated when creating a class with RegisterClass. See [Apple Docs] for an understanding of these attributes. | 160 | + | var TagFormatError = errors.New(`objc tag doesn't match "ClassName : SuperClassName <Protocol, ...>""`) |
| 179 | - | // The fields are still accessible using objc.GetIvar and objc.SetIvar regardless of the value of IvarAttrib. | |||
| 180 | - | // | |||
| 181 | - | // Take for example this Objective-C code: | |||
| 182 | - | // | |||
| 183 | - | // @property (readwrite) float value; | |||
| 184 | - | // | |||
| 185 | - | // In Go, the functions can be accessed as followed: | |||
| 186 | - | // | |||
| 187 | - | // var value = purego.Send[float32](id, purego.RegisterName("value")) | |||
| 188 | - | // id.Send(purego.RegisterName("setValue:"), 3.46) | |||
| 189 | - | // | |||
| 190 | - | // [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html | |||
| 191 | - | type IvarAttrib int | |||
| 192 | 161 | ||||
| 193 | - | const ( | 162 | + | // MismatchError occurs when the Go struct definition doesn't match that of Objective-C |
| 194 | - | ReadOnly IvarAttrib = 1 << iota | 163 | + | var MismatchError = errors.New("go struct doesn't match objective-c struct") |
| 195 | - | ReadWrite | |||
| 196 | - | ) | |||
| 197 | 164 | ||||
| 198 | - | // FieldDef is a definition of a field to add to an Objective-C class. | 165 | + | // RegisterClass takes a pointer to a struct that implements the Selector interface. |
| 199 | - | // The name of the field is what will be used to access it through the Ivar. If the type is bool | 166 | + | // It will register the structs fields and pointer receiver methods in the Objective-C |
| 200 | - | // the name cannot start with `is` since a getter will be generated with the name `isBoolName`. | 167 | + | // runtime using the SEL returned from Selector. Any errors that occur trying to add |
| 201 | - | // The name also cannot contain any spaces. | 168 | + | // a Method or Ivar is returned as an error. Such errors may occur in parsing or because |
| 202 | - | // The type is the Go equivalent type of the Ivar. | 169 | + | // the size of the struct does not match the size in Objective-C. If no errors occur |
| 203 | - | // Attribute determines if a getter and or setter method is generated for this field. | 170 | + | // then the returned Class has been registered successfully. |
| 204 | - | type FieldDef struct { | 171 | + | // |
| 205 | - | Name string | 172 | + | // The struct's first field must be of type Class and have a tag that matches the format |
| 206 | - | Type reflect.Type | 173 | + | // `objc:"ClassName : SuperClassName <Protocol, ...>`. This tag is equal to how the class |
| 207 | - | Attribute IvarAttrib | 174 | + | // would be defined in Objective-C. |
| 208 | - | } | 175 | + | func RegisterClass(object Selector) (Class, error) { |
| 176 | + | ptr := reflect.TypeOf(object) | |||
| 177 | + | strct := ptr.Elem() | |||
| 178 | + | if strct.NumField() == 0 || strct.Field(0).Type != reflect.TypeOf(Class(0)) { | |||
| 179 | + | return 0, fmt.Errorf("objc: need objc.Class as first field: %w", MismatchError) | |||
| 180 | + | } | |||
| 181 | + | isa := strct.Field(0) | |||
| 182 | + | tag := isa.Tag.Get("objc") | |||
| 183 | + | if tag == "" { | |||
| 184 | + | return 0, fmt.Errorf("objc: missing objc tag: %w", TagFormatError) | |||
| 185 | + | } | |||
| 186 | + | // split contains the class name and super class name followed by all the Protocols | |||
| 187 | + | // start with two for ClassName : SuperClassName | |||
| 188 | + | var split = make([]string, 2) | |||
| 189 | + | { | |||
| 190 | + | // This is a simple parser for the objc tag that looks for the format | |||
| 191 | + | // "ClassName : SuperClassName <Protocol, ...>" | |||
| 192 | + | // It appends to the split variable with the [ClassName, SuperClassName, Protocol, ...] | |||
| 193 | + | ||||
| 194 | + | var i int // from tag[0:i] is whatever identifier is next | |||
| 195 | + | var r rune // r is the current rune | |||
| 196 | + | skipSpace := func() { | |||
| 197 | + | for _, c := range tag { | |||
| 198 | + | if c == ' ' { | |||
| 199 | + | tag = tag[1:] | |||
| 200 | + | continue | |||
| 201 | + | } | |||
| 202 | + | break | |||
| 203 | + | } | |||
| 204 | + | } | |||
| 205 | + | skipSpace() | |||
| 206 | + | // get ClassName | |||
| 207 | + | for i, r = range tag { | |||
| 208 | + | if r == ' ' || r == ':' { | |||
| 209 | + | break | |||
| 210 | + | } | |||
| 211 | + | } | |||
| 212 | + | split[0] = tag[0:i] // store ClassName | |||
| 213 | + | tag = tag[i:] | |||
| 209 | 214 | ||||
| 210 | - | // ivarRegex checks to make sure the Ivar is correctly formatted | 215 | + | skipSpace() |
| 211 | - | var ivarRegex = regexp.MustCompile("[a-z][a-zA-Z0-9]*") | |||
| 212 | 216 | ||||
| 213 | - | // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class | 217 | + | // check for ':' |
| 214 | - | // implements, a list of fields this class has and a list of methods. It returns the created class or an error | 218 | + | if len(tag) > 0 && tag[0] != ':' { |
| 215 | - | // describing what went wrong. | 219 | + | return 0, fmt.Errorf("objc: missing ':': %w", TagFormatError) |
| 216 | - | func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars []FieldDef, methods []MethodDef) (Class, error) { | 220 | + | } |
| 217 | - | class := objc_allocateClassPair(superClass, name, 0) | 221 | + | tag = tag[1:] // skip ':' |
| 222 | + | skipSpace() | |||
| 223 | + | ||||
| 224 | + | // get SuperClassName | |||
| 225 | + | for i, r = range tag { | |||
| 226 | + | if r == ' ' { | |||
| 227 | + | break | |||
| 228 | + | } else if i+1 == len(tag) { | |||
| 229 | + | // if this is the last character in the string | |||
| 230 | + | // make sure to increment i so that tag[:i] | |||
| 231 | + | // includes the last character | |||
| 232 | + | i++ | |||
| 233 | + | break | |||
| 234 | + | } | |||
| 235 | + | } | |||
| 236 | + | if len(tag) < i { | |||
| 237 | + | return 0, fmt.Errorf("objc: missing SuperClassName: %w", TagFormatError) | |||
| 238 | + | } | |||
| 239 | + | split[1] = tag[:i] // store SuperClassName | |||
| 240 | + | tag = tag[i:] // drop SuperClassName | |||
| 241 | + | skipSpace() | |||
| 242 | + | if len(tag) > 0 { | |||
| 243 | + | if tag[0] != '<' { | |||
| 244 | + | return 0, fmt.Errorf("objc: expected '<': %w", TagFormatError) | |||
| 245 | + | } | |||
| 246 | + | tag = tag[1:] // drop '<' | |||
| 247 | + | // get Protocols | |||
| 248 | + | outer: | |||
| 249 | + | for { | |||
| 250 | + | skipSpace() | |||
| 251 | + | for i, r = range tag { | |||
| 252 | + | switch r { | |||
| 253 | + | case ' ': | |||
| 254 | + | split = append(split, tag[:i]) | |||
| 255 | + | tag = tag[i:] | |||
| 256 | + | continue outer | |||
| 257 | + | case ',': | |||
| 258 | + | // If there is actually an identifier - add it. | |||
| 259 | + | if i > 0 { | |||
| 260 | + | split = append(split, tag[:i]) | |||
| 261 | + | tag = tag[i:] | |||
| 262 | + | } else { | |||
| 263 | + | // Otherwise, drop ',' | |||
| 264 | + | tag = tag[1:] | |||
| 265 | + | } | |||
| 266 | + | continue outer | |||
| 267 | + | case '>': | |||
| 268 | + | // If there is actually an identifier - add it. | |||
| 269 | + | if i > 0 { | |||
| 270 | + | split = append(split, tag[:i]) | |||
| 271 | + | tag = tag[i:] | |||
| 272 | + | } | |||
| 273 | + | break outer | |||
| 274 | + | } | |||
| 275 | + | } | |||
| 276 | + | return 0, fmt.Errorf("objc: expected '>': %w", TagFormatError) | |||
| 277 | + | } | |||
| 278 | + | } | |||
| 279 | + | } | |||
| 280 | + | class := objc_allocateClassPair(GetClass(split[1]), split[0], 0) | |||
| 218 | if class == 0 { | 281 | if class == 0 { | ||
| 219 | - | return 0, fmt.Errorf("objc: failed to create class with name '%s'", name) | 282 | + | return 0, fmt.Errorf("objc: failed to create class with name '%s'", split[0]) |
| 220 | } | 283 | } | ||
| 221 | - | // Add Protocols | 284 | + | if len(split) > 2 { |
| 222 | - | for _, p := range protocols { | 285 | + | // Add Protocols |
| 223 | - | if !class.AddProtocol(p) { | 286 | + | for _, n := range split[2:] { |
| 224 | - | return 0, fmt.Errorf("objc: couldn't add Protocol %s", protocol_getName(p)) | 287 | + | if !class.AddProtocol(GetProtocol(n)) { |
| 288 | + | return 0, fmt.Errorf("objc: couldn't add Protocol %s", n) | |||
| 289 | + | } | |||
| 225 | } | 290 | } | ||
| 226 | } | 291 | } | ||
| 227 | - | // Add exported methods based on the selectors returned from ClassDef(string) SEL | 292 | + | // Add exported methods based on the selectors returned from Selector(string) SEL |
| 228 | - | for idx, def := range methods { | 293 | + | for i := 0; i < ptr.NumMethod(); i++ { |
| 294 | + | met := ptr.Method(i) | |||
| 295 | + | // we know this method is the interface one since RegisterClass | |||
| 296 | + | // requires that the struct implement Selector. | |||
| 297 | + | if met.Name == "Selector" { | |||
| 298 | + | continue | |||
| 299 | + | } | |||
| 300 | + | sel := object.Selector(met.Name) | |||
| 301 | + | if sel == 0 { | |||
| 302 | + | continue | |||
| 303 | + | } | |||
| 304 | + | fn := met.Func.Interface() | |||
| 229 | imp, err := func() (imp IMP, err error) { | 305 | imp, err := func() (imp IMP, err error) { | ||
| 230 | defer func() { | 306 | defer func() { | ||
| 231 | if r := recover(); r != nil { | 307 | if r := recover(); r != nil { | ||
| 232 | err = fmt.Errorf("objc: failed to create IMP: %s", r) | 308 | err = fmt.Errorf("objc: failed to create IMP: %s", r) | ||
| 233 | } | 309 | } | ||
| 234 | }() | 310 | }() | ||
| 235 | - | return NewIMP(def.Fn), nil | 311 | + | return NewIMP(fn), nil |
| 236 | }() | 312 | }() | ||
| 237 | if err != nil { | 313 | if err != nil { | ||
| 238 | - | return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) | 314 | + | return 0, fmt.Errorf("objc: couldn't add Method %s: %w", met.Name, err) |
| 239 | } | 315 | } | ||
| 240 | - | encoding, err := encodeFunc(def.Fn) | 316 | + | encoding, err := encodeFunc(fn) |
| 241 | if err != nil { | 317 | if err != nil { | ||
| 242 | - | return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) | 318 | + | return 0, fmt.Errorf("objc: couldn't add Method %s: %w", met.Name, err) |
| 243 | } | 319 | } | ||
| 244 | - | if !class.AddMethod(def.Cmd, imp, encoding) { | 320 | + | if !class.AddMethod(sel, imp, encoding) { |
| 245 | - | return 0, fmt.Errorf("objc: couldn't add Method at index %d", idx) | 321 | + | return 0, fmt.Errorf("objc: couldn't add Method %s", met.Name) |
| 246 | } | 322 | } | ||
| 247 | } | 323 | } | ||
| 248 | // Add Ivars | 324 | // Add Ivars | ||
| 249 | - | for _, ivar := range ivars { | 325 | + | // Start at 1 because we skip the class object which is first |
| 250 | - | if !ivarRegex.MatchString(ivar.Name) { | 326 | + | for i := 1; i < strct.NumField(); i++ { |
| 251 | - | return 0, fmt.Errorf("objc: Ivar must start with a lowercase letter and only contain ASCII letters and numbers: '%s'", ivar.Name) | 327 | + | f := strct.Field(i) |
| 328 | + | if f.Name == "_" { | |||
| 329 | + | continue | |||
| 252 | } | 330 | } | ||
| 253 | - | size := ivar.Type.Size() | 331 | + | size := f.Type.Size() |
| 254 | - | alignment := uint8(math.Log2(float64(ivar.Type.Align()))) | 332 | + | alignment := uint8(math.Log2(float64(f.Type.Align()))) |
| 255 | - | enc, err := encodeType(ivar.Type, false) | 333 | + | enc, err := encodeType(f.Type, false) |
| 256 | if err != nil { | 334 | if err != nil { | ||
| 257 | - | return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", ivar.Name, err) | 335 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", f.Name, err) |
| 258 | } | 336 | } | ||
| 259 | - | if !class_addIvar(class, ivar.Name, size, alignment, enc) { | 337 | + | if !class_addIvar(class, f.Name, size, alignment, enc) { |
| 260 | - | return 0, fmt.Errorf("objc: couldn't add Ivar %s", ivar.Name) | 338 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s", f.Name) |
| 261 | } | 339 | } | ||
| 262 | - | switch ivar.Attribute { | 340 | + | if offset := class.InstanceVariable(f.Name).Offset(); offset != f.Offset { |
| 263 | - | case ReadWrite: | 341 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s because offset (%d != %d)", f.Name, offset, f.Offset) |
| 264 | - | ty := reflect.FuncOf( | |||
| 265 | - | []reflect.Type{ | |||
| 266 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, | |||
| 267 | - | }, | |||
| 268 | - | nil, false, | |||
| 269 | - | ) | |||
| 270 | - | var encoding string | |||
| 271 | - | if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { | |||
| 272 | - | return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) | |||
| 273 | - | } | |||
| 274 | - | instanceVariable := class.InstanceVariable(ivar.Name) | |||
| 275 | - | val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |||
| 276 | - | // on entry the first and second arguments are ID and SEL followed by the value | |||
| 277 | - | if len(args) != 3 { | |||
| 278 | - | panic(fmt.Errorf("objc: incorrect number of arguments")) | |||
| 279 | - | } | |||
| 280 | - | // Grab the pointer from using the Ivar and dereference it | |||
| 281 | - | var value ID | |||
| 282 | - | switch args[2].Kind() { | |||
| 283 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
| 284 | - | value = ID(args[2].Int()) | |||
| 285 | - | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
| 286 | - | value = ID(args[2].Uint()) | |||
| 287 | - | case reflect.Float32, reflect.Float64: | |||
| 288 | - | value = ID(math.Float64bits(args[2].Float())) | |||
| 289 | - | case reflect.Bool: | |||
| 290 | - | if args[2].Bool() { | |||
| 291 | - | value = 1 | |||
| 292 | - | } | |||
| 293 | - | case reflect.Pointer, reflect.UnsafePointer: | |||
| 294 | - | value = ID(args[2].Pointer()) | |||
| 295 | - | default: | |||
| 296 | - | panic(fmt.Errorf("objc: unsupported kind %s", args[2].Kind())) | |||
| 297 | - | } | |||
| 298 | - | object_setIvar(args[0].Interface().(ID), instanceVariable, value) | |||
| 299 | - | return nil | |||
| 300 | - | }).Interface() | |||
| 301 | - | // this code only works for ascii but that shouldn't be a problem | |||
| 302 | - | selector := "set" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + ":\x00" | |||
| 303 | - | class.AddMethod(RegisterName(selector), NewIMP(val), encoding) | |||
| 304 | - | fallthrough // also implement the read method | |||
| 305 | - | case ReadOnly: | |||
| 306 | - | ty := reflect.FuncOf( | |||
| 307 | - | []reflect.Type{ | |||
| 308 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), | |||
| 309 | - | }, | |||
| 310 | - | []reflect.Type{ivar.Type}, false, | |||
| 311 | - | ) | |||
| 312 | - | var encoding string | |||
| 313 | - | if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { | |||
| 314 | - | return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) | |||
| 315 | - | } | |||
| 316 | - | instanceVariable := class.InstanceVariable(ivar.Name) | |||
| 317 | - | val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |||
| 318 | - | // on entry the first and second arguments are ID and SEL | |||
| 319 | - | if len(args) != 2 { | |||
| 320 | - | panic(fmt.Errorf("objc: incorrect number of arguments")) | |||
| 321 | - | } | |||
| 322 | - | // Grab the pointer from using the Ivar and dereference it | |||
| 323 | - | variable := object_getIvar(args[0].Interface().(ID), instanceVariable) | |||
| 324 | - | return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Pointer(&variable)).Elem()} | |||
| 325 | - | }).Interface() | |||
| 326 | - | if ivar.Type.Kind() == reflect.Bool { | |||
| 327 | - | // this code only works for ascii but that shouldn't be a problem | |||
| 328 | - | ivar.Name = "is" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] | |||
| 329 | - | } | |||
| 330 | - | class.AddMethod(RegisterName(ivar.Name), NewIMP(val), encoding) | |||
| 331 | - | default: | |||
| 332 | - | return 0, fmt.Errorf("objc: unknown Ivar Attribute (%d)", ivar.Attribute) | |||
| 333 | } | 342 | } | ||
| 334 | } | 343 | } | ||
| 335 | objc_registerClassPair(class) | 344 | objc_registerClassPair(class) | ||
| 345 | + | if size1, size2 := class.InstanceSize(), strct.Size(); size1 != size2 { | |||
| 346 | + | return 0, fmt.Errorf("objc: sizes don't match %d != %d: %w", size1, size2, MismatchError) | |||
| 347 | + | } | |||
| 336 | return class, nil | 348 | return class, nil | ||
| 337 | } | 349 | } | ||
| 338 | 350 | ||||
| @@ -408,7 +420,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||||
| 408 | if insidePtr { | 420 | if insidePtr { | ||
| 409 | return encStructBegin + typ.Name() + encStructEnd, nil | 421 | return encStructBegin + typ.Name() + encStructEnd, nil | ||
| 410 | } | 422 | } | ||
| 411 | - | encoding := encStructBegin | 423 | + | var encoding = encStructBegin |
| 412 | encoding += typ.Name() | 424 | encoding += typ.Name() | ||
| 413 | encoding += "=" | 425 | encoding += "=" | ||
| 414 | for i := 0; i < typ.NumField(); i++ { | 426 | for i := 0; i < typ.NumField(); i++ { | ||
| @@ -532,24 +544,19 @@ func (i Ivar) Offset() uintptr { | |||||
| 532 | } | 544 | } | ||
| 533 | 545 | ||||
| 534 | // Protocol is a type that declares methods that can be implemented by any class. | 546 | // Protocol is a type that declares methods that can be implemented by any class. | ||
| 535 | - | type Protocol [0]func() | 547 | + | type Protocol uintptr |
| 536 | 548 | ||||
| 537 | // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name. | 549 | // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name. | ||
| 538 | func GetProtocol(name string) *Protocol { | 550 | func GetProtocol(name string) *Protocol { | ||
| 539 | return objc_getProtocol(name) | 551 | return objc_getProtocol(name) | ||
| 540 | } | 552 | } | ||
| 541 | 553 | ||||
| 542 | - | // Equals return true if the two protocols are the same. | |||
| 543 | - | func (p *Protocol) Equals(p2 *Protocol) bool { | |||
| 544 | - | return protocol_isEqual(p, p2) | |||
| 545 | - | } | |||
| 546 | - | ||||
| 547 | // IMP is a function pointer that can be called by Objective-C code. | 554 | // IMP is a function pointer that can be called by Objective-C code. | ||
| 548 | type IMP uintptr | 555 | type IMP uintptr | ||
| 549 | 556 | ||||
| 550 | // NewIMP takes a Go function that takes (ID, SEL) as its first two arguments. | 557 | // NewIMP takes a Go function that takes (ID, SEL) as its first two arguments. | ||
| 558 | + | // ID may instead be a pointer to a struct whose first field has type Class. | |||
| 551 | // It returns an IMP function pointer that can be called by Objective-C code. | 559 | // It returns an IMP function pointer that can be called by Objective-C code. | ||
| 552 | - | // The function panics if an error occurs. | |||
| 553 | // The function pointer is never deallocated. | 560 | // The function pointer is never deallocated. | ||
| 554 | func NewIMP(fn interface{}) IMP { | 561 | func NewIMP(fn interface{}) IMP { | ||
| 555 | ty := reflect.TypeOf(fn) | 562 | ty := reflect.TypeOf(fn) | ||
| @@ -561,9 +568,13 @@ func NewIMP(fn interface{}) IMP { | |||||
| 561 | switch { | 568 | switch { | ||
| 562 | case ty.NumIn() < 2: | 569 | case ty.NumIn() < 2: | ||
| 563 | fallthrough | 570 | fallthrough | ||
| 564 | - | case ty.In(0) != reflect.TypeOf(ID(0)): | 571 | + | case ty.In(0).Kind() != reflect.Uintptr && // checks if it's objc.ID |
| 572 | + | // or that it's a pointer to a struct | |||
| 573 | + | (ty.In(0).Kind() != reflect.Pointer || ty.In(0).Elem().Kind() != reflect.Struct || | |||
| 574 | + | // and that the structs first field is an objc.Class | |||
| 575 | + | ty.In(0).Elem().Field(0).Type != reflect.TypeOf(Class(0))): | |||
| 565 | fallthrough | 576 | fallthrough | ||
| 566 | - | case ty.In(1) != reflect.TypeOf(SEL(0)): | 577 | + | case ty.In(1).Kind() != reflect.Uintptr: |
| 567 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | 578 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | ||
| 568 | } | 579 | } | ||
| 569 | return IMP(purego.NewCallback(fn)) | 580 | return IMP(purego.NewCallback(fn)) | ||
M objc/objc_runtime_darwin_test.go +19 -41
| @@ -5,7 +5,6 @@ package objc_test | |||
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | 7 | "fmt" | |
| 8 | - | "reflect" | |
| 9 | 8 | "testing" | |
| 10 | 9 | ||
| 11 | 10 | "github.com/ebitengine/purego" | |
| @@ -13,56 +12,35 @@ import ( | |||
| 13 | 12 | ) | |
| 14 | 13 | ||
| 15 | 14 | func ExampleAllocateClassPair() { | |
| 16 | - | class := objc.AllocateClassPair(objc.GetClass("NSObject"), "FooObject", 0) | |
| 15 | + | var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "FooObject", 0) | |
| 17 | 16 | class.AddMethod(objc.RegisterName("run"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) { | |
| 18 | 17 | fmt.Println("Hello World!") | |
| 19 | 18 | }), "v@:") | |
| 20 | 19 | class.Register() | |
| 21 | 20 | ||
| 22 | - | fooObject := objc.ID(class).Send(objc.RegisterName("new")) | |
| 21 | + | var fooObject = objc.ID(class).Send(objc.RegisterName("new")) | |
| 23 | 22 | fooObject.Send(objc.RegisterName("run")) | |
| 24 | 23 | // Output: Hello World! | |
| 25 | 24 | } | |
| 26 | 25 | ||
| 27 | - | func ExampleRegisterClass() { | |
| 28 | - | var ( | |
| 29 | - | sel_new = objc.RegisterName("new") | |
| 30 | - | sel_init = objc.RegisterName("init") | |
| 31 | - | sel_setBar = objc.RegisterName("setBar:") | |
| 32 | - | sel_bar = objc.RegisterName("bar") | |
| 33 | - | ||
| 34 | - | BarInit = func(id objc.ID, cmd objc.SEL) objc.ID { | |
| 35 | - | return id.SendSuper(cmd) | |
| 36 | - | } | |
| 37 | - | ) | |
| 38 | - | ||
| 39 | - | class, err := objc.RegisterClass( | |
| 40 | - | "BarObject", | |
| 41 | - | objc.GetClass("NSObject"), | |
| 42 | - | []*objc.Protocol{ | |
| 43 | - | objc.GetProtocol("NSDelegateWindow"), | |
| 44 | - | }, | |
| 45 | - | []objc.FieldDef{ | |
| 46 | - | { | |
| 47 | - | Name: "bar", | |
| 48 | - | Type: reflect.TypeOf(int(0)), | |
| 49 | - | Attribute: objc.ReadWrite, | |
| 50 | - | }, | |
| 51 | - | }, | |
| 52 | - | []objc.MethodDef{ | |
| 53 | - | { | |
| 54 | - | Cmd: sel_init, | |
| 55 | - | Fn: BarInit, | |
| 56 | - | }, | |
| 57 | - | }, | |
| 58 | - | ) | |
| 59 | - | if err != nil { | |
| 60 | - | panic(err) | |
| 26 | + | func ExampleClass_AddIvar() { | |
| 27 | + | type barObject struct { | |
| 28 | + | isa objc.Class | |
| 29 | + | bar int | |
| 61 | 30 | } | |
| 31 | + | var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "BarObject", 0) | |
| 32 | + | class.AddIvar("bar", int(0), "q") | |
| 33 | + | class.AddMethod(objc.RegisterName("bar"), objc.NewIMP(func(self *barObject, _cmd objc.SEL) int { | |
| 34 | + | return self.bar | |
| 35 | + | }), "q@:") | |
| 36 | + | class.AddMethod(objc.RegisterName("setBar:"), objc.NewIMP(func(self *barObject, _cmd objc.SEL, bar int) { | |
| 37 | + | self.bar = bar | |
| 38 | + | }), "v@:q") | |
| 39 | + | class.Register() | |
| 62 | 40 | ||
| 63 | - | object := objc.ID(class).Send(sel_new) | |
| 64 | - | object.Send(sel_setBar, 123) | |
| 65 | - | bar := int(object.Send(sel_bar)) | |
| 41 | + | var object = objc.ID(class).Send(objc.RegisterName("new")) | |
| 42 | + | object.Send(objc.RegisterName("setBar:"), 123) | |
| 43 | + | var bar = int(object.Send(objc.RegisterName("bar"))) | |
| 66 | 44 | fmt.Println(bar) | |
| 67 | 45 | // Output: 123 | |
| 68 | 46 | } | |
| @@ -107,7 +85,7 @@ func TestSend(t *testing.T) { | |||
| 107 | 85 | // Initialize a NSNumber | |
| 108 | 86 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | |
| 109 | 87 | // Then get that number back using the generic Send function. | |
| 110 | - | number := objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) | |
| 88 | + | var number = objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) | |
| 111 | 89 | if double != number { | |
| 112 | 90 | t.Failed() | |
| 113 | 91 | } | |
| @@ -5,7 +5,6 @@ package objc_test | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | - | "reflect" | |||
| 9 | "testing" | 8 | "testing" | ||
| 10 | 9 | ||||
| 11 | "github.com/ebitengine/purego" | 10 | "github.com/ebitengine/purego" | ||
| @@ -13,56 +12,35 @@ import ( | |||||
| 13 | ) | 12 | ) | ||
| 14 | 13 | ||||
| 15 | func ExampleAllocateClassPair() { | 14 | func ExampleAllocateClassPair() { | ||
| 16 | - | class := objc.AllocateClassPair(objc.GetClass("NSObject"), "FooObject", 0) | 15 | + | var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "FooObject", 0) |
| 17 | class.AddMethod(objc.RegisterName("run"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) { | 16 | class.AddMethod(objc.RegisterName("run"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) { | ||
| 18 | fmt.Println("Hello World!") | 17 | fmt.Println("Hello World!") | ||
| 19 | }), "v@:") | 18 | }), "v@:") | ||
| 20 | class.Register() | 19 | class.Register() | ||
| 21 | 20 | ||||
| 22 | - | fooObject := objc.ID(class).Send(objc.RegisterName("new")) | 21 | + | var fooObject = objc.ID(class).Send(objc.RegisterName("new")) |
| 23 | fooObject.Send(objc.RegisterName("run")) | 22 | fooObject.Send(objc.RegisterName("run")) | ||
| 24 | // Output: Hello World! | 23 | // Output: Hello World! | ||
| 25 | } | 24 | } | ||
| 26 | 25 | ||||
| 27 | - | func ExampleRegisterClass() { | 26 | + | func ExampleClass_AddIvar() { |
| 28 | - | var ( | 27 | + | type barObject struct { |
| 29 | - | sel_new = objc.RegisterName("new") | 28 | + | isa objc.Class |
| 30 | - | sel_init = objc.RegisterName("init") | 29 | + | bar int |
| 31 | - | sel_setBar = objc.RegisterName("setBar:") | |||
| 32 | - | sel_bar = objc.RegisterName("bar") | |||
| 33 | - | ||||
| 34 | - | BarInit = func(id objc.ID, cmd objc.SEL) objc.ID { | |||
| 35 | - | return id.SendSuper(cmd) | |||
| 36 | - | } | |||
| 37 | - | ) | |||
| 38 | - | ||||
| 39 | - | class, err := objc.RegisterClass( | |||
| 40 | - | "BarObject", | |||
| 41 | - | objc.GetClass("NSObject"), | |||
| 42 | - | []*objc.Protocol{ | |||
| 43 | - | objc.GetProtocol("NSDelegateWindow"), | |||
| 44 | - | }, | |||
| 45 | - | []objc.FieldDef{ | |||
| 46 | - | { | |||
| 47 | - | Name: "bar", | |||
| 48 | - | Type: reflect.TypeOf(int(0)), | |||
| 49 | - | Attribute: objc.ReadWrite, | |||
| 50 | - | }, | |||
| 51 | - | }, | |||
| 52 | - | []objc.MethodDef{ | |||
| 53 | - | { | |||
| 54 | - | Cmd: sel_init, | |||
| 55 | - | Fn: BarInit, | |||
| 56 | - | }, | |||
| 57 | - | }, | |||
| 58 | - | ) | |||
| 59 | - | if err != nil { | |||
| 60 | - | panic(err) | |||
| 61 | } | 30 | } | ||
| 31 | + | var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "BarObject", 0) | |||
| 32 | + | class.AddIvar("bar", int(0), "q") | |||
| 33 | + | class.AddMethod(objc.RegisterName("bar"), objc.NewIMP(func(self *barObject, _cmd objc.SEL) int { | |||
| 34 | + | return self.bar | |||
| 35 | + | }), "q@:") | |||
| 36 | + | class.AddMethod(objc.RegisterName("setBar:"), objc.NewIMP(func(self *barObject, _cmd objc.SEL, bar int) { | |||
| 37 | + | self.bar = bar | |||
| 38 | + | }), "v@:q") | |||
| 39 | + | class.Register() | |||
| 62 | 40 | ||||
| 63 | - | object := objc.ID(class).Send(sel_new) | 41 | + | var object = objc.ID(class).Send(objc.RegisterName("new")) |
| 64 | - | object.Send(sel_setBar, 123) | 42 | + | object.Send(objc.RegisterName("setBar:"), 123) |
| 65 | - | bar := int(object.Send(sel_bar)) | 43 | + | var bar = int(object.Send(objc.RegisterName("bar"))) |
| 66 | fmt.Println(bar) | 44 | fmt.Println(bar) | ||
| 67 | // Output: 123 | 45 | // Output: 123 | ||
| 68 | } | 46 | } | ||
| @@ -107,7 +85,7 @@ func TestSend(t *testing.T) { | |||||
| 107 | // Initialize a NSNumber | 85 | // Initialize a NSNumber | ||
| 108 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | 86 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | ||
| 109 | // Then get that number back using the generic Send function. | 87 | // Then get that number back using the generic Send function. | ||
| 110 | - | number := objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) | 88 | + | var number = objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) |
| 111 | if double != number { | 89 | if double != number { | ||
| 112 | t.Failed() | 90 | t.Failed() | ||
| 113 | } | 91 | } | ||