detached 9246c5c

objc: generic Send and SendSuper (#90)

ID.Send and ID.SendSuper can only return integer types (such as ID). These two new functions can take any return type as long as it is a type that RegisterFunc supports.

Closes #87
commit
9246c5c809fa87320d2791d4f85e4fa870afc535
author
TotallyGamerJet · · 2023-01-09 04:51 UTC
committer
GitHub · · 2023-01-09 04:51 UTC
parent
6d5fb77
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
2 files · +45 -3
M objc/objc_runtime_darwin.go +31 -3
@@ -18,7 +18,9 @@ import (
1818 //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
1919
2020 var (
21+ objc_msgSend_fn uintptr
2122 objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
23+ objc_msgSendSuper2_fn uintptr
2224 objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
2325 objc_getClass func(name string) Class
2426 objc_getProtocol func(name string) *Protocol
@@ -37,9 +39,13 @@ var (
3739
3840 func init() {
3941 objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
40-
41- purego.RegisterLibFunc(&objc_msgSend, objc, "objc_msgSend")
42- purego.RegisterLibFunc(&objc_msgSendSuper2, objc, "objc_msgSendSuper2")
42+ if err := purego.Dlerror(); err != "" {
43+ panic("objc: " + err)
44+ }
45+ objc_msgSend_fn = purego.Dlsym(objc, "objc_msgSend")
46+ purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
47+ objc_msgSendSuper2_fn = purego.Dlsym(objc, "objc_msgSendSuper2")
48+ purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn)
4349 purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
4450 purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
4551 purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
@@ -70,6 +76,15 @@ func (id ID) Send(sel SEL, args ...interface{}) ID {
7076 return objc_msgSend(id, sel, args...)
7177 }
7278
79+// Send is a convenience method for sending messages to objects that can return any type.
80+// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
81+// It is best to cache the result of RegisterName.
82+func Send[T any](id ID, sel SEL, args ...any) T {
83+ var fn func(id ID, sel SEL, args ...any) T
84+ purego.RegisterFunc(&fn, objc_msgSend_fn)
85+ return fn(id, sel, args...)
86+}
87+
7388 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
7489 // as the receiver of a message. It specifies the class definition of the particular superclass that should
7590 // be messaged.
@@ -89,6 +104,19 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
89104 return objc_msgSendSuper2(super, sel, args...)
90105 }
91106
107+// SendSuper is a convenience method for sending message to object's super that can return any type.
108+// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
109+// It is best to cache the result of RegisterName.
110+func SendSuper[T any](id ID, sel SEL, args ...any) T {
111+ var super = &objc_super{
112+ receiver: id,
113+ superClass: id.Class(),
114+ }
115+ var fn func(objcSuper *objc_super, sel SEL, args ...any) T
116+ purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
117+ return fn(super, sel, args...)
118+}
119+
92120 // SEL is an opaque type that represents a method selector
93121 type SEL uintptr
94122
@@ -18,7 +18,9 @@ import (
18 //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime18 //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
19 19
20 var (20 var (
21+ objc_msgSend_fn uintptr
21 objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID22 objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
23+ objc_msgSendSuper2_fn uintptr
22 objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID24 objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
23 objc_getClass func(name string) Class25 objc_getClass func(name string) Class
24 objc_getProtocol func(name string) *Protocol26 objc_getProtocol func(name string) *Protocol
@@ -37,9 +39,13 @@ var (
37 39
38 func init() {40 func init() {
39 objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)41 objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
40-42+ if err := purego.Dlerror(); err != "" {
41- purego.RegisterLibFunc(&objc_msgSend, objc, "objc_msgSend")43+ panic("objc: " + err)
42- purego.RegisterLibFunc(&objc_msgSendSuper2, objc, "objc_msgSendSuper2")44+ }
45+ objc_msgSend_fn = purego.Dlsym(objc, "objc_msgSend")
46+ purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
47+ objc_msgSendSuper2_fn = purego.Dlsym(objc, "objc_msgSendSuper2")
48+ purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn)
43 purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")49 purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
44 purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")50 purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
45 purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")51 purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
@@ -70,6 +76,15 @@ func (id ID) Send(sel SEL, args ...interface{}) ID {
70 return objc_msgSend(id, sel, args...)76 return objc_msgSend(id, sel, args...)
71 }77 }
72 78
79+// Send is a convenience method for sending messages to objects that can return any type.
80+// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
81+// It is best to cache the result of RegisterName.
82+func Send[T any](id ID, sel SEL, args ...any) T {
83+ var fn func(id ID, sel SEL, args ...any) T
84+ purego.RegisterFunc(&fn, objc_msgSend_fn)
85+ return fn(id, sel, args...)
86+}
87+
73 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword88 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
74 // as the receiver of a message. It specifies the class definition of the particular superclass that should89 // as the receiver of a message. It specifies the class definition of the particular superclass that should
75 // be messaged.90 // be messaged.
@@ -89,6 +104,19 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
89 return objc_msgSendSuper2(super, sel, args...)104 return objc_msgSendSuper2(super, sel, args...)
90 }105 }
91 106
107+// SendSuper is a convenience method for sending message to object's super that can return any type.
108+// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
109+// It is best to cache the result of RegisterName.
110+func SendSuper[T any](id ID, sel SEL, args ...any) T {
111+ var super = &objc_super{
112+ receiver: id,
113+ superClass: id.Class(),
114+ }
115+ var fn func(objcSuper *objc_super, sel SEL, args ...any) T
116+ purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
117+ return fn(super, sel, args...)
118+}
119+
92 // SEL is an opaque type that represents a method selector120 // SEL is an opaque type that represents a method selector
93 type SEL uintptr121 type SEL uintptr
94 122
M objc/objc_runtime_darwin_test.go +14 -0
@@ -5,6 +5,7 @@ package objc_test
55
66 import (
77 "fmt"
8+ "testing"
89
910 "github.com/ebitengine/purego"
1011 "github.com/ebitengine/purego/objc"
@@ -73,3 +74,16 @@ func ExampleID_SendSuper() {
7374 // Output: In Child
7475 // In Super!
7576 }
77+
78+func TestSend(t *testing.T) {
79+ // NSNumber comes from Foundation so make sure we have linked to that framework.
80+ _ = purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL)
81+ const double = float64(2.34)
82+ // Initialize a NSNumber
83+ NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double)
84+ // Then get that number back using the generic Send function.
85+ var number = objc.Send[float64](NSNumber, objc.RegisterName("doubleValue"))
86+ if double != number {
87+ t.Failed()
88+ }
89+}
@@ -5,6 +5,7 @@ package objc_test
5 5
6 import (6 import (
7 "fmt"7 "fmt"
8+ "testing"
8 9
9 "github.com/ebitengine/purego"10 "github.com/ebitengine/purego"
10 "github.com/ebitengine/purego/objc"11 "github.com/ebitengine/purego/objc"
@@ -73,3 +74,16 @@ func ExampleID_SendSuper() {
73 // Output: In Child74 // Output: In Child
74 // In Super!75 // In Super!
75 }76 }
77+
78+func TestSend(t *testing.T) {
79+ // NSNumber comes from Foundation so make sure we have linked to that framework.
80+ _ = purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL)
81+ const double = float64(2.34)
82+ // Initialize a NSNumber
83+ NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double)
84+ // Then get that number back using the generic Send function.
85+ var number = objc.Send[float64](NSNumber, objc.RegisterName("doubleValue"))
86+ if double != number {
87+ t.Failed()
88+ }
89+}

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