objc: add RegisterClass API (#59)
adds RegisterClass function which takes a Selector interface that it uses to add methods based on the argument sent to RegisterClass. It returns an Objective-C class that matches the pointer to struct or an error if something failed. I also included an example under example/objc/main_darwin.go. * AddMethod & objc tag support Protocol This adds Selector interface which allows to convert Go method names to Objc methods. There is a handwritten parser for the objc tag which matches that of an Objc Class definition. This adds support for adding Protocols from the tag.
- commit
- 13a6f7821dfb5973706cdd8718243385b0a8e0c9
- author
- TotallyGamerJet · · 2022-11-04 13:30 UTC
- committer
- GitHub · · 2022-11-04 13:30 UTC
- parent
- 4f3e551
- signature
- GPG Unverified
4AEE18F83AFDEB23The key is unknown
2 files · +384 -2
A example/objc/main_darwin.go +61 -0
| @@ -0,0 +1,61 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package main | |
| 5 | + | ||
| 6 | + | import ( | |
| 7 | + | "fmt" | |
| 8 | + | "unsafe" | |
| 9 | + | ||
| 10 | + | "github.com/ebitengine/purego/objc" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | var ( | |
| 14 | + | sel_new = objc.RegisterName("new") | |
| 15 | + | sel_init = objc.RegisterName("init") | |
| 16 | + | sel_setBar = objc.RegisterName("setBar:") | |
| 17 | + | sel_bar = objc.RegisterName("bar") | |
| 18 | + | ) | |
| 19 | + | ||
| 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 | + | } | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | func main() { | |
| 51 | + | class, err := objc.RegisterClass(&barObject{}) | |
| 52 | + | if err != nil { | |
| 53 | + | panic(err) | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | var object = objc.ID(class).Send(sel_new) | |
| 57 | + | object.Send(sel_setBar, 123) | |
| 58 | + | var bar = int(object.Send(sel_bar)) | |
| 59 | + | fmt.Println(bar) | |
| 60 | + | // Output: 123 | |
| 61 | + | } | |
| @@ -0,0 +1,61 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package main | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "fmt" | |||
| 8 | + | "unsafe" | |||
| 9 | + | ||||
| 10 | + | "github.com/ebitengine/purego/objc" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | var ( | |||
| 14 | + | sel_new = objc.RegisterName("new") | |||
| 15 | + | sel_init = objc.RegisterName("init") | |||
| 16 | + | sel_setBar = objc.RegisterName("setBar:") | |||
| 17 | + | sel_bar = objc.RegisterName("bar") | |||
| 18 | + | ) | |||
| 19 | + | ||||
| 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 | + | } | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | func main() { | |||
| 51 | + | class, err := objc.RegisterClass(&barObject{}) | |||
| 52 | + | if err != nil { | |||
| 53 | + | panic(err) | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | var object = objc.ID(class).Send(sel_new) | |||
| 57 | + | object.Send(sel_setBar, 123) | |||
| 58 | + | var bar = int(object.Send(sel_bar)) | |||
| 59 | + | fmt.Println(bar) | |||
| 60 | + | // Output: 123 | |||
| 61 | + | } | |||
M objc/objc_runtime_darwin.go +323 -2
| @@ -6,9 +6,12 @@ | |||
| 6 | 6 | package objc | |
| 7 | 7 | ||
| 8 | 8 | import ( | |
| 9 | - | "github.com/ebitengine/purego" | |
| 9 | + | "errors" | |
| 10 | + | "fmt" | |
| 10 | 11 | "math" | |
| 11 | 12 | "reflect" | |
| 13 | + | ||
| 14 | + | "github.com/ebitengine/purego" | |
| 12 | 15 | ) | |
| 13 | 16 | ||
| 14 | 17 | //TODO: support try/catch? | |
| @@ -24,6 +27,7 @@ var ( | |||
| 24 | 27 | sel_registerName func(name string) SEL | |
| 25 | 28 | class_getSuperclass func(class Class) Class | |
| 26 | 29 | class_getInstanceVariable func(class Class, name string) Ivar | |
| 30 | + | class_getInstanceSize func(class Class) uintptr | |
| 27 | 31 | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | |
| 28 | 32 | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | |
| 29 | 33 | class_addProtocol func(class Class, protocol *Protocol) bool | |
| @@ -47,6 +51,7 @@ func init() { | |||
| 47 | 51 | purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod") | |
| 48 | 52 | purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar") | |
| 49 | 53 | purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") | |
| 54 | + | purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") | |
| 50 | 55 | purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") | |
| 51 | 56 | } | |
| 52 | 57 | ||
| @@ -98,10 +103,317 @@ func GetClass(name string) Class { | |||
| 98 | 103 | } | |
| 99 | 104 | ||
| 100 | 105 | // AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created | |
| 106 | + | // | |
| 107 | + | // Deprecated: use RegisterClass instead | |
| 101 | 108 | func AllocateClassPair(super Class, name string, extraBytes uintptr) Class { | |
| 102 | 109 | return objc_allocateClassPair(super, name, extraBytes) | |
| 103 | 110 | } | |
| 104 | 111 | ||
| 112 | + | // Selector is an interface that takes a Go method name | |
| 113 | + | // and returns the selector equivalent name. | |
| 114 | + | // If it returns a nil SEL then that method | |
| 115 | + | // is not added to the class object. | |
| 116 | + | type Selector interface { | |
| 117 | + | Selector(string) SEL | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | // TagFormatError occurs when the parser fails to parse the objc tag in a Selector object | |
| 121 | + | var TagFormatError = errors.New(`objc tag doesn't match "ClassName : SuperClassName <Protocol, ...>""`) | |
| 122 | + | ||
| 123 | + | // MismatchError occurs when the Go struct definition doesn't match that of Objective-C | |
| 124 | + | var MismatchError = errors.New("go struct doesn't match objective-c struct") | |
| 125 | + | ||
| 126 | + | // RegisterClass takes a pointer to a struct that implements the Selector interface. | |
| 127 | + | // It will register the structs fields and pointer receiver methods in the Objective-C | |
| 128 | + | // runtime using the SEL returned from Selector. Any errors that occur trying to add | |
| 129 | + | // a Method or Ivar is returned as an error. Such errors may occur in parsing or because | |
| 130 | + | // the size of the struct does not match the size in Objective-C. If no errors occur | |
| 131 | + | // then the returned Class has been registered successfully. | |
| 132 | + | // | |
| 133 | + | // The struct's first field must be of type Class and have a tag that matches the format | |
| 134 | + | // `objc:"ClassName : SuperClassName <Protocol, ...>`. This tag is equal to how the class | |
| 135 | + | // would be defined in Objective-C. | |
| 136 | + | func RegisterClass(object Selector) (Class, error) { | |
| 137 | + | ptr := reflect.TypeOf(object) | |
| 138 | + | strct := ptr.Elem() | |
| 139 | + | if strct.NumField() == 0 || strct.Field(0).Type != reflect.TypeOf(Class(0)) { | |
| 140 | + | return 0, fmt.Errorf("objc: need objc.Class as first field: %w", MismatchError) | |
| 141 | + | } | |
| 142 | + | isa := strct.Field(0) | |
| 143 | + | tag := isa.Tag.Get("objc") | |
| 144 | + | if tag == "" { | |
| 145 | + | return 0, fmt.Errorf("objc: missing objc tag: %w", TagFormatError) | |
| 146 | + | } | |
| 147 | + | // split contains the class name and super class name followed by all the Protocols | |
| 148 | + | // start with two for ClassName : SuperClassName | |
| 149 | + | var split = make([]string, 2) | |
| 150 | + | { | |
| 151 | + | // This is a simple parser for the objc tag that looks for the format | |
| 152 | + | // "ClassName : SuperClassName <Protocol, ...>" | |
| 153 | + | // It appends to the split variable with the [ClassName, SuperClassName, Protocol, ...] | |
| 154 | + | ||
| 155 | + | var i int // from tag[0:i] is whatever identifier is next | |
| 156 | + | var r rune // r is the current rune | |
| 157 | + | skipSpace := func() { | |
| 158 | + | for _, c := range tag { | |
| 159 | + | if c == ' ' { | |
| 160 | + | tag = tag[1:] | |
| 161 | + | continue | |
| 162 | + | } | |
| 163 | + | break | |
| 164 | + | } | |
| 165 | + | } | |
| 166 | + | skipSpace() | |
| 167 | + | // get ClassName | |
| 168 | + | for i, r = range tag { | |
| 169 | + | if r == ' ' || r == ':' { | |
| 170 | + | break | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | split[0] = tag[0:i] // store ClassName | |
| 174 | + | tag = tag[i:] | |
| 175 | + | ||
| 176 | + | skipSpace() | |
| 177 | + | ||
| 178 | + | // check for ':' | |
| 179 | + | if len(tag) > 0 && tag[0] != ':' { | |
| 180 | + | return 0, fmt.Errorf("objc: missing ':': %w", TagFormatError) | |
| 181 | + | } | |
| 182 | + | tag = tag[1:] // skip ':' | |
| 183 | + | skipSpace() | |
| 184 | + | ||
| 185 | + | // get SuperClassName | |
| 186 | + | for i, r = range tag { | |
| 187 | + | if r == ' ' { | |
| 188 | + | break | |
| 189 | + | } else if i+1 == len(tag) { | |
| 190 | + | // if this is the last character in the string | |
| 191 | + | // make sure to increment i so that tag[:i] | |
| 192 | + | // includes the last character | |
| 193 | + | i++ | |
| 194 | + | break | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | if len(tag) < i { | |
| 198 | + | return 0, fmt.Errorf("objc: missing SuperClassName: %w", TagFormatError) | |
| 199 | + | } | |
| 200 | + | split[1] = tag[:i] // store SuperClassName | |
| 201 | + | tag = tag[i:] // drop SuperClassName | |
| 202 | + | skipSpace() | |
| 203 | + | if len(tag) > 0 { | |
| 204 | + | if tag[0] != '<' { | |
| 205 | + | return 0, fmt.Errorf("objc: expected '<': %w", TagFormatError) | |
| 206 | + | } | |
| 207 | + | tag = tag[1:] // drop '<' | |
| 208 | + | // get Protocols | |
| 209 | + | outer: | |
| 210 | + | for { | |
| 211 | + | skipSpace() | |
| 212 | + | for i, r = range tag { | |
| 213 | + | switch r { | |
| 214 | + | case ' ': | |
| 215 | + | split = append(split, tag[:i]) | |
| 216 | + | tag = tag[i:] | |
| 217 | + | continue outer | |
| 218 | + | case ',': | |
| 219 | + | // If there is actually an identifier - add it. | |
| 220 | + | if i > 0 { | |
| 221 | + | split = append(split, tag[:i]) | |
| 222 | + | tag = tag[i:] | |
| 223 | + | } else { | |
| 224 | + | // Otherwise, drop ',' | |
| 225 | + | tag = tag[1:] | |
| 226 | + | } | |
| 227 | + | continue outer | |
| 228 | + | case '>': | |
| 229 | + | // If there is actually an identifier - add it. | |
| 230 | + | if i > 0 { | |
| 231 | + | split = append(split, tag[:i]) | |
| 232 | + | tag = tag[i:] | |
| 233 | + | } | |
| 234 | + | break outer | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | return 0, fmt.Errorf("objc: expected '>': %w", TagFormatError) | |
| 238 | + | } | |
| 239 | + | } | |
| 240 | + | } | |
| 241 | + | class := objc_allocateClassPair(GetClass(split[1]), split[0], 0) | |
| 242 | + | if class == 0 { | |
| 243 | + | return 0, fmt.Errorf("objc: failed to create class with name '%s'", split[0]) | |
| 244 | + | } | |
| 245 | + | if len(split) > 2 { | |
| 246 | + | // Add Protocols | |
| 247 | + | for _, n := range split[2:] { | |
| 248 | + | succeed := class.AddProtocol(GetProtocol(n)) | |
| 249 | + | if !succeed { | |
| 250 | + | return 0, fmt.Errorf("objc: couldn't add Protocol %s", n) | |
| 251 | + | } | |
| 252 | + | } | |
| 253 | + | } | |
| 254 | + | // Add exported methods based on the selectors returned from Selector(string) SEL | |
| 255 | + | for i := 0; i < ptr.NumMethod(); i++ { | |
| 256 | + | met := ptr.Method(i) | |
| 257 | + | // we know this method is the interface one since RegisterClass | |
| 258 | + | // requires that the struct implement Selector. | |
| 259 | + | if met.Name == "Selector" { | |
| 260 | + | continue | |
| 261 | + | } | |
| 262 | + | sel := object.Selector(met.Name) | |
| 263 | + | if sel == 0 { | |
| 264 | + | continue | |
| 265 | + | } | |
| 266 | + | fn := met.Func.Interface() | |
| 267 | + | imp, err := func() (imp IMP, err error) { | |
| 268 | + | defer func() { | |
| 269 | + | if r := recover(); r != nil { | |
| 270 | + | err = fmt.Errorf("objc: failed to create IMP: %s", r) | |
| 271 | + | } | |
| 272 | + | }() | |
| 273 | + | return NewIMP(fn), nil | |
| 274 | + | }() | |
| 275 | + | if err != nil { | |
| 276 | + | return 0, fmt.Errorf("objc: couldn't add Method %s: %w", met.Name, err) | |
| 277 | + | } | |
| 278 | + | succeed := class.AddMethod(sel, imp, encodeFunc(fn)) | |
| 279 | + | if !succeed { | |
| 280 | + | return 0, fmt.Errorf("objc: couldn't add Method %s", met.Name) | |
| 281 | + | } | |
| 282 | + | } | |
| 283 | + | // Add Ivars | |
| 284 | + | // Start at 1 because we skip the class object which is first | |
| 285 | + | for i := 1; i < strct.NumField(); i++ { | |
| 286 | + | f := strct.Field(i) | |
| 287 | + | size := f.Type.Size() | |
| 288 | + | alignment := uint8(math.Log2(float64(f.Type.Align()))) | |
| 289 | + | succeed := class_addIvar(class, f.Name, size, alignment, encodeType(f.Type)) | |
| 290 | + | if !succeed { | |
| 291 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s", f.Name) | |
| 292 | + | } | |
| 293 | + | if offset := class.InstanceVariable(f.Name).Offset(); offset != f.Offset { | |
| 294 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s", f.Name) | |
| 295 | + | } | |
| 296 | + | } | |
| 297 | + | objc_registerClassPair(class) | |
| 298 | + | if size1, size2 := class.InstanceSize(), strct.Size(); size1 != size2 { | |
| 299 | + | return 0, fmt.Errorf("objc: sizes don't match %d != %d: %w", size1, size2, MismatchError) | |
| 300 | + | } | |
| 301 | + | return class, nil | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | const ( | |
| 305 | + | encId = "@" | |
| 306 | + | encClass = "#" | |
| 307 | + | encSelector = ":" | |
| 308 | + | encChar = "c" | |
| 309 | + | encUChar = "C" | |
| 310 | + | encShort = "s" | |
| 311 | + | encUShort = "S" | |
| 312 | + | encInt = "i" | |
| 313 | + | encUInt = "I" | |
| 314 | + | encLong = "l" | |
| 315 | + | encULong = "L" | |
| 316 | + | encFloat = "f" | |
| 317 | + | encDouble = "d" | |
| 318 | + | encBool = "B" | |
| 319 | + | encVoid = "v" | |
| 320 | + | encPtr = "^" | |
| 321 | + | encCharPtr = "*" | |
| 322 | + | encStructBegin = "{" | |
| 323 | + | encStructEnd = "}" | |
| 324 | + | ) | |
| 325 | + | ||
| 326 | + | // encodeType returns a string representing a type as if it was given to @encode(typ) | |
| 327 | + | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | |
| 328 | + | func encodeType(typ reflect.Type) string { | |
| 329 | + | switch typ { | |
| 330 | + | case reflect.TypeOf(Class(0)): | |
| 331 | + | return encClass | |
| 332 | + | case reflect.TypeOf(ID(0)): | |
| 333 | + | return encId | |
| 334 | + | case reflect.TypeOf(SEL(0)): | |
| 335 | + | return encSelector | |
| 336 | + | } | |
| 337 | + | ||
| 338 | + | kind := typ.Kind() | |
| 339 | + | switch kind { | |
| 340 | + | case reflect.Bool: | |
| 341 | + | return encBool | |
| 342 | + | case reflect.Int: | |
| 343 | + | return encLong | |
| 344 | + | case reflect.Int8: | |
| 345 | + | return encChar | |
| 346 | + | case reflect.Int16: | |
| 347 | + | return encShort | |
| 348 | + | case reflect.Int32: | |
| 349 | + | return encInt | |
| 350 | + | case reflect.Int64: | |
| 351 | + | return encULong | |
| 352 | + | case reflect.Uint: | |
| 353 | + | return encULong | |
| 354 | + | case reflect.Uint8: | |
| 355 | + | return encUChar | |
| 356 | + | case reflect.Uint16: | |
| 357 | + | return encUShort | |
| 358 | + | case reflect.Uint32: | |
| 359 | + | return encUInt | |
| 360 | + | case reflect.Uint64: | |
| 361 | + | return encULong | |
| 362 | + | case reflect.Uintptr: | |
| 363 | + | return encPtr | |
| 364 | + | case reflect.Float32: | |
| 365 | + | return encFloat | |
| 366 | + | case reflect.Float64: | |
| 367 | + | return encDouble | |
| 368 | + | case reflect.Ptr: | |
| 369 | + | return encPtr + encodeType(typ.Elem()) | |
| 370 | + | case reflect.Struct: | |
| 371 | + | var encoding = encStructBegin | |
| 372 | + | encoding += typ.Name() | |
| 373 | + | encoding += "=" | |
| 374 | + | for i := 0; i < typ.NumField(); i++ { | |
| 375 | + | f := typ.Field(i) | |
| 376 | + | encoding += encodeType(f.Type) | |
| 377 | + | } | |
| 378 | + | encoding = encStructEnd | |
| 379 | + | return encoding | |
| 380 | + | case reflect.String: | |
| 381 | + | return encCharPtr | |
| 382 | + | } | |
| 383 | + | ||
| 384 | + | panic(fmt.Sprintf("objc: unhandled/invalid kind %v typed %v", kind, typ)) | |
| 385 | + | } | |
| 386 | + | ||
| 387 | + | // encodeFunc returns a functions type as if it was given to @encode(fn) | |
| 388 | + | func encodeFunc(fn interface{}) string { | |
| 389 | + | typ := reflect.TypeOf(fn) | |
| 390 | + | if typ.Kind() != reflect.Func { | |
| 391 | + | panic("objc: not a func") | |
| 392 | + | } | |
| 393 | + | ||
| 394 | + | encoding := "" | |
| 395 | + | switch typ.NumOut() { | |
| 396 | + | case 0: | |
| 397 | + | encoding += encVoid | |
| 398 | + | case 1: | |
| 399 | + | encoding += encodeType(typ.Out(0)) | |
| 400 | + | default: | |
| 401 | + | panic("objc: too many output parameters") | |
| 402 | + | } | |
| 403 | + | ||
| 404 | + | if typ.NumIn() == 0 { | |
| 405 | + | panic("objc: func doesn't take ID and SEL as its first two parameters") | |
| 406 | + | } | |
| 407 | + | ||
| 408 | + | encoding += encodeType(typ.In(0)) | |
| 409 | + | encoding += encSelector | |
| 410 | + | ||
| 411 | + | for i := 1; i < typ.NumIn(); i++ { | |
| 412 | + | encoding += encodeType(typ.In(i)) | |
| 413 | + | } | |
| 414 | + | return encoding | |
| 415 | + | } | |
| 416 | + | ||
| 105 | 417 | // SuperClass returns the superclass of a class. | |
| 106 | 418 | // You should usually use NSObject‘s superclass method instead of this function. | |
| 107 | 419 | func (c Class) SuperClass() Class { | |
| @@ -121,6 +433,8 @@ func (c Class) AddMethod(name SEL, imp IMP, types string) bool { | |||
| 121 | 433 | // Adding an instance variable to an existing class is not supported. | |
| 122 | 434 | // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. | |
| 123 | 435 | // It takes the instance of the type of the Ivar and a string representing the type. | |
| 436 | + | // | |
| 437 | + | // Deprecated: use RegisterClass instead | |
| 124 | 438 | func (c Class) AddIvar(name string, ty interface{}, types string) bool { | |
| 125 | 439 | typeOf := reflect.TypeOf(ty) | |
| 126 | 440 | size := typeOf.Size() | |
| @@ -135,6 +449,11 @@ func (c Class) AddProtocol(protocol *Protocol) bool { | |||
| 135 | 449 | return class_addProtocol(c, protocol) | |
| 136 | 450 | } | |
| 137 | 451 | ||
| 452 | + | // InstanceSize returns the size in bytes of instances of the class or 0 if cls is nil | |
| 453 | + | func (c Class) InstanceSize() uintptr { | |
| 454 | + | return class_getInstanceSize(c) | |
| 455 | + | } | |
| 456 | + | ||
| 138 | 457 | // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name. | |
| 139 | 458 | func (c Class) InstanceVariable(name string) Ivar { | |
| 140 | 459 | return class_getInstanceVariable(c, name) | |
| @@ -142,6 +461,8 @@ func (c Class) InstanceVariable(name string) Ivar { | |||
| 142 | 461 | ||
| 143 | 462 | // Register registers a class that was allocated using AllocateClassPair. | |
| 144 | 463 | // It can now be used to make objects by sending it either alloc and init or new. | |
| 464 | + | // | |
| 465 | + | // Deprecated: use RegisterClass instead | |
| 145 | 466 | func (c Class) Register() { | |
| 146 | 467 | objc_registerClassPair(c) | |
| 147 | 468 | } | |
| @@ -189,7 +510,7 @@ func NewIMP(fn interface{}) IMP { | |||
| 189 | 510 | ty.In(0).Elem().Field(0).Type != reflect.TypeOf(Class(0))): | |
| 190 | 511 | fallthrough | |
| 191 | 512 | case ty.In(1).Kind() != reflect.Uintptr: | |
| 192 | - | panic("objc: NewIMP must take a (id, SEL) as its first two arguments") | |
| 513 | + | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | |
| 193 | 514 | } | |
| 194 | 515 | return IMP(purego.NewCallback(fn)) | |
| 195 | 516 | } | |
| @@ -6,9 +6,12 @@ | |||||
| 6 | package objc | 6 | package objc | ||
| 7 | 7 | ||||
| 8 | import ( | 8 | import ( | ||
| 9 | - | "github.com/ebitengine/purego" | 9 | + | "errors" |
| 10 | + | "fmt" | |||
| 10 | "math" | 11 | "math" | ||
| 11 | "reflect" | 12 | "reflect" | ||
| 13 | + | ||||
| 14 | + | "github.com/ebitengine/purego" | |||
| 12 | ) | 15 | ) | ||
| 13 | 16 | ||||
| 14 | //TODO: support try/catch? | 17 | //TODO: support try/catch? | ||
| @@ -24,6 +27,7 @@ var ( | |||||
| 24 | sel_registerName func(name string) SEL | 27 | sel_registerName func(name string) SEL | ||
| 25 | class_getSuperclass func(class Class) Class | 28 | class_getSuperclass func(class Class) Class | ||
| 26 | class_getInstanceVariable func(class Class, name string) Ivar | 29 | class_getInstanceVariable func(class Class, name string) Ivar | ||
| 30 | + | class_getInstanceSize func(class Class) uintptr | |||
| 27 | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | 31 | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | ||
| 28 | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | 32 | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | ||
| 29 | class_addProtocol func(class Class, protocol *Protocol) bool | 33 | class_addProtocol func(class Class, protocol *Protocol) bool | ||
| @@ -47,6 +51,7 @@ func init() { | |||||
| 47 | purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod") | 51 | purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod") | ||
| 48 | purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar") | 52 | purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar") | ||
| 49 | purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") | 53 | purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") | ||
| 54 | + | purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") | |||
| 50 | purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") | 55 | purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") | ||
| 51 | } | 56 | } | ||
| 52 | 57 | ||||
| @@ -98,10 +103,317 @@ func GetClass(name string) Class { | |||||
| 98 | } | 103 | } | ||
| 99 | 104 | ||||
| 100 | // AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created | 105 | // AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created | ||
| 106 | + | // | |||
| 107 | + | // Deprecated: use RegisterClass instead | |||
| 101 | func AllocateClassPair(super Class, name string, extraBytes uintptr) Class { | 108 | func AllocateClassPair(super Class, name string, extraBytes uintptr) Class { | ||
| 102 | return objc_allocateClassPair(super, name, extraBytes) | 109 | return objc_allocateClassPair(super, name, extraBytes) | ||
| 103 | } | 110 | } | ||
| 104 | 111 | ||||
| 112 | + | // Selector is an interface that takes a Go method name | |||
| 113 | + | // and returns the selector equivalent name. | |||
| 114 | + | // If it returns a nil SEL then that method | |||
| 115 | + | // is not added to the class object. | |||
| 116 | + | type Selector interface { | |||
| 117 | + | Selector(string) SEL | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | // TagFormatError occurs when the parser fails to parse the objc tag in a Selector object | |||
| 121 | + | var TagFormatError = errors.New(`objc tag doesn't match "ClassName : SuperClassName <Protocol, ...>""`) | |||
| 122 | + | ||||
| 123 | + | // MismatchError occurs when the Go struct definition doesn't match that of Objective-C | |||
| 124 | + | var MismatchError = errors.New("go struct doesn't match objective-c struct") | |||
| 125 | + | ||||
| 126 | + | // RegisterClass takes a pointer to a struct that implements the Selector interface. | |||
| 127 | + | // It will register the structs fields and pointer receiver methods in the Objective-C | |||
| 128 | + | // runtime using the SEL returned from Selector. Any errors that occur trying to add | |||
| 129 | + | // a Method or Ivar is returned as an error. Such errors may occur in parsing or because | |||
| 130 | + | // the size of the struct does not match the size in Objective-C. If no errors occur | |||
| 131 | + | // then the returned Class has been registered successfully. | |||
| 132 | + | // | |||
| 133 | + | // The struct's first field must be of type Class and have a tag that matches the format | |||
| 134 | + | // `objc:"ClassName : SuperClassName <Protocol, ...>`. This tag is equal to how the class | |||
| 135 | + | // would be defined in Objective-C. | |||
| 136 | + | func RegisterClass(object Selector) (Class, error) { | |||
| 137 | + | ptr := reflect.TypeOf(object) | |||
| 138 | + | strct := ptr.Elem() | |||
| 139 | + | if strct.NumField() == 0 || strct.Field(0).Type != reflect.TypeOf(Class(0)) { | |||
| 140 | + | return 0, fmt.Errorf("objc: need objc.Class as first field: %w", MismatchError) | |||
| 141 | + | } | |||
| 142 | + | isa := strct.Field(0) | |||
| 143 | + | tag := isa.Tag.Get("objc") | |||
| 144 | + | if tag == "" { | |||
| 145 | + | return 0, fmt.Errorf("objc: missing objc tag: %w", TagFormatError) | |||
| 146 | + | } | |||
| 147 | + | // split contains the class name and super class name followed by all the Protocols | |||
| 148 | + | // start with two for ClassName : SuperClassName | |||
| 149 | + | var split = make([]string, 2) | |||
| 150 | + | { | |||
| 151 | + | // This is a simple parser for the objc tag that looks for the format | |||
| 152 | + | // "ClassName : SuperClassName <Protocol, ...>" | |||
| 153 | + | // It appends to the split variable with the [ClassName, SuperClassName, Protocol, ...] | |||
| 154 | + | ||||
| 155 | + | var i int // from tag[0:i] is whatever identifier is next | |||
| 156 | + | var r rune // r is the current rune | |||
| 157 | + | skipSpace := func() { | |||
| 158 | + | for _, c := range tag { | |||
| 159 | + | if c == ' ' { | |||
| 160 | + | tag = tag[1:] | |||
| 161 | + | continue | |||
| 162 | + | } | |||
| 163 | + | break | |||
| 164 | + | } | |||
| 165 | + | } | |||
| 166 | + | skipSpace() | |||
| 167 | + | // get ClassName | |||
| 168 | + | for i, r = range tag { | |||
| 169 | + | if r == ' ' || r == ':' { | |||
| 170 | + | break | |||
| 171 | + | } | |||
| 172 | + | } | |||
| 173 | + | split[0] = tag[0:i] // store ClassName | |||
| 174 | + | tag = tag[i:] | |||
| 175 | + | ||||
| 176 | + | skipSpace() | |||
| 177 | + | ||||
| 178 | + | // check for ':' | |||
| 179 | + | if len(tag) > 0 && tag[0] != ':' { | |||
| 180 | + | return 0, fmt.Errorf("objc: missing ':': %w", TagFormatError) | |||
| 181 | + | } | |||
| 182 | + | tag = tag[1:] // skip ':' | |||
| 183 | + | skipSpace() | |||
| 184 | + | ||||
| 185 | + | // get SuperClassName | |||
| 186 | + | for i, r = range tag { | |||
| 187 | + | if r == ' ' { | |||
| 188 | + | break | |||
| 189 | + | } else if i+1 == len(tag) { | |||
| 190 | + | // if this is the last character in the string | |||
| 191 | + | // make sure to increment i so that tag[:i] | |||
| 192 | + | // includes the last character | |||
| 193 | + | i++ | |||
| 194 | + | break | |||
| 195 | + | } | |||
| 196 | + | } | |||
| 197 | + | if len(tag) < i { | |||
| 198 | + | return 0, fmt.Errorf("objc: missing SuperClassName: %w", TagFormatError) | |||
| 199 | + | } | |||
| 200 | + | split[1] = tag[:i] // store SuperClassName | |||
| 201 | + | tag = tag[i:] // drop SuperClassName | |||
| 202 | + | skipSpace() | |||
| 203 | + | if len(tag) > 0 { | |||
| 204 | + | if tag[0] != '<' { | |||
| 205 | + | return 0, fmt.Errorf("objc: expected '<': %w", TagFormatError) | |||
| 206 | + | } | |||
| 207 | + | tag = tag[1:] // drop '<' | |||
| 208 | + | // get Protocols | |||
| 209 | + | outer: | |||
| 210 | + | for { | |||
| 211 | + | skipSpace() | |||
| 212 | + | for i, r = range tag { | |||
| 213 | + | switch r { | |||
| 214 | + | case ' ': | |||
| 215 | + | split = append(split, tag[:i]) | |||
| 216 | + | tag = tag[i:] | |||
| 217 | + | continue outer | |||
| 218 | + | case ',': | |||
| 219 | + | // If there is actually an identifier - add it. | |||
| 220 | + | if i > 0 { | |||
| 221 | + | split = append(split, tag[:i]) | |||
| 222 | + | tag = tag[i:] | |||
| 223 | + | } else { | |||
| 224 | + | // Otherwise, drop ',' | |||
| 225 | + | tag = tag[1:] | |||
| 226 | + | } | |||
| 227 | + | continue outer | |||
| 228 | + | case '>': | |||
| 229 | + | // If there is actually an identifier - add it. | |||
| 230 | + | if i > 0 { | |||
| 231 | + | split = append(split, tag[:i]) | |||
| 232 | + | tag = tag[i:] | |||
| 233 | + | } | |||
| 234 | + | break outer | |||
| 235 | + | } | |||
| 236 | + | } | |||
| 237 | + | return 0, fmt.Errorf("objc: expected '>': %w", TagFormatError) | |||
| 238 | + | } | |||
| 239 | + | } | |||
| 240 | + | } | |||
| 241 | + | class := objc_allocateClassPair(GetClass(split[1]), split[0], 0) | |||
| 242 | + | if class == 0 { | |||
| 243 | + | return 0, fmt.Errorf("objc: failed to create class with name '%s'", split[0]) | |||
| 244 | + | } | |||
| 245 | + | if len(split) > 2 { | |||
| 246 | + | // Add Protocols | |||
| 247 | + | for _, n := range split[2:] { | |||
| 248 | + | succeed := class.AddProtocol(GetProtocol(n)) | |||
| 249 | + | if !succeed { | |||
| 250 | + | return 0, fmt.Errorf("objc: couldn't add Protocol %s", n) | |||
| 251 | + | } | |||
| 252 | + | } | |||
| 253 | + | } | |||
| 254 | + | // Add exported methods based on the selectors returned from Selector(string) SEL | |||
| 255 | + | for i := 0; i < ptr.NumMethod(); i++ { | |||
| 256 | + | met := ptr.Method(i) | |||
| 257 | + | // we know this method is the interface one since RegisterClass | |||
| 258 | + | // requires that the struct implement Selector. | |||
| 259 | + | if met.Name == "Selector" { | |||
| 260 | + | continue | |||
| 261 | + | } | |||
| 262 | + | sel := object.Selector(met.Name) | |||
| 263 | + | if sel == 0 { | |||
| 264 | + | continue | |||
| 265 | + | } | |||
| 266 | + | fn := met.Func.Interface() | |||
| 267 | + | imp, err := func() (imp IMP, err error) { | |||
| 268 | + | defer func() { | |||
| 269 | + | if r := recover(); r != nil { | |||
| 270 | + | err = fmt.Errorf("objc: failed to create IMP: %s", r) | |||
| 271 | + | } | |||
| 272 | + | }() | |||
| 273 | + | return NewIMP(fn), nil | |||
| 274 | + | }() | |||
| 275 | + | if err != nil { | |||
| 276 | + | return 0, fmt.Errorf("objc: couldn't add Method %s: %w", met.Name, err) | |||
| 277 | + | } | |||
| 278 | + | succeed := class.AddMethod(sel, imp, encodeFunc(fn)) | |||
| 279 | + | if !succeed { | |||
| 280 | + | return 0, fmt.Errorf("objc: couldn't add Method %s", met.Name) | |||
| 281 | + | } | |||
| 282 | + | } | |||
| 283 | + | // Add Ivars | |||
| 284 | + | // Start at 1 because we skip the class object which is first | |||
| 285 | + | for i := 1; i < strct.NumField(); i++ { | |||
| 286 | + | f := strct.Field(i) | |||
| 287 | + | size := f.Type.Size() | |||
| 288 | + | alignment := uint8(math.Log2(float64(f.Type.Align()))) | |||
| 289 | + | succeed := class_addIvar(class, f.Name, size, alignment, encodeType(f.Type)) | |||
| 290 | + | if !succeed { | |||
| 291 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s", f.Name) | |||
| 292 | + | } | |||
| 293 | + | if offset := class.InstanceVariable(f.Name).Offset(); offset != f.Offset { | |||
| 294 | + | return 0, fmt.Errorf("objc: couldn't add Ivar %s", f.Name) | |||
| 295 | + | } | |||
| 296 | + | } | |||
| 297 | + | objc_registerClassPair(class) | |||
| 298 | + | if size1, size2 := class.InstanceSize(), strct.Size(); size1 != size2 { | |||
| 299 | + | return 0, fmt.Errorf("objc: sizes don't match %d != %d: %w", size1, size2, MismatchError) | |||
| 300 | + | } | |||
| 301 | + | return class, nil | |||
| 302 | + | } | |||
| 303 | + | ||||
| 304 | + | const ( | |||
| 305 | + | encId = "@" | |||
| 306 | + | encClass = "#" | |||
| 307 | + | encSelector = ":" | |||
| 308 | + | encChar = "c" | |||
| 309 | + | encUChar = "C" | |||
| 310 | + | encShort = "s" | |||
| 311 | + | encUShort = "S" | |||
| 312 | + | encInt = "i" | |||
| 313 | + | encUInt = "I" | |||
| 314 | + | encLong = "l" | |||
| 315 | + | encULong = "L" | |||
| 316 | + | encFloat = "f" | |||
| 317 | + | encDouble = "d" | |||
| 318 | + | encBool = "B" | |||
| 319 | + | encVoid = "v" | |||
| 320 | + | encPtr = "^" | |||
| 321 | + | encCharPtr = "*" | |||
| 322 | + | encStructBegin = "{" | |||
| 323 | + | encStructEnd = "}" | |||
| 324 | + | ) | |||
| 325 | + | ||||
| 326 | + | // encodeType returns a string representing a type as if it was given to @encode(typ) | |||
| 327 | + | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | |||
| 328 | + | func encodeType(typ reflect.Type) string { | |||
| 329 | + | switch typ { | |||
| 330 | + | case reflect.TypeOf(Class(0)): | |||
| 331 | + | return encClass | |||
| 332 | + | case reflect.TypeOf(ID(0)): | |||
| 333 | + | return encId | |||
| 334 | + | case reflect.TypeOf(SEL(0)): | |||
| 335 | + | return encSelector | |||
| 336 | + | } | |||
| 337 | + | ||||
| 338 | + | kind := typ.Kind() | |||
| 339 | + | switch kind { | |||
| 340 | + | case reflect.Bool: | |||
| 341 | + | return encBool | |||
| 342 | + | case reflect.Int: | |||
| 343 | + | return encLong | |||
| 344 | + | case reflect.Int8: | |||
| 345 | + | return encChar | |||
| 346 | + | case reflect.Int16: | |||
| 347 | + | return encShort | |||
| 348 | + | case reflect.Int32: | |||
| 349 | + | return encInt | |||
| 350 | + | case reflect.Int64: | |||
| 351 | + | return encULong | |||
| 352 | + | case reflect.Uint: | |||
| 353 | + | return encULong | |||
| 354 | + | case reflect.Uint8: | |||
| 355 | + | return encUChar | |||
| 356 | + | case reflect.Uint16: | |||
| 357 | + | return encUShort | |||
| 358 | + | case reflect.Uint32: | |||
| 359 | + | return encUInt | |||
| 360 | + | case reflect.Uint64: | |||
| 361 | + | return encULong | |||
| 362 | + | case reflect.Uintptr: | |||
| 363 | + | return encPtr | |||
| 364 | + | case reflect.Float32: | |||
| 365 | + | return encFloat | |||
| 366 | + | case reflect.Float64: | |||
| 367 | + | return encDouble | |||
| 368 | + | case reflect.Ptr: | |||
| 369 | + | return encPtr + encodeType(typ.Elem()) | |||
| 370 | + | case reflect.Struct: | |||
| 371 | + | var encoding = encStructBegin | |||
| 372 | + | encoding += typ.Name() | |||
| 373 | + | encoding += "=" | |||
| 374 | + | for i := 0; i < typ.NumField(); i++ { | |||
| 375 | + | f := typ.Field(i) | |||
| 376 | + | encoding += encodeType(f.Type) | |||
| 377 | + | } | |||
| 378 | + | encoding = encStructEnd | |||
| 379 | + | return encoding | |||
| 380 | + | case reflect.String: | |||
| 381 | + | return encCharPtr | |||
| 382 | + | } | |||
| 383 | + | ||||
| 384 | + | panic(fmt.Sprintf("objc: unhandled/invalid kind %v typed %v", kind, typ)) | |||
| 385 | + | } | |||
| 386 | + | ||||
| 387 | + | // encodeFunc returns a functions type as if it was given to @encode(fn) | |||
| 388 | + | func encodeFunc(fn interface{}) string { | |||
| 389 | + | typ := reflect.TypeOf(fn) | |||
| 390 | + | if typ.Kind() != reflect.Func { | |||
| 391 | + | panic("objc: not a func") | |||
| 392 | + | } | |||
| 393 | + | ||||
| 394 | + | encoding := "" | |||
| 395 | + | switch typ.NumOut() { | |||
| 396 | + | case 0: | |||
| 397 | + | encoding += encVoid | |||
| 398 | + | case 1: | |||
| 399 | + | encoding += encodeType(typ.Out(0)) | |||
| 400 | + | default: | |||
| 401 | + | panic("objc: too many output parameters") | |||
| 402 | + | } | |||
| 403 | + | ||||
| 404 | + | if typ.NumIn() == 0 { | |||
| 405 | + | panic("objc: func doesn't take ID and SEL as its first two parameters") | |||
| 406 | + | } | |||
| 407 | + | ||||
| 408 | + | encoding += encodeType(typ.In(0)) | |||
| 409 | + | encoding += encSelector | |||
| 410 | + | ||||
| 411 | + | for i := 1; i < typ.NumIn(); i++ { | |||
| 412 | + | encoding += encodeType(typ.In(i)) | |||
| 413 | + | } | |||
| 414 | + | return encoding | |||
| 415 | + | } | |||
| 416 | + | ||||
| 105 | // SuperClass returns the superclass of a class. | 417 | // SuperClass returns the superclass of a class. | ||
| 106 | // You should usually use NSObject‘s superclass method instead of this function. | 418 | // You should usually use NSObject‘s superclass method instead of this function. | ||
| 107 | func (c Class) SuperClass() Class { | 419 | func (c Class) SuperClass() Class { | ||
| @@ -121,6 +433,8 @@ func (c Class) AddMethod(name SEL, imp IMP, types string) bool { | |||||
| 121 | // Adding an instance variable to an existing class is not supported. | 433 | // Adding an instance variable to an existing class is not supported. | ||
| 122 | // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. | 434 | // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. | ||
| 123 | // It takes the instance of the type of the Ivar and a string representing the type. | 435 | // It takes the instance of the type of the Ivar and a string representing the type. | ||
| 436 | + | // | |||
| 437 | + | // Deprecated: use RegisterClass instead | |||
| 124 | func (c Class) AddIvar(name string, ty interface{}, types string) bool { | 438 | func (c Class) AddIvar(name string, ty interface{}, types string) bool { | ||
| 125 | typeOf := reflect.TypeOf(ty) | 439 | typeOf := reflect.TypeOf(ty) | ||
| 126 | size := typeOf.Size() | 440 | size := typeOf.Size() | ||
| @@ -135,6 +449,11 @@ func (c Class) AddProtocol(protocol *Protocol) bool { | |||||
| 135 | return class_addProtocol(c, protocol) | 449 | return class_addProtocol(c, protocol) | ||
| 136 | } | 450 | } | ||
| 137 | 451 | ||||
| 452 | + | // InstanceSize returns the size in bytes of instances of the class or 0 if cls is nil | |||
| 453 | + | func (c Class) InstanceSize() uintptr { | |||
| 454 | + | return class_getInstanceSize(c) | |||
| 455 | + | } | |||
| 456 | + | ||||
| 138 | // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name. | 457 | // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name. | ||
| 139 | func (c Class) InstanceVariable(name string) Ivar { | 458 | func (c Class) InstanceVariable(name string) Ivar { | ||
| 140 | return class_getInstanceVariable(c, name) | 459 | return class_getInstanceVariable(c, name) | ||
| @@ -142,6 +461,8 @@ func (c Class) InstanceVariable(name string) Ivar { | |||||
| 142 | 461 | ||||
| 143 | // Register registers a class that was allocated using AllocateClassPair. | 462 | // Register registers a class that was allocated using AllocateClassPair. | ||
| 144 | // It can now be used to make objects by sending it either alloc and init or new. | 463 | // It can now be used to make objects by sending it either alloc and init or new. | ||
| 464 | + | // | |||
| 465 | + | // Deprecated: use RegisterClass instead | |||
| 145 | func (c Class) Register() { | 466 | func (c Class) Register() { | ||
| 146 | objc_registerClassPair(c) | 467 | objc_registerClassPair(c) | ||
| 147 | } | 468 | } | ||
| @@ -189,7 +510,7 @@ func NewIMP(fn interface{}) IMP { | |||||
| 189 | ty.In(0).Elem().Field(0).Type != reflect.TypeOf(Class(0))): | 510 | ty.In(0).Elem().Field(0).Type != reflect.TypeOf(Class(0))): | ||
| 190 | fallthrough | 511 | fallthrough | ||
| 191 | case ty.In(1).Kind() != reflect.Uintptr: | 512 | case ty.In(1).Kind() != reflect.Uintptr: | ||
| 192 | - | panic("objc: NewIMP must take a (id, SEL) as its first two arguments") | 513 | + | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) |
| 193 | } | 514 | } | ||
| 194 | return IMP(purego.NewCallback(fn)) | 515 | return IMP(purego.NewCallback(fn)) | ||
| 195 | } | 516 | } | ||