detached 13a6f78

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 Unverified4AEE18F83AFDEB23 The 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 @@
66 package objc
77
88 import (
9- "github.com/ebitengine/purego"
9+ "errors"
10+ "fmt"
1011 "math"
1112 "reflect"
13+
14+ "github.com/ebitengine/purego"
1215 )
1316
1417 //TODO: support try/catch?
@@ -24,6 +27,7 @@ var (
2427 sel_registerName func(name string) SEL
2528 class_getSuperclass func(class Class) Class
2629 class_getInstanceVariable func(class Class, name string) Ivar
30+ class_getInstanceSize func(class Class) uintptr
2731 class_addMethod func(class Class, name SEL, imp IMP, types string) bool
2832 class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
2933 class_addProtocol func(class Class, protocol *Protocol) bool
@@ -47,6 +51,7 @@ func init() {
4751 purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod")
4852 purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar")
4953 purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol")
54+ purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize")
5055 purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset")
5156 }
5257
@@ -98,10 +103,317 @@ func GetClass(name string) Class {
98103 }
99104
100105 // 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
101108 func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
102109 return objc_allocateClassPair(super, name, extraBytes)
103110 }
104111
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+
105417 // SuperClass returns the superclass of a class.
106418 // You should usually use NSObject‘s superclass method instead of this function.
107419 func (c Class) SuperClass() Class {
@@ -121,6 +433,8 @@ func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
121433 // Adding an instance variable to an existing class is not supported.
122434 // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
123435 // It takes the instance of the type of the Ivar and a string representing the type.
436+//
437+// Deprecated: use RegisterClass instead
124438 func (c Class) AddIvar(name string, ty interface{}, types string) bool {
125439 typeOf := reflect.TypeOf(ty)
126440 size := typeOf.Size()
@@ -135,6 +449,11 @@ func (c Class) AddProtocol(protocol *Protocol) bool {
135449 return class_addProtocol(c, protocol)
136450 }
137451
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+
138457 // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
139458 func (c Class) InstanceVariable(name string) Ivar {
140459 return class_getInstanceVariable(c, name)
@@ -142,6 +461,8 @@ func (c Class) InstanceVariable(name string) Ivar {
142461
143462 // Register registers a class that was allocated using AllocateClassPair.
144463 // It can now be used to make objects by sending it either alloc and init or new.
464+//
465+// Deprecated: use RegisterClass instead
145466 func (c Class) Register() {
146467 objc_registerClassPair(c)
147468 }
@@ -189,7 +510,7 @@ func NewIMP(fn interface{}) IMP {
189510 ty.In(0).Elem().Field(0).Type != reflect.TypeOf(Class(0))):
190511 fallthrough
191512 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())
193514 }
194515 return IMP(purego.NewCallback(fn))
195516 }
@@ -6,9 +6,12 @@
6 package objc6 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) SEL27 sel_registerName func(name string) SEL
25 class_getSuperclass func(class Class) Class28 class_getSuperclass func(class Class) Class
26 class_getInstanceVariable func(class Class, name string) Ivar29 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) bool31 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) bool32 class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
29 class_addProtocol func(class Class, protocol *Protocol) bool33 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 created105 // 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 fallthrough511 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 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close