It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【ReactNative】React Nativeで開発を始めるにあたって

最近趣味でReactNativeを触っていましたが、タイミングよく業務でもReactNativeのアプリに関わることになりました。 趣味の範囲では、適当に触って入ればよかったのですが、流石に業務で使うとなると少し真面目に勉強しなくてはいけないなと思い始めています。

ReactNativeはまだまだ日本語のドキュメントも少なく、アプリ開発をする上で決してメジャーな選択ではないと思いますが、効率的な開発を行なう上では有力な選択肢ではないかと思います。
色々とハマりどころはあると思いますが、少しずつ知見を溜めていけたら良いなと思います。

[ReactNativeを使った開発を行う上での選択肢]

1. Expo

Expoとは、ReactNativeでのアプリ開発を支援するためのプラットフォーム(SDK)です。
※ Expoの詳細については、色々な方が説明しているかと思うので割愛します。
特徴としてはExpoの機能で完結するアプリの場合、アプリの作成はJavascriptのみで完結します。
iOSAndroidでは、ビルドや開発にXCode(iOS)、AndroidStudio(Android)を使うのが一般的ですがExpoを使うとビルド作業自体もExpoサービス上で行ってくれます。
デメリットとしては、上記の通り基本的にiOSAndroidのネイティブコードを触ることができないためExpoSDKでできないことがやりたくなった場合は別途ejectを行い、ネイティブアプリの書き出しを行います。

プロジェクトの始め方は、ここを参照。

※ このすべてをExpoサービス上で行う開発をmanaged-workflowと呼ぶみたいです。

2. React Native

基本的なReactNativeでの開発で、iOSAndroidソースコード + ReactNativeライブラリを使って開発を行います。
一通りのセットアップは、react-native CLIツールが提供してくれており、その手順に従うとReactNativeでの開発が可能な状態でiOSAndroidのプロジェクトを作成することができます。
各アプリのビルドは、iOSAndroidの本来のビルドツールで行う必要があります。
Expoには提供されていないライブラリを使用したい場合や、Expoではできないようなネイティブコードに特化した機能をカスタマイズしたい場合はこの方法で開発することになります。

プロジェクトの始め方は、ここを参照。

3. React Native + Expo SDK

1 と 2をあわせたような方法です。
Expoから作成したライブラリをejectするときの選択肢の一つで、ExpoSDKで使用できるツール郡はそのまま使い続けるが、ビルドなどのワークフローは独自に行うという方法になります。
Expoプロジェクトを expo eject するときにXXXを選択した場合、ExpoSDKを組み込んだ状態のiOSAndroidのプロジェクトが出力されます。

? How would you like to eject your app?
  Read more: https://docs.expo.io/versions/latest/expokit/eject/ (Use arrow keys)
❯ React Native: I'd like a regular React Native project.
  ExpoKit: I'll create or log in with an Expo account to use React Native and the Expo SDK.
  Cancel: I'll continue with my current project structure.
  • 上記の選択肢でExpoKitを選んだ状態

この方法の利点は、ExpoSDKの使える機能を利用しつつ、Expoだけではできない機能のライブラリなどを組み込むことができるようになることです。

※ このExpoの機能を利用するフローをbare-workflowと呼ぶみたいです。

[どの方法を選択するか?]

業務で関わっているアプリは、2のReactNativeのみで作成されており、個人の趣味で作成したアプリは3のExpoSDKを組み込んだ状態で作成しました。
個人的には、3のExpoSDKを活用した方法が良いかと思っていますが、色々と判断の上自分たちにあった方法を選択するのが良いと思います。
3を選んで感じたことをいくつか書いていきます。
※ あくまで個人的な感想です。
(目的が明確であり、シンプルさと速度を重視するのであれば、1のExpoのみで開発するのが一番簡単だと思います。)

[良かったこと] OTA Updates(code push)

ReactNativeで開発するメリットの一つにOTA Updates(code push)があります。
これは、アプリストア(AppStore、GooglePlayStore)を介さずに、直接アプリをアップデートできる仕組みです。
簡単に説明すると特定の契機(アプリ起動時など)に最新のJSファイルをサーバからダウンロードして、現在動いているものと差し替えるというものです。
ReactNativeでこれを実現するためには、Microsoftが開発しているcode pushライブラリを使うのが一般的ですが、Expoでは標準で組み込まれています。
使い方も非常にシンプルで、Expoに機能がまとめられていることはメリットの一つではないかと思います。

[良かったこと] シミュレーターでの開発

ReactNativeでシミュレーターを使った開発を行なうときは、 iOSであれば react-native run-iosAndroidreact-native run-android を実行しますがExpoは expo start のみで両方のOSが繋がります。
また、詳しく検証してないので実際のところわかりませんが、シミュレータ自体の動きもExpoのほうが安定しているように感じます。(個人的な感想です)
ベースの技術は同じような気がするのですが、後発のExpoのほうが色々とうまくできているというのはあるのかもしれません。

[悪かったこと] ExpoSDK内で使用されているライブラリと別ライブラリで競合が発生した

具体的には、AndroidのExpoSDK内で使用しているokhttpという通信ライブラリがあるバージョンからExpo独自パッケージに変わり、依存しているその他のライブラリと競合が発生してビルドで苦労した。という内容です。
Androidではあるあるといえばあるあるなのですが、ExpoSDK自体が決して小さいライブラリではないのでこういった依存関係での問題はそれなりにあるのかなと思います。

[React Nativeでアプリを作る上で注意すること]

ゼロからアプリを作る上でいくつかハマったり、感じたことがあったので記載します。

iOSAndroidは、必ず同時に動作確認をする

僕の場合、まずはiOSだけで動作確認して開発をしており、「ある程度動いたらAndroidも確認するか」ぐらいの気持ちでいました。
そしてある程度作り上げた時点で、Androidでビルドしてみたいのですが全く動きませんでした。(ビルドがまず全然通らなかった)
ReactNativeは、クロスプラットフォームツールのためよほどのことがない限りは、外部のライブラリを比較的多く利用すると思います。
そのため、ビルド時の依存関係で問題が発生することは多々あります。
これはReactNativeに限った話してはないのですが、依存関係の問題は複数が同時に発生しているほど読み解くのが困難になり非常に辛いです。
ですので、iOSAndroidの動作確認は常に同時に行っておくべきでした。(ネイティブライブラリを利用したタイミングでは特に)
また、iOSAndroidでは描画システムが異なるため、片方できれいに配置されていても、もう片方では位置がずれているようなこともしばしばあります。
そういった点も踏まえると、常に両プラットフォームを確認しておくのが結果的に効率的かと思いました。

やりたいことが実現できるライブラリがないと開発コストが上がる

ReactNativeは、基本的はJSを使った開発になるのですが、各OS固有の機能やモバイルデバイス固有の機能を使う際にネイティブ層を意識した実装が必要になります。
(例えばカメラやGPS、Push通知など)
これらの多くの機能は、ReactNative自身やExpoが提供してくれてはいますが、その中に使いたい機能が存在しなかった場合は以下の選択肢になります。

  • ①.ネイティブ層(iOS,Android)とのブリッジを自分で実装し、各ネイティブ言語で使いたい機能を実装する
  • ②.3rdパーティ製のライブラリを探して、組み込む

①を選択する場合は、各ネイティブOSの実装知識が必要となるため多くの場合は②をまずは選択するかと思います。

そのため、ReactNativeを使った開発を選択する場合、「ライブラリをさがす作業」に多くの時間を使いがちになるなと感じています。
(今どきのモバイル開発はかなりのエコシステム化が進んでいて各ネイティブで開発をしていても基本的に誰が便利なライブラリを公開してくれていますが、ReactNativeの場合はモバイルエンジニア以外の人が始めることもあるので特にだと思います。)

それでも、どうしても機能が実現できなかった場合は①を選択するしかありません。
僕の場合は、iOSAndroidも一応実装できるのですが上記のようなことが起きたときに「これiOS(Android)ならそんなに難しくないのにReactNative上で両OSを意識して実装するのめんどくさいなぁ」と思うときが時々あります。


引き続き何かあれば、追記していきます。

【React】【メモ】Reactのアプリをgithub-pagesにデプロイする

Reactで作成したアプリを手軽な環境にデプロイする方法のメモ。

環境

% node -v
v10.1.0

% yarn -v
1.6.0

公式ドキュメント

gh-pagesをインストール

yarn add gh-pages --dev

package.jsonにデプロイ先のURLを指定する

"homepage": "https://<user_name>.github.io",
  • github上のSettings > GitHub Pagesに表示されているURLをそのまま設定する

package.jsonに「redeploy」「deploy」コマンドを追加

"scripts": {
+   "predeploy": "npm run build",
+   "deploy": "gh-pages -d build",
    "start": "react-scripts start",
    "build": "react-scripts build",
  • 「predeploy」コマンドは任意の場所にreactのアプリをコンパイルする
    • 上記は、create-react-appで作成されたアプリのためデフォルトのbuildコマンドでビルドしている
  • 「deploy」コマンドの-dはビルド先のディレクトリ(index.htmlがある場所)を指定する

デプロイ

yarn deploy
  • デプロイに成功すると「gh-pages」というブランチがgithub上にできる

github pagesの公開ブランチ設定を「gh-pages」に変更する

  • github上からSettings > GitHub Pagesに行き、「Source」を「gh-pages」に変更しsave
  • 数分後に設定したURLに表示されていることが確認できればOK

再デプロイ

yarn deploy again

【gRPC】gRPCを触ってみた

gRPCについて興味があったため触ってみました。
記載内容については誤りがあるかもしれません。ご指摘ありましたらいただけると幸いです。

gRPCとは

Googleによって開発されたRPCフレームワークです。
そもそもRPCとは(remote procedure call)の略で、なかなか上手く説明できないのですが、 例えばあるマシンからHTTPのプロトコルで決まった形式でのやり取りを行って、別のマシンのプログラム(関数)を実行する。ようなものを指します。
RPCには様々な種類が存在しますが今回は割愛します。(JSON-RPC、XML-RPCなど)
gRPCもRPCの一種になります。

Protocol Buffers

gRPCの特徴の一つとしてデータのシリアライズに「Protocol buffers」という技術が使われています。
これはメッセージデータの構造、手続きの構造を定義するためのインタフェース記述言語(IDL: Interface Description Language)に属するもので「.proto」という拡張子のファイルにメッセージタイプを定義します。
現在「prot3 version」までバージョンが進んでいます。詳しくは公式ページを参照ください。

.protoのサンプル

message Person {
  // 基本は[型名] [名前] でデータを定義するみたいです
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  // enumで固定の数値も定義できる
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

gRPCではこの「.proto」ファイルを各言語のコンパイラ(protocol buffer compiler)でコンパイルすることで、インターフェース処理コードを自動生成します。
APIを自動生成するフレームワークSwaggerがありますがイメージは似ているかもしれません。
※ Swagger使ったことないので間違っているかもしれません。
現在は次のプログラミング言語に対応しているようです。

HTTP/2への対応

gRPCはHTTP/2を前提として定義された規格されています。※ HTTP/2については詳細は割愛します。
HTTP/2を前提としているためクライアントとサーバーの双方向通信において柔軟なやりとりが可能というのもメリットの一つのようです。
環境によっては従来のHTTP/1よりも高パフォーマンスを出すことができるかもしれません。

gRPCを使う時の流れ

では実際にどうやって使えばいいのかを公式のQuick Startに沿ってやってみます。
公式ではいくつかサンプルソースが公開されていますが、今回は「helloworld」サンプルを使用します。
対応言語については、今回は言語はGoを選択しました。

※ 細かな手順は公式の方がわかりやすいと思うので、詳細は上記リンクを参照ください
※ Go言語については理解が足りない部分があるため、間違っていたらすみません

前提

  • Goのバージョンは1.6以上が必要のようです

ツールのインストール

  • gRPCの実装には次のツールが必要なのでインストールします

Helloworld

  • gRPCをインストールするとサンプルソースが幾つか梱包されていて、helloworldも以下に格納されています。
$GOPATH/src/google.golang.org/grpc/examples/helloworld

1. 「.protoファイル」にインターフェースを定義する

  • まずは、protocol buffersを使ったインターフェースを定義します。
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

service Greeter {
  // rpcというキーワードでgRPCの定義ができる
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  // データ形式と名前でフィールドを定義。後ろの数字はフィールドを固有に識別するためのタグ。ユニークの必要がある。
  // フィールドの識別タグは、1~15は1バイト、16~2047は2バイトでエンコードされるため頻繁に使うフィールドは1~15にすると良いよう
  string name = 1;
}

message HelloReply {
  string message = 1;
}
  • helloworldサンプルのルートディレクトリ配下のhelloworldディレクトリ内に「helloworld.prot」というファイルがあります。
  • サンプルでは次の定義がされています。
    • Greeter: serviceというキーワードで宣言されていて 1つのRPC手続きが定義されています。(メソッド定義のようなもの)
    • HelloRequest: 1つのフィールドをもち、SayHelloの呼び出し時メッセージとして使われます。
    • HelloReply: 1つのフィールドをもち、SayHelloの返却メッセージとして使われます。

※ protocol buffersの仕様については、公式ページを参照ください

2. 定義ファイルをコンパイル

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld
  • 上記コマンドを実行すると「helloworld.prot」と同じディレクトリに「helloworld.pb.go」というgoファイルが出力されます

「helloworld.pb.go」

  • 色々と自動生成でソースが出力されていますが、詳細は省略して定義した箇所に関連しそうな部分だけ抜粋しています
package helloworld

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

import (
    context "golang.org/x/net/context"
    grpc "google.golang.org/grpc"
)

// 定義したフィールドのゲッターメソッド
func (m *HelloRequest) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

// 定義したフィールドのゲッターメソッド
func (m *HelloReply) GetMessage() string {
    if m != nil {
        return m.Message
    }
    return ""
}

/*----------- クライアント側のコード --------------------*/
type GreeterClient interface {
    // 定義したメソッドのクライアント側のインターフェース
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
    cc *grpc.ClientConn
}

func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
    return &greeterClient{cc}
}

// クライアントが呼び出すSayHelloメソッド
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    out := new(HelloReply)
    err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

/*----------- サーバー側のコード --------------------*/
type GreeterServer interface {
    // 定義したメソッドのサーバー側のインターフェース
    // メソッドの実装は、サーバー側で記述する
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

// サーバーインターフェースを登録する為のメソッド
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    s.RegisterService(&_Greeter_serviceDesc, srv)
}
  • この様に対応した言語に合わせてインターフェースのコードが自動生成されます

クライアントの実装

  • 上記自動生成されたgoコードを使って実装されているクライアントのコードを見てみます。

greeter_client/main.go

package main

import (
    "log"
    "os"
    "time"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
  // 生成されたpb.goファイルのimport
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // grpcのコネクションを生成
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
  // GreeterのClientの生成
    c := pb.NewGreeterClient(conn)
  // コマンドラインに引数があればそれをnameとして渡す
  // なければ world
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
  // SayHelloメソッドの呼び出し
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}
  • クライアン側はgrpcのコネクションとともに生成したクライアントからSayHelloメソッドを呼んでいます。

サーバーの実装

  • 上記自動生成されたgoコードを使って実装されているサーバー側のコードを見てみます。

greeter_server/main.go

package main

import (
    "log"
    "net"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
    "google.golang.org/grpc/reflection"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// 自動生成されたインターフェースに対してメソッドを実装する
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  // 自動生成されたHelloReplyを使ってレスポンスを返す
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
  // 生成された登録メソッドにgrpcのサーバーを登録する
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    // おそらくこのreflectionにサーバーを登録することでメッセージをハンドリングしてくれる?
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
  • サーバー側もシンプルでGreeterServerに対してgrpcサーバーを登録するだけのようです。

サンプルを動かす

サーバーの起動

$ go run greeter_server/main.go

クライアントの実行

引数なし

$ go run greeter_client/main.go

出力

2018/05/05 11:56:04 Greeting: Hello world

引数あり

$ go run greeter_client/main.go hoge

出力

2018/05/05 11:56:45 Greeting: Hello hoge

感想

  • 詳細はもう少し勉強する必要がありますが、手順は簡単でした
  • .protoは可読性が高いのでメンテナンス性は良さそうです
  • ソースコードが自動生成されるのも運用的には良さそうです

参考

【Go】メソッドとレシーバ

※ この記事時点のGoのバージョンは「1.10.2」です

Goでは、特定の型に関連づけられた関数を「メソッド」と呼びます。
また、メソッドを呼び出される対象(型)のことを「レシーバ」と呼びます。

inon29.hateblo.jp

ポインタレシーバと変数レシーバ(値レシーバ)

  • メソッドを宣言した時のレシーバの型が「ポインタ」なのか「変数(値)」なのかによってメソッドの挙動が異なります。
  • レシーバがポインタのものを「ポインタレシーバ」変数(値)のものを「変数レシーバ(値レシーバ)」と呼びます。
package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    fmt.Printf("X: %f, Y: %f\n", d.X, d.Y)
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    fmt.Printf("X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    d.Display()

    d2 := &Direction{20.0, 50.0}
    d2.DisplayP()
}

ポインタレシーバと変数レシーバの違い

  • 変数レシーバは、メソッド呼び出し時に変数の値をそのままコピーし、新たな値をメソッドに渡します。そのためメソッド内で変数を変更してももとの値は変更されません。
  • ポインタレシーバは、もとの値のポインタを渡すため、メソッド内で変数の値を変更するともとの値が変わります。
    Java(や他の言語)のメソッドの「値渡し」 「参照渡し」と同じです
package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    // xとyを書き換える
    d.X = 15.0
    d.Y = 5.0
    fmt.Printf("Display() => X: %f, Y: %f\n", d.X, d.Y)
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    // xとyを書き換える
    d.X = 99.0
    d.Y = 88.0
    fmt.Printf("DisplayP() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    d.Display()
    // 元の変数の値は変わらない
    fmt.Printf("main() d => X: %f, Y: %f\n", d.X, d.Y)

    d2 := &Direction{20.0, 50.0}
    d2.DisplayP()
    // 元の変数の値も変わる
    fmt.Printf("main() d2 => X: %f, Y: %f\n", d2.X, d2.Y)
}

出力

Display() => X: 15.000000, Y: 5.000000
main() d => X: 10.000000, Y: 3.500000
DisplayP() => X: 99.000000, Y: 88.000000
main() d2 => X: 99.000000, Y: 88.000000

レシーバの暗黙的型変換

宣言されているレシーバがポインタレシーバである時は、呼び出し元が「変数(値)」または「ポインタ」のどちらであっても実行できます。
変数であったとしてもGoのコンパイラが内部でポインタ型に変換して実行してくれます。

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    fmt.Printf("DisplayP() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    // ポインタレシーバのメソッドを変数(値)から呼び出せる
    d.DisplayP()
    // コンパイラが(&d)に変換してくれる
    (&d).DisplayP()
}

逆に宣言されているレシーバが変数レシーバ(値レシーバ)である時も、呼び出し元が「変数(値)」または「ポインタ」のどちらであっても実行できます。
この場合はGoのコンパイラが内部で変数に変換してくれています。
※ 変数レシーバであることは変わらないので実行時に渡されるのは呼び出しの変数のコピーになる

package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    fmt.Printf("Display() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := &Direction{20.0, 50.0}
    d.Display()
    // コンパイラが(*d)に変換してくれる
    (*d).Display()
}

構造体がポインタで合っても、変数であっても変わらずドット(.)アクセスできるのと同じように、利便性を考えこのような変換が行われるようです。

参照

【Go】関数とメソッド

最近Goを勉強し始めました。

関数とメソッドの違い

基本的には次のように考えています。

  • 関数は、一般的な関数(funcから始まる)
  • メソッドは「型」に関連付けられた関数

関数

package main

import "fmt"

// これは関数
func Bark() {
    fmt.Println("Bow wow. Woof woof.")
}

func main() {
  // 関数呼び出し
  Bark()
}

出力

Bow wow. Woof woof.

メソッド

package main

import "fmt"

// 型を一つ定義
type Animal struct{ name string }

// これはメソッド
func (animal Animal) Bark() {
    fmt.Printf("[%v] meow.", animal.name)
}

func main() {
    animal := Animal{"Cat"}
    // メソッド呼び出し
    // 型に対してドット(.)でアクセス
    animal.Bark()
}

出力

[Cat] meow.
  • 変数を宣言して、ドット経由で呼び出せます

定義の違い

メソッドの定義は関数定義のfuncの後ろに(<変数> <型>)が追加されています

関数

func Bark() {

メソッド

func (animal Animal) Bark() {
  • この(<変数> <型>)で定義されている関連する型を「レシーバ(receiver)」と呼びます

オブジェクトの表現

Go言語にはクラスという概念がないため、このメソッドとレシーバ(型)との関連でオブジェクトを表現します
※ 言語仕様としてオブジェクトというものは存在しません

例えば、上記サンプルをJavaで表現すると以下のようになります。

class Animal {
  String name;
  
  void bark() {
      System.out.println("Bow wow. Woof woof.");
  }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.bark();
    }
}

Javaのオブジェクト思考の概念では、クラスは「属性(フィールド)」(上ではname)と「操作(メソッド)」(上ではbark)で表現されます。
つまり動物(Animal)というオブジェクトは、名前(name)という属性(データ)で構成されていて、吠える(bark)という操作(振る舞い、動作)をするというような考え方です。

Goの場合は、Javaよりも仕様がシンプルで「型(データ)」がまずあります。
そしてそれは何らかの「関数」によって操作されます。
その特定のデータを操作する特別な関数(振る舞い)を「メソッド」と定義しています。

参考


ちょっとずつ言語仕様を理解していけたらと思います。

【ReDash】AWSのAMIでRedashインスタンスを立ち上げる

EC2インスタンスのセットアップ

  • AMIが提供されているので対象のインスタンスのリンクをクリック
    • クリックすると通常のEC2セットアップ画面に遷移するので環境に合わせた設定でインスタンスを作成する
    • 一応上記リンクではt2.smallが最低として推奨されているようです
      • AMIそのまま起動するとメモリ900M近くを使っているのでsmall以下だと厳しいようです
    • セキュリティグループはhttp(80)とhttps(443)のインバウンドを許可します
  • sshでアクセス
  • とりあえず、パッケージをアップデートする
    • sudo apt-get -u update
    • sudo apt-get -u upgrade
  • redashを最新バージョンにアップデート
    • cd /opt/redash/current
    • sudo bin/upgrade
    • そのまま実行するとAttributeError: 'module' object has no attribute 'SSL_ST_INIT'と出てとまる
    • cd ../redash.<最新バージョン>
    • 依存パッケージをインストール
    • sudo pip install -r requirements_all_ds.txt
    • 完了したら再度以下コマンドでアップデート
    • cd /opt/redash/current
    • sudo bin/upgrade

Redashのセットアップ

  • http://<ip_address>/setupにアクセスするとadmin_userのパスワードを設定できます
  • 右上のデータベースのアイコンを押下し、DataSourcesからNewDataSourceを選択すると接続DBの設定が行えます

【Ethereum】【Solidity】low-level callを使ってコントラクトの関数を実行する

Solidityには、アカウント(アドレス)に対して直接バイトコードを実行する為に、address型の変数に対してcallという関数が用意されています。(low-level callと呼ばれるようです。)

http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses

これは、ConsenSysなどが公開しているMultiSigWalletで使われている仕組みです。
MultiSigWalletは、特定の処理を行う為のコントラクトのアドレスと実行する関数のバイトコードをTransactionとして事前に登録し、規定の人数の承認が得られたタイミングで対象コントラクトの関数を実行する、ということをこのlow-level callを使って実現しています。

具体的な構文としては、以下になります。

<アドレス>.call.(<バイトデータ>)

これを実際にサンプルのContractで動かしてみます。

呼び出し元

pragma solidity ^0.4.17;


contract Caller {

  function callFunc(address destination, bytes data) 
  public
  {
    if (!destination.value(data)) {
      revert();
    }
  }
}
  • 引数destinationは関数を実行する対象のコントラクトのアドレスを指定します
  • dataには関数の実行バイトデータを指定します

実行するターゲットのコントラクト

pragma solidity ^0.4.17;


contract Test {
  uint public num;

  function setNum(uint n) 
  public
  {
    num = n;
  }
}
  • TestコントラクトはsetNumという関数を一つもち、変数numに引数の値をセットしています

テストコード

  • 検証のテストコードは、truffleを使っています。
const Caller = artifacts.require('../contracts/Caller.sol');
const Test = artifacts.require('../contracts/Test.sol');

contract('LowLevelCall', (accounts) => {
  it('Low level call test.', async () => {
    caller = await Caller.new();
    test = await Test.new();
    // 実行前のnumの値
    const before = await test.num();
    console.log("before: " + before);
    // 関数のバイトコードを取り出す
    const data = test.contract.setNum.getData(10);
    console.log("bytecode: " + data);
    // LowLevelCall
    await caller.callFunc(test.address, 0, data);
    // 実行後のnumの値
    const after = await test.num();
    console.log("after: " + after);
  });
});
  • コントラクトのオブジェクトを生成して<コントラクトオブジェクト>.contract.<関数名>.getData(<引数>)バイトコードを取得します
    • ※ truffleの場合は、上記の方法で取得していますが、web3などで取得する場合は若干異なります

テスト実行

truffle(develop)> test
Using network 'develop'.

  Contract: LowLevelCall
before: 0
bytecode: 0xcd16ecbf000000000000000000000000000000000000000000000000000000000000000a
after: 10
    ✓ Low level call test. (202ms)

  1 passing (214ms)
  • Testコントラクトのnum変数が更新されています

同時にEthも送る

call関数は、バイトコードの実行と同時にETHも送ることが可能です。
MultiSigWalletは、その名のとおりWalletなのでバイトコードの実行というよりもこちらの使い方をするほうが多いかと思います。

<アドレス>.call.value(<送信するETH>)(<バイトデータ>)
  • value関数を一緒に呼び出すことETHが送れるようになります
  • これを先程のサンプルコントラクトで試してみます

呼び出し元コントラクトの修正

pragma solidity ^0.4.17;


contract Caller {
  // 引数に送信するETHのvalueを追加
  function callFunc(address destination, uint value, bytes data) 
  public
  {
    if (!destination.call.value(value)(data)) {
      revert();
    }
  }

  // ETHの送信をCaller自体が受け取れる用にpayableの関数を定義
  function () 
  payable public {
  }
}
  • 関数callFuncにETHの送信量を指定する変数valueを追加します
  • コントラクトにETHを送る為のpayableの無名関数を追加します。これはvalue()を実行した時のETHの送り元がCallerコントラクト自身であるため、Callerへ事前にETHを送るためのものです

ターゲットコントラクトの修正

pragma solidity ^0.4.17;


contract Test {
  uint public num;

  // ETHを受け取れる用にpayable修飾子を追加
  function setNum(uint n) 
  payable public
  {
    num = n;
  }
}
  • 実行される関数setNumにpayable修飾子を追加します

テストコード

const Caller = artifacts.require('../contracts/Caller.sol');
const Test = artifacts.require('../contracts/Test.sol');

contract('LowLevelCall', (accounts) => {

  it('Low level call test.', async () => {
    caller = await Caller.new();
    test = await Test.new();
    // 実行前
    const beforeNum = await test.num();
    const beforeBalance = web3.eth.getBalance(test.address);
    console.log("beforeNum: " + beforeNum);
    console.log("beforeBalance: " + beforeBalance);
    // 予めCallerコントラクトに20wei送っておく
    await caller.send(20);
    // 関数のバイトコードを取り出す
    const data = test.contract.setNum.getData(10);
    console.log("bytecode: " + data);
    // LowLevelCall ※10weiも一緒に送る
    await caller.callFunc(test.address, 20, data);
    // 実行後
    const afterNum = await test.num();
    const afterBalance = web3.eth.getBalance(test.address);
    console.log("afterNum: " + afterNum);
    console.log("afterBalance: " + afterBalance);
  });
});
  • まずは、Callerコントラクトに予め送信するための20weiを送っています
  • あとは、callFuncに送信する量(20wei)を追加しているだけです

テスト実行

truffle(develop)> test
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/lowLevelCall/Caller.sol...
Compiling ./contracts/lowLevelCall/Test.sol...

  Contract: LowLevelCall
beforeNum: 0
beforeBalance: 0
bytecode: 0xcd16ecbf000000000000000000000000000000000000000000000000000000000000000a
afterNum: 10
afterBalance: 20

    ✓ Low level call test. (455ms)
  1 passing (466ms)
  • コントラクトTestに20weiが送信されていることが確認できました