Golang 开发跨平台SDK(iOS, Android),Part 2

    上一篇简单的介绍了下Golang Mobile的工作原理,这一篇讲介绍如何使用Golang 做一个跨平台的RSS SDK。

RSS和HTTP

    RSS是基于XML标准,在互联网上被广泛采用的内容包装和投递协议,主要用来描述和同步网站内容。既然RSS是一种网络协议,那我们就需要使用Golang的http模块了。整个流程可以归结为,RSS的SDK提供一个方法去请求RSS源获得数据,然后通过callback获得数据,而这些数据最好是Android/iOS都可以直接使用的model集合。

    作为一个http请求在移动端最好是在子进程中执行,通过异步回调返回给主进程或UI 进程,这其中带来的明显好处就是我们的http任务不会阻塞整个UI,用户在请求RSS源获取最新网站内容的同时依然可以继续对应用进行操作。Golang 本身并没有提供一个异步执行http的库,但是我们可以利用Golang routine 简单的封装一个异步执行http的库。

func (t *Task) Runtask() {
	r := t.create()      // 这里create是创建channel
	go func() {
		res, err := http.Get(t.Url)
		if err != nil {
			r.err <- err
		} else {
			buf, err := ioutil.ReadAll(res.Body)
			if err != nil {
				r.err <- err
			} else {
				r.data <- buf
			}
		}
	}()
}

上面的代码段摘自我实现的库go-async,其原理就是将http任务在 golang routine中去执行,当执行结束是通过channel 返回。

接口

    通过简单的继承上面的go-async我们就可以实现一个简单的RSS http请求。

func RSS() string {
	return "http://rss.cnbeta.com/rss"
}
type Listener interface {
	OnSuccess(buf []byte)
	OnFailure(err string)
	OnEnd()
}
type CnBeta struct {
	listener Listener
}
func (cb *CnBeta) Run() {
	t := async.NewTask(RSS(), cb)
	t.Runtask()
}
func (cb *CnBeta) Success(buf []byte) {
}

func (cb *CnBeta) Failure(err error) {
}

因为Golang现在不支持倒出constant类型的数据结构,所以我们需要定义一个function,然后利用function的返回值取得这个常量。SuccessFailure是从go-async继承的方法,当异步http callback的时候就会执行其中一个方法。Listener是定义的一个外部接口,当然在Java/swift/Objective-C中实现这个接口在应用中拿到异步callback的数据。

Model

    上面我实现了请求RSS源并且异步获取了byte数据,但是我们的目标是设计一个SDk,只提供方法并不能满足一个完整的SDK的需要,SDK需要接口返回的是Model或者Model的集合。

func NewRssXml() *RssXml {
	return &RssXml{
		Channel: &RssFeed{},
	}
}

type RssXml struct {
	Version string
	Channel *RssFeed
}

type RssFeed struct {
	Title       string
	Link        string
	Description string
	Language    string
	Copyright   string
	PubDate     string

	Items []*RssItem
}

type RssItem struct {
	Title       string
	Link        string
	Description string
	PubDate     string
}

func (rss *RssXml) Feed() *RssFeed {
	return rss.Channel
}

上面就是使用Golang实现的RSS的model,由于Golang mobile 本身的限制只能export基本的数据类型,所以我只export需要的类型,但是我也想要直接export Items []*RssItem,这时我们可以通过实现一个类型方法来实现

type RssFeed struct {
	Title       string
	Link        string
	Description string
	Language    string
	Copyright   string
	PubDate     string

	items []*RssItem
}

func (feed *RssFeed) Length() int64 {
	return int64(len(feed.items))
}

func (feed *RssFeed) Item(p int64) *RssItem {
	if p >= feed.Length() {
		return &RssItem{}
	}
	return feed.items[p]
}

通过 rssfeed.Length() 我们获得总共有多少RssItem,而通过rssfeed.Item(1) 我们变可以得到第二个RssItem,变相的实现了直接使用rssfee.Items[1]

整合

    实现了Rss Model这时我们便可以将接口改写下

type Listener interface {
	OnSuccess(rss *RssXml)
	OnFailure(err string)
	OnEnd()
}

func (cb *CnBeta) Success(buf []byte) {
	doc := xmlx.New()
	if err := doc.LoadBytes(buf, nil); err != nil {
		cb.Failure(err)
		return
	}
	cb.listener.OnSuccess(parser(doc))
	cb.listener.OnEnd()
}

func (cb *CnBeta) Failure(err error) {
	cb.listener.OnFailure(err.Error())
	cb.listener.OnEnd()
}

这是一整个SDK 便是完成了,但是最关键的部分是编译后在应用开发时引入,以iOS为例通过前一篇的方法我们得到了rss.framework,这时我们只要通过XCode将这个rss.framework引入到项目中便可以使用了。

func loadRss() {
    let cnbeta = GoCbNewCnBeta(self)
    cnbeta.run()
}

// MARK: - cnbeta lisener interface
func onEnd() {
}

func onFailure(err: String!) {
}

func onSuccess(rss: GoCbRssXml!) {
    cbRss = rss
    
    dispatch_async(dispatch_get_main_queue(), {
        
        if (self.refreshControl!.refreshing) {
            self.refreshControl!.endRefreshing()
        }
        self.tableView.reloadData()
    })
}

下面的截图便是我写的一个Demo

结论

    作为一个跨平台的语言,Golang拥有其特有的优势,编译成机器码带来的高效的性能,而且作为一个编写SDK或者数据处理层的工具,其高效的开发效率也是值得考虑的。至于能不能用在production的项目上,我想说Google都在用。

refs