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

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 · 25 comments

Comments

Projects
None yet
8 participants
Contributor

eliasnaur commented Sep 14, 2016

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

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

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

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

Contributor

adg commented Sep 26, 2016

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 gopherbot pushed a commit to golang/mobile that referenced this issue Oct 5, 2016

@eliasnaur eliasnaur internal/importers: introduce package to analyze ObjC types
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>
6ea0bb5

@gopherbot gopherbot pushed a commit to golang/mobile that referenced this issue Oct 16, 2016

@eliasnaur eliasnaur bind,cmd: add generator for ObjC API wrappers
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>
6ecf8ee

joeblew99 commented Oct 17, 2016 edited

@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

Contributor

eliasnaur commented Oct 17, 2016

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 commented Oct 18, 2016 edited

@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.

Contributor

eliasnaur commented Oct 18, 2016

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
.

@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 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

Contributor

eliasnaur commented Jul 1, 2017

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 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?

Contributor

eliasnaur commented Jul 1, 2017

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 commented Jul 6, 2017 edited

@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.

Contributor

eliasnaur commented Jul 6, 2017

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

Contributor

eliasnaur commented Jul 7, 2017

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 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?

Contributor

eliasnaur commented Jul 7, 2017

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.

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.

Contributor

eliasnaur commented Jul 7, 2017

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 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?

Contributor

eliasnaur commented Jul 7, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment