Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: expose ObjC API to gomobile bind programs #17102

Closed
eliasnaur opened this issue Sep 14, 2016 · 26 comments
Closed

proposal: expose ObjC API to gomobile bind programs #17102

eliasnaur opened this issue Sep 14, 2016 · 26 comments

Comments

@eliasnaur
Copy link
Contributor

Abstract

Today, gomobile bind can take a set og Go packages and expose their public API to Java or ObjC apps. The proposal is to support the reverse, exposing ObjC API to the bound Go packages.

This is the twin proposal to #16876, describing how ObjC API is accessed from Go.

Motivation

The motivation for this functionality is the same as for Java: to allow more of a mobile app to be written in Go in a convenient way that reduces boiler plate.

Proposed features

Importing ObjC classes and protocols from Go

The Go wrappers for the ObjC API references by the bound packages are generated each time gomobile bind is called. To access a ObjC type, use import statements on the form:

    import "ObjC/Module"

Which is the Go equivalent to @import Module in ObjC.

To access the static methods on a ObjC class, use

    import "ObjC/Module/NSSomeClass"

Static methods

After importing, the resulting packages NSSomeClass will contain the static methods of its ObjC counterpart. For example

import "ObjC/Foundation/NSDate"

will allow Go code to use call NSDate.Date(), which is equivalent to [NSDate date] in ObjC

ObjC classes and protocols

The package "ObjC/Module" contains Go interfaces wrapping every referenced ObjC type in the module. The wrapper types represent their wrapped ObjC types across the language barrier and can be used to call methods on wrapped instances. For example, the following Go function takes an ObjC NSDate instance and returns its description:

import "ObjC/Foundation"

func F(d Foundation.NSDate) string {
    return d.Description()
}

Creating new ObjCinstances

To create a new instance of a Java class, use the New functions defined in the class package. There is a constructor function for every ObjC instance method whose name starts with init. For example the following function performs the equivalent of [[NSObject alloc] init]:

import (
    "ObjC/Foundation/NSObject"
)

func NewNSObject() Foundation.NSObject {
    return NSObject.New()
}

Errors

ObjC methods that return errors are automatically converted to return a Go error result.

Inheriting from ObjC classes or conform to ObjC protocols in Go

Gomobile already exposes exported Go structs to ObjC; this proposal adds support for constructing Go structs directly from ObjC. In addition, Go structs will be able to extend ObjC classes and implement ObjC protocols.

To declare a Go struct that extends or implements ObjC types, use the form:

import "ObjC/UIKit"
import "ObjC/Foundation/NSCopying"

type S struct {
    UIKit.UIResponder// extends UIResponder
    Foundation.NSCopying // implements NSCopying
}

New instances of S are created in ObjC with [[GoPkgS alloc] init]. The default init initializer results in the ObjC instance referring to a new(S) from Go.

Overriding ObjC methods

To override a method from a super class or implement a method from a protocol, declare a Go method with the same name and its first letter capitalized. For example, to override the description method in GoObject:

func (o *GoObject) Description() string {
    ...
}

Exposing this

Whenever an foreign object is passed across the language barrier, a proxy is created to represent it. In the example above, there is a GoObject ObjC instance created in ObjC, and it contains a reference to its counterpart GoObject Go instance in Go. That means that when a Go method is called from ObjC, its method receiver contains the Go instance, while the ObjC instance is only accessible to Java.
To access the Java instance (for passing back to other ObjC APIs), any Go overriding method can declare a this argument with one of the Java types the enclosing class extends or implements. For example, to access the this from the description method, use:

func (o *GoObject) Description(this Foundation.NSObject) string {
    ...
}

The this variable will behave just as if it were a pure ObjC NSObject, and if passed or returned to ObjC, will have the same identity as the ObjC reference.

Calling super

In Go, delegation is achieved through delegation, but in ObjC, the keyword super is needed to access overridden methods. To call a super method from Go, use the Super() method on the this variable:

func (o *GoObject) Descriptionthis Foundation.NSObject) string {
    return this.Super().Description()
}

Type name mangling

In ObjC, classes and protocols have difference namespaces. For example, there is both a NSObject class and a NSObject protocol. In such cases, the Go names for the class has "C" appendded and the protocol has "P" appended. Thus, Foundation.NSObjectC is the name for the Go NSObject class, while Foundation.NSObjectP is the name for the protocol.

Method names

In ObjC, the parameters are named and part of the function signature. The Go name for a method is the upper case first part of the method signature. If that is ambiguous, the full signature is used with colons removed and parameter names upper-cased. The final fallback is the ObjC signature with colons replaced with underscores, resulting in guaranteed unique, albeit ugly, names. For example, the ObjC methods

someMethod
someMethod:
anotherMethod:
anotherMethod:withArgument:
thirdMethod:withArgument

are translated to the following Go method names:

SomeMethod
SomeMethod_
AnotherMethod
AnotherMethodWithArgument
ThirdMethod
@gopherbot
Copy link

CL https://golang.org/cl/29175 mentions this issue.

@gopherbot
Copy link

CL https://golang.org/cl/29174 mentions this issue.

@gopherbot
Copy link

CL https://golang.org/cl/29173 mentions this issue.

@adg
Copy link
Contributor

adg commented Sep 26, 2016

cc @crawshaw @rakyll @hyangah

@joeblew99
Copy link

This is nice.
I played around last week with calling Java code from go, and this is the same for IOS.
Its going to be really interesting how people use this.

@quentinmit quentinmit modified the milestone: Proposal Oct 4, 2016
gopherbot pushed a commit to golang/mobile that referenced this issue Oct 5, 2016
The objc package adds a parser that uses the clang -cc1 -ast-dump
command to extract type information about ObjC classes and protocol.

The resulting type information is needed to generate ObjC API wrappers
in Go.

This is the first part of the implementation of proposal golang/go#17102.

For golang/go#17102

Change-Id: I8382b54c0bd315703ec5a62cc177e1a2ace061e9
Reviewed-on: https://go-review.googlesource.com/29173
Reviewed-by: David Crawshaw <crawshaw@golang.org>
gopherbot pushed a commit to golang/mobile that referenced this issue Oct 16, 2016
Using the new ObjC type analyzer API, scan the bound packages for
references to ObjC classes and protocols and generate Go wrappers for them.

This is the second part of the implementation of proposal golang/go#17102.

For golang/go#17102

Change-Id: I773db7b0362a7ff526d0a0fd6da5b2fa33301144
Reviewed-on: https://go-review.googlesource.com/29174
Reviewed-by: David Crawshaw <crawshaw@golang.org>
@joeblew99
Copy link

joeblew99 commented Oct 17, 2016

@crawshaw @rakyll @hyangah
I would be allot happier if an example can be added to the main examples...

https://github.com/golang/mobile/tree/master/example

@eliasnaur
Copy link
Contributor Author

There is some documentation and an Android example incoming in https://go-review.googlesource.com/c/31170/

There is not iOS app example yet, but you can check out
https://github.com/golang/mobile/tree/master/bind/testpkg/objcpkg in the meantime.

@joeblew99
Copy link

joeblew99 commented Oct 18, 2016

@eliasnaur thanks for reply. Ok good to know.

i see from the docs its called "direct integration mode" Which leads me to ask this side question:
gomobile when building complete apps (not binding & not direct integration mode ), compiles into a NativeActivity as i understand it. Can one of these exposed http services locally and run as a daemon service ?
Or can one use the "direct integration mode" to build local daemon / services that expose http services locally.

@eliasnaur
Copy link
Contributor Author

I don't see why not.
tir. 18. okt. 2016 kl. 13.18 skrev jow blew notifications@github.com:

@eliasnaur https://github.com/eliasnaur thanks for reply. Ok good to
know.

Side question:
gomobile when building complete apps (not binding), compiles into a
NativeActivity as i understand it. can one of these exposed http services
locally and run as a daemon service ?


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#17102 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAgCDKpA8z5qnX3zuB_SpPOsCZJEmN-wks5q1KshgaJpZM4J8600
.

@joeblew99
Copy link

@eliasnaur
It would be nice if an equivalent IOS example was added that does the same thing as Reverse at https://go-review.googlesource.com/#/c/31170/.
Maybe it can be added after this new code settles down..

@eginez
Copy link

eginez commented Jul 1, 2017

hey all is there an example for the gobind command to generate reverse bindings? I am looking into generate bindings for NSFoundation classes, not necessarily ios stuff

@eliasnaur
Copy link
Contributor Author

There are no examples per se, but take a look at the golang.org/x/mobile/bind/testpkg/objcpkg package that contains test code for the iOS reverse bindings.

@eginez
Copy link

eginez commented Jul 1, 2017

Hey @eliasnaur thanks for pointing that out. I am still a little confused on how to get the bindings. I am invoking gobind directly on the package like so: "gobind -lang objc -outdir ObjC golang.org/x/mobile/bind/testpkg/objcpkg", but that's failing (package "golang.org/x/mobile/bind/testpkg/objcpkg": no buildable Go source files in /Users/eginez/repos/goland/src/golang.org/x/mobile/bind/testpkg/objcpkg). Any ideas?

@eliasnaur
Copy link
Contributor Author

eliasnaur commented Jul 1, 2017 via email

@eginez
Copy link

eginez commented Jul 2, 2017

I am not trying to create a mobile app I am trying to use nsfoundation in my go app for mac os, happy to get some directions if it's better to use the gombile command

@cmanus
Copy link

cmanus commented Jul 6, 2017

@eliasnaur I hope you can give me a bit of guidance. I am trying to create a UIViewController in Go using gomobile bind and the UIViewController ViewDidLoad method is being called as expected, but I want to programmatically add a webview (or label or anything) to the UIViewControllers view property and I just can't figure out how to do it. I have done a lot of gomobile sdk style apps and I am somewhat familiar with reverse bindings, but failing to see what is wrong in my case. The commented out code is what I am trying to do:

`
import (
//"fmt"
"ObjC/UIKit"
//"ObjC/Foundation"
gopkg "ObjC/GoExcite"

)

type MainViewController struct {
UIKit.UIViewController
}

func (vc *MainViewController) ViewDidLoad(self gopkg.MainViewController) {
self.Super().ViewDidLoad()
//webview := UIKit.UIWebView.NewWithFrame(self.View().Bounds())
//url := Foundation.NSURL.URLWithString("https://google.com")
//request := Foundation.NSURLRequest.RequestWithURL(url)
//webview.LoadRequest(request)
//self.View().AddSubView(webview)

}
func (vc *MainViewController) DidReceiveMemoryWarning(self gopkg.MainViewController) {
self.Super().DidReceiveMemoryWarning()
}

`
My fundamental question is that none of the methods I expect are on self.View() which I expect to be a the same as the .view property of a UIViewController in Obj-C. I also can't figure out how to create a new instance of things like the UIWebView.

@eliasnaur
Copy link
Contributor Author

eliasnaur commented Jul 6, 2017 via email

@cmanus
Copy link

cmanus commented Jul 7, 2017

fmt.Println(self.View().Bounds())
results in:
./ViewController.go:18: self.View().Bounds undefined (type ObjC.UIKit_UIView is interface with no methods)

but the other problem seems to be that UIKit.UIWebView.NewWithFrame doesn't exist. Also "New" doesn't exist either. I thought that initWithFrame would be translated to "NewWithFrame", Overall, it seems like the mapping of method names isn't what I expect from the documentation. I'll help update the documentation if we get this working. Here is full set of code.

https://github.com/cmanus/GoExcite

@eliasnaur
Copy link
Contributor Author

Ah, https://developer.apple.com/documentation/uikit/uiview/1622580-bounds is a CGRect. Unfortunately, go mobile doesn't (yet) support value structs; this applies to reverse bindings as well as regular bindings.

@cmanus
Copy link

cmanus commented Jul 7, 2017

@eliasnaur well that pretty much means you can't build anything GUI related with reverse bindings. It is interesting though that it said UIView was a interface with no methods, is it possibly due to the fact that nothing in UIView is mappable?

@eliasnaur
Copy link
Contributor Author

That's true, I'm afraid. The Android reverse bindings are advanced enough to enable GUI programming in Go. The iOS reverse bindings never progressed as far.
Yes, I suspect the empty interface is caused by nothing in UIView is being bound by gomobile.

@groob
Copy link
Contributor

groob commented Jul 7, 2017

@eliasnaur do you know if it would be possible to generate the objc bindings on macOS?
I played around a bit, and it seems that only arm is supported at the moment?

I'm looking for the ability to have something similar to PyOBJC on macOS.

@eliasnaur
Copy link
Contributor Author

Not much (if any) of the generators themselves are specific to iOS and it should be possible to generate macOS bindings. However, go mobile doesn't support anything else than iOS and Android so I don't know how much work is needed in practice.

@cmanus
Copy link

cmanus commented Jul 7, 2017

@eliasnaur I'd really like to get those C structures exposed, admittedly my knowledge of cgo is pretty limited, but it seems like it would be possible to reference them that way, then just make the bind utility aware of them. Perhaps I am completely off on that assumption. Anyway if I wanted to tackle that, where should I start, and do you think it is possible without deep knowledge of everything else?

@eliasnaur
Copy link
Contributor Author

eliasnaur commented Jul 7, 2017 via email

@ensoreus
Copy link

ensoreus commented Jun 8, 2018

Guys. All the things you've done is really great!
But faced a task I can't accomplish myself nor can google an answer in a 2 days already.

I have a Go interface
type GetResponse interface { OnResult(json string) }

I have to subscribe on that event OnResult from ObjC using this interface.
func Subscribe( response GetResponse){ response.OnResult("some json") }

ObjC bind gives me a corresponding protocol and a basic class
`
@interface GetResponse : NSObject <goSeqRefInterface, GetResponse> {
}
@Property(strong, readonly) id _ref;

(instancetype)initWithRef:(id)ref;
(void)onResult:(NSString*)json;
@EnD
`
So, I need to get this json in my ObjC env. How can I do that?

  1. Subclassing If I subclass this GetResponse or just use it as is and pass to Subscribe routine, it crashes
    'go_seq_go_to_refnum on objective-c objects is not permitted'
  2. Category if I create struct on Go side with the protocol support, I can't subclass it but at least it's not crashes:
    type GetResponseStruct struct{} func (GetResponseStruct) OnResult(json string){log.Info("GO RESULT")} func CreateGetResponse() *GetResponseStruct{ return &GetResponseStruct{} }
    I have a solid object without obvious way to hook up my callback. If I make a category and override the onResult routine, it's not called. Just because overriding existing methods of class is not determined behavior according to AppleDoc. Anytime OnResult called from Go, the default implementation invokes and "GO RESULT" appears.
  3. Swizzling I tried to use category and swizzle (replace method's implementation with mine renamed method) but it only works if I call onResult from ObjC env.

Any way to solve my issue? Or I just red the doc not very accurately? Please help me

@golang golang locked and limited conversation to collaborators Jun 4, 2020
imWildCat pushed a commit to imWildCat/go-mobile that referenced this issue Apr 10, 2021
Accept ObjC API wrapper types as arguments and return values from
bound Go package functions and methods. Also, allow Go structs
to extend ObjC classes and implement ObjC protocols as well as override
and implement methods.

This is the third and final part of the implementation of the golang/go#17102
proposal.

Fixes golang/go#17102

Change-Id: I601d90fb6d22b8d6f8b7d5fe0130daa1a4dd4734
Reviewed-on: https://go-review.googlesource.com/29175
Reviewed-by: David Crawshaw <crawshaw@golang.org>
imWildCat pushed a commit to imWildCat/go-mobile that referenced this issue Apr 11, 2021
Accept ObjC API wrapper types as arguments and return values from
bound Go package functions and methods. Also, allow Go structs
to extend ObjC classes and implement ObjC protocols as well as override
and implement methods.

This is the third and final part of the implementation of the golang/go#17102
proposal.

Fixes golang/go#17102

Change-Id: I601d90fb6d22b8d6f8b7d5fe0130daa1a4dd4734
Reviewed-on: https://go-review.googlesource.com/29175
Reviewed-by: David Crawshaw <crawshaw@golang.org>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants