まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

golangでコマンドラインツールを作る #4 ファイルをパースする

f:id:masato47744:20150816150451p:plain

その3の続き

project.pbxprojのファイル形式

さて、どうやってパースしようかと考えて中身を見てみると key = valueの形でなんか何かの形式っぽいんだけど、 拡張子が変なやつだからXcode特有かと思ったらそうじゃなかった。 how to parse project.pbxprojとかググってたら出てきた。

Xcode のファイルツリーを名前順に並べたいという話 (フェンリル | デベロッパーズブログ)

この中で以下のように書いてあった。

このファイルは古い plist 形式になっていて、読み書きが大変やりにくいわけです。

そこでさらに調べ見ると、plutil というコマンドがある事を知りました。このコマンドを使用すると古い plist を新しい XML 形式の plist に変換できます。これで project.pbxproj の読み書きが可能になります。

見た目は特殊っぽいけど、plist形式の一種だったんだ。それならMacにそういうコマンドがあっても不思議ではない。 で、plutil*1コマンドを見てみると、どうやらjsonにもformat変換ができるぽいぞ。

 -convert fmt
rewrite property list files in format
fmt is one of: xml1 binary1 json

よし、ということで、plutilコマンドでjsonに変換してそれをgoで扱えばいいかなという方針にする。 pluitilのコマンドはこんな感じ。

$ plutil -convert json -o tmp.json -r project.pbxproj

これで、tmp.jsonに人間でも読めるようにインデントされたjsonが出力される。

外部コマンドを実行する

Golangで外部コマンド実行するにはどうすればいいか調べる。ちゃんと用意されてるね。

exec パッケージ - golang.jp

あとは、コマンドを実行してみる。

   json := "tmp.json"
    cmd := exec.Command("plutil", "-convert", "json", "-o", json, "-r", c.Args()[0])
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()

go installして実行すると、tmp.jsonできてた

{
  "classes" : {

  },
  "objectVersion" : "46",
  "archiveVersion" : "1",
  "objects" : {
    "53DA2D1B1B7CEAF900A18036" : {
      "buildConfigurationList" : "53DA2D3B1B7CEAFA00A18036",
省略

おっけー、あとはこのjsonファイルをgoでパースするだけだ。

jsonをパースする

golangでは標準のjsonパースできるやつが用意されてるようだ。

json パッケージ - golang.jp

やり方としては、パースするjsonの型が分かってればそれをせっせと定義して、Unmarshalすればいいらしい。 でも、pbxprojをjsonにしたやつよく見ると型がめちゃくちゃだった。

外観はこんな感じでkey名が固定されてそうなんだけど、

{
  "classes" : {

  },
  "objectVersion" : "46",
  "archiveVersion" : "1",
  "objects" : {}
  "rootObject" : "53DA2D141B7CEAF900A18036"
}

objectsの中身がてんでバラバラ。しかもkey名が謎の識別子みたいなやつ。

    "53DA2D271B7CEAF900A18036" : {
      "isa" : "PBXBuildFile",
      "fileRef" : "53DA2D251B7CEAF900A18036"
    },
    "53DA2D351B7CEAFA00A18036" : {
      "isa" : "PBXGroup",
      "name" : "Supporting Files",
      "children" : [
        "53DA2D361B7CEAFA00A18036"
      ],
      "sourceTree" : "<group>"
    },
省略

isaってやつがとれればどれが何を指してるかは分かりそう。これはどうやってパースすればいいんだろうか・・ 型が分からないからそういうやり方でやらないといけないかも。

型が分からない場合のjsonのパース

標準パッケージのやり方はInterface{} ってやつで受けて値の型ごとに処理を分割するってやつらしい。 ライブラリで簡単にできる風な感じな bitly/go-simplejson · GitHub でやってみる。 Goで簡単にJSON形式を扱うパッケージ: go-simplejson - Qiitaを参考にあうる。

パッケージをgetする。

$ go get github.com/bitly/go-simplejson

使い方はこんな感じ。stringを取りたい場合は、こう。

v := js.Get("objectVersion").MustString()
fmt.Println("objectVersion = " + v)

rangeってのでmapを回せる。

cs := js.Get("classes").MustMap()
for k, v := range cs {
    fmt.Println(k + " = ")
    fmt.Println(v)
}

rangeで回したあとの、中でまたmapがあった場合は、キャストがいる。 キャストの仕方は、xxxx.(キャストしたい型)てやるようだ。 map[string]interface{}ってなんじゃらほいって感じだ。

objects := js.Get("objects").MustMap()
for _, m := range objects {
    for k, v := range m.(map[string]interface{}) {
        if k == "isa" {
            fmt.Println(k + " = " + v.(string))
        }
    }
}

project.pbxprojをjsonに変換して、それをパースして、各objectのisaを出力するところまできた。

======== classes =========
======== objectVersion =========
objectVersion = 46
======== archiveVersion =========
archiveVersion = 1
======== rootObject =========
rootObject = 53DA2D141B7CEAF900A18036
======== objects =========
isa = PBXNativeTarget
isa = PBXBuildFile
isa = PBXNativeTarget
isa = PBXProject
isa = PBXGroup
isa = PBXGroup
isa = PBXGroup
isa = PBXBuildFile
isa = PBXResourcesBuildPhase
isa = PBXGroup
isa = XCConfigurationList
isa = PBXBuildFile
isa = PBXFrameworksBuildPhase
isa = PBXFrameworksBuildPhase
isa = PBXFileReference
isa = PBXBuildFile
isa = PBXVariantGroup
isa = PBXResourcesBuildPhase
isa = XCConfigurationList
isa = PBXFileReference
isa = XCBuildConfiguration
isa = PBXFileReference
isa = PBXContainerItemProxy
isa = PBXSourcesBuildPhase
isa = XCBuildConfiguration
isa = PBXGroup
isa = PBXFileReference
isa = PBXFileReference
isa = XCBuildConfiguration
isa = PBXBuildFile
isa = PBXSourcesBuildPhase
isa = PBXFileReference
isa = XCBuildConfiguration
isa = PBXGroup
isa = PBXTargetDependency
isa = PBXVariantGroup
isa = PBXFileReference
isa = PBXFileReference
isa = PBXBuildFile
isa = PBXFileReference
isa = XCConfigurationList
isa = XCBuildConfiguration
isa = PBXFileReference
isa = XCBuildConfiguration

あとは、これらのisaをうまい感じにグルーピングしていい感じに表現してあげればよさそう。

ここまでの感想

  • OSコマンドの実行方法と、jsonのパースの仕方がなんとなく分かった。
  • あと、変数名のつけかたの流儀がなかなかつかめない。なんか長い変数名にするのはgoっぽくないってのは聞いたことあるんだけど、 どのくらい短くすればいいのかが感覚としてつかめてない。
  • golang開発してていいなって思ったのは、使ってない変数警告を出すってのはいいかも。
  • atomの予測変換がかなり優秀。IDEかと思っちゃう。
  • interfaceってのがよく出てくるけどよく分かってない。
  • .(string)でキャストできるぽいけどこれ間違っててもランタイムエラーにならないのか?
  • assertionって言葉がよく出てくるけどこれはgolangにそういう仕組みがあるのか?Optional binding的な
  • 使ってもらうときの依存関係はどこに書いておくんだろう?cocoapodsのPodfile的なのいらない?
  • 小文字でprivate functionを表現するということを理解した。
  • log.fatalとpanicの使いどころの違いと、io、ioutilの違いがよく分かってない。

その5

参考URL

*1:よく調べてみると同じか分からないけどperlスクリプトのやつもあるっぽい。 plutil.pl for Windows/Linux

golangでコマンドラインツールを作る #3 ファイルの中身を読み込む

f:id:masato47744:20150816150451p:plain

その2の続きです。

ひとまずファイルを読み込んでみよう

中身をパースとかの前にとにかくファイルが読み込めないといけない。 それっぽい記事を探す。インターネットすごい。

Go でファイルを1行ずつ読み込む(csv ファイルも) - Qiita

ふむ、なんとなく、bufio.Scannerで読み込むというのが推奨と書いてあるしよさそう。 このプログラムはコマンドラインの引数でファイルを指定しているけど、 今回は、codegangsta/cliを使ってるから、os.Argsではとれなそうな雰囲気する。 なので、本家のコマンドライン引数の取り方を調べる。

Argumentsというところにあった。親切だ。c.Args()[0]ってやればとれそう。

...
app.Action = func(c *cli.Context) {
  println("Hello", c.Args()[0])
}
...

これをもとに読み込んだやつをそのまま表示するやつ書いてみた。

func CmdList(c *cli.Context) {

    var fp *os.File
    var err error

    fp, err = os.Open(c.Args()[0])

    if err != nil {
        panic(err)
    }

    scanner := bufio.NewScanner(fp)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        panic(err)
    }

}

panicってのはよく分かってないけど、とりあえず実行してみると、ファイルの中身をprintしてくれた!

あるセクションだけを読み込むということをやってみる

ここからはiOSの話だけど、project.pbxprojファイルはXcode上のプロジェクト構成を表現するファイル。なので、BuildSettingsから、Build Phase、はては、プロジェクトのファイルの並びなどたくさんの情報が入ってる。 それをパースしようって話。なので、まずは中のファイル構造がどうなってるかを調べてみる。

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 46;
    objects = {

/* Begin PBXBuildFile section */
        53DA2D221B7CEAF900A18036 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DA2D211B7CEAF900A18036 /* AppDelegate.swift */; };
        53DA2D241B7CEAF900A18036 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DA2D231B7CEAF900A18036 /* ViewController.swift */; };
        53DA2D271B7CEAF900A18036 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53DA2D251B7CEAF900A18036 /* Main.storyboard */; };
        53DA2D291B7CEAF900A18036 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53DA2D281B7CEAF900A18036 /* Images.xcassets */; };
        53DA2D2C1B7CEAF900A18036 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53DA2D2A1B7CEAF900A18036 /* LaunchScreen.xib */; };
        53DA2D381B7CEAFA00A18036 /* SampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DA2D371B7CEAFA00A18036 /* SampleTests.swift */; };
/* End PBXBuildFile section */

略

だいたいこんな感じ。セクションは、コメントアウトみたいな形で Begin .... section〜 End .... section で区切られている。そして、それぞれは、jsonのような感じで key = value の形式で表現されてる。 ふむふむ、なんとなく規則性はありそうだしパースできそうな雰囲気。

いきなりは難しいので、とりあえず、Beginの文字列を見つけて、Endの文字列が来たら終わりにするみたいなことでセクションをパースしてみる。

逆引き文字列的なやつとリファランスを見つけた。

逆引きGolang (文字列)

strings パッケージ - golang.jp

これで、まずは、文字列があるかないかをチェックするやつを見てみる。多分、strings.Containsをやればよさそう。

Contains関数
func Contains(s, substr string) bool

Containsは、s内にsubstrがあるときtrueを返します。

   for scanner.Scan() {
        t := scanner.Text()
        if strings.Contains(t, "/* Begin") {
            fmt.Println(t)
        }
        if strings.Contains(t, "/* End") {
            fmt.Println(t)
        }

    }

こんな感じに書き換えると出力結果がこう。なんかそれっぽい。

/* Begin PBXBuildFile section */
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
/* End PBXNativeTarget section */
/* Begin PBXProject section */
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
/* End XCConfigurationList section */

ふむふむ。では、これらをsection名だけ表示するようにしてみよう。 /* Beginsection */を削除して表示すればOKっぽいけどどうやろうか。whiteスペースで区切って、必ず、3番目がそうなってるからそれでいいかな。

Fields関数
func Fields(s string) []string
Fieldsは、文字列sをひとつ以上の連続したホワイトスペースで分割し、sの部分文字列の配列を返します。sにホワイトスペースしか含まれていないときは空リストを返します。

ということでプログラムをこう書き換えて

   for scanner.Scan() {
        t := scanner.Text()
        if strings.Contains(t, "/* Begin") {
            fs := strings.Fields(t)
            fmt.Println(fs[2])
        }
    }

結果は、

PBXBuildFile
PBXContainerItemProxy
PBXFileReference
PBXFrameworksBuildPhase
PBXGroup
PBXNativeTarget
PBXProject
PBXResourcesBuildPhase
PBXSourcesBuildPhase
PBXTargetDependency
PBXVariantGroup
XCBuildConfiguration
XCConfigurationList

うむ。よさそうだ。

ここまでの感想

標準のリファレンスがとても分かりやすいと感じた。そして使いたいやつが揃ってる印象。 あと、昔GoTourやったときに、スライスってなんか難しいなって印象あったんだけど、適当に普通の配列っぽくやってみたら動いたしまぁいいかという感じ。 あと、いつのまにか、go installしただけでシェルを立ち上げ直さなくてもOKになった。なぞ。

その4

golangでコマンドラインツールを作る #2 ビルドしてコマンドを実行する

f:id:masato47744:20150816150451p:plain

その1の続きです。

開発環境

早速開発してみる。まずは、開発環境を何にするか問題。 IDEはちょっと大変かなってことでatomでやってる人もいそうなので、それでやってみる。

ATOMでGolangの環境設定 - Qiita

を参考にとにかく入れてみる。go-plusのsettingsが出てこなかったけど再起動したらいけた。 多分syntaxハイライトできてるしよさそう。

開発の進め方

Go言語のインストール - golang.jpにのってたHow to Write Go Code - The Go Programming Languageに、go installみたいなの書いてあるから開発したら、これで確認してくのかな?

ためしに、list.goに以下のようにimport文でfmtをimportしてみるように書いて保存すると、、強制的に元に戻される。なんじゃこりゃw

import(
    "fmt"
)

書き方が間違ってるから強制的に元に戻っちゃうのかな?よくわからん。書き換えてないのに、go-plusのところにはwarningでてるし。

色々悩んでいてふとひらめいた。fmtを使ってないのに、fmtをimportしようとしてるからなんか整理されちゃってるんじゃないかって。 きっと、色々プラグイン入れたからうまいこと保存時にフォーマットされるようになってるんじゃないかと予想。ためしに、関数の中に、fmt.Println("")ってのを書いておいたら大丈夫だった。 ふむ、余計なものをimportするなってことか。

あと、go-plusのエラーで exported function CmdList should have comment or be unexportedって出てる。 うーん。documentかけってことかなと思って色々javadoc的なもの書いてみても消えない。 waning messageに関数名から始めろみたいに書いてあるからまさかと思って、CmdListから始めてみたら・・

// CmdList print list
func CmdList(c *cli.Context) {

warning消えたw

なんかわかってきたかも知れない。ためしに、list.goを以下のように書いてgo installしてみる。

package command

import (
    "fmt"

    "github.com/codegangsta/cli"
)

// CmdList prints list
func CmdList(c *cli.Context) {
    // Write your code here
    fmt.Println("hello world")
}

そして、そのまま xgodeprojって打っても not foundになっちゃうから別シェル立ち上げて打ったら出てきた!

$ xgodeproj list
hello world

よし、なんかいけそうだ。

ここまでの感想

Atomでやってみはじめたけど、予測変換出てくるし、gofmtがあるから勝手にそれっぽくしてくれる。 あとは、ビルド方法が go installして別シェル立ち上げてってのがなんか面倒だな。きっともっといい方法あるんだろうな。 あと、テストをどう書けばいいか想像つかないけどそれは後回しにしよう。

その3

golangでコマンドラインツールを作る #1 gcliをインストールする

なぜ作ろうと思ったか

Go Tourは昔ちょっとやったことあったけどほとんど覚えてないってレベル。でも、これで発表することになったからやるぞってなった。

んで、題材が思いつかなかったけど最近iOSでxcodeproj/project.pbxprojがよくコンフリクトしてるから、あれをコマンドラインでさらっとパースできないかなと思ったので作ることにしてみた。

f:id:masato47744:20150816150451p:plain

何から調べるか

なんか、go getとかGOHOMEとか色々後戻りできない感じがあったようななかったような感じがしたがなんかその辺調べるのめんどくなってきた。とりあえずコマンドラインツールが作れればいいのでと思ってこれを見つけた。

そういえば社内の勉強会でcodegangsta/cli · GitHubよりよさそうみたいなこと言ってた気がするのでこれでやってみるかということに。

まずはGitHubレポジトリを作る

雰囲気出るし。xocdeprojとGolangを組み合わせて、xgodeprojにしてみた。ダサいw

そしたらこれで作ってみる

tcnksm/gcli · GitHub

どうやってインストールすればいいのか。ってかGo入ってんのか?俺の環境。

$ go
zsh: command not found: go

多分入ってない?

Goをインストールする

$ brew install go
==> Downloading https://homebrew.bintray.com/bottles/go-1.4.2.yosemite.bottle.2.
######################################################################## 100.0%
==> Pouring go-1.4.2.yosemite.bottle.2.tar.gz
==> Caveats
As of go 1.2, a valid GOPATH is required to use the `go get` command:
  https://golang.org/doc/code.html#GOPATH

You may wish to add the GOROOT-based install location to your PATH:
  export PATH=$PATH:/usr/local/opt/go/libexec/bin
==> Summary
🍺  /usr/local/Cellar/go/1.4.2: 4566 files, 155M
$ go version
go version go1.4.2 darwin/amd64

多分これでいいのかな。

gcliを取り込む

よくわからないけど多分、go getみたいのすればよさそう。READMEを読もう。Installationで説明してくれてる素晴らしい。 GOPATHとか設定されてないけどとりあえずやってみよう。

$  echo $GOPATH

$  echo $GOROOT

とりあえずなんも入ってない。go get してみると、

$ go get -d github.com/tcnksm/gcli
package github.com/tcnksm/gcli: cannot download, $GOPATH not set. For more details see: go help gopath

go help gopathって書いてある。読んでもすっと頭に入ってこない。 gopath調べるとよさそうな記事発見。

GOPATH は適当に決めて問題ない - Qiita

$HOME/go$HOME/.goが多いようだ。$HOME/.goにしよう。

zshrcの追加。

export GOPATH=$HOME/.go

そして、go getしてみる。が、エラー。

$ go get -d github.com/tcnksm/gcli
# github.com/tcnksm/gcli/skeleton
/Users/mpon/.go/src/github.com/tcnksm/gcli/skeleton/template.go:34: undefined: Asset

GOPATHの下を見ると、何かができてる感は感じられる。

$ ll $GOPATH/*
/Users/mpon/.go/pkg:
total 0
drwxr-xr-x  4 mpon  staff   136B  8 13 18:19 darwin_amd64

/Users/mpon/.go/src:
total 0
drwxr-xr-x  7 mpon  staff   238B  8 13 18:19 github.com
drwxr-xr-x  3 mpon  staff   102B  8 13 18:19 golang.org

Go言語 + cli-init でコマンドラインツールを作る | Developers.IOを真似して、

$ cd $GOPATH
$ go get -d github.com/tcnksm/cli-init
$ make install

をやってみた。けど、make installでこけた。

cd skeleton; go-bindata -pkg="skeleton" resource/...
/bin/sh: go-bindata: command not found
make: *** [install] Error 127

エラーメッセージでググって、go-bindataはなんか別のライブラリかな?

jteeuwen/go-bindata · GitHub

Installしてみる。-uってなんだろう。まぁいいか。

$ go get -u github.com/jteeuwen/go-bindata/

これをやったあともやっぱり、make installでこける。これは、issueないかなと思ったらあったw

/bin/sh: go-bindata: command not found · Issue #23 · tcnksm/gcli · GitHub

$GOPATH/binを$PATHに追加したらいけた!

$ make install
go get -v golang.org/x/tools/cmd/vet
go get -v github.com/golang/lint/golint
go get -v github.com/jteeuwen/go-bindata/...
go get -v -d -t ./...
cd skeleton; go-bindata -pkg="skeleton" resource/...
go install -ldflags "-X main.GitCommit \"$(git describe --always)\""

そして、この$GOPATHの下はgithub.comとかなってて、gcliのREADME読んでて、この$GOPATH/srcの下にに自分のソースコードを作るみたいなことが分かった。

gcliを実行してみる

cd $GOHOME/src/github.com/mpon/xcodeprojに移動して、READMEに沿ってコマンドをたたく。 なんか、codegangstaとかFrameworkが選べるってやつはよく分からなかったから指定しなかった。

gcli new -c list xgodeproj
  Created xgodeproj/version.go
  Created xgodeproj/command/list.go
  Created xgodeproj/CHANGELOG.md
  Created xgodeproj/commands.go
  Created xgodeproj/command/list_test.go
  Created xgodeproj/main.go
  Created xgodeproj/README.md
====> Successfully generated xgodeproj

そしたらxgodeprojがまるまる出来てしまった。そうか、こっちが先だったっぽい。なので、直下にファイルが来るようにmvでせっせと移動する。

出来上がったファイルをmain.goみてみたら、github.com/codegangsta/cliが書いてあるから、何も指定しないとこれになるんだね。あと、READMEとかcommands.goの自分のGitHub名が本名になっててgit configのuser.nameとかみてるのかな?その辺を直した。

テンプレで出来たコマンドが実行できるか確認してみる

$ go build github.com/mpon/xgodeproj
../.go/src/github.com/mpon/xgodeproj/commands.go:7:2: cannot find package "github.com/codegangsta/cli" in any of:
    /usr/local/Cellar/go/1.4.2/libexec/src/github.com/codegangsta/cli (from $GOROOT)
    /Users/mpon/.go/src/github.com/codegangsta/cli (from $GOPATH)
$ go get github.com/codegangsta/cli

go buildしてみるとcodegangsta/cliがないっぽいのでgo getする。 再びgo build するとディレクトリが出来上がった。 そしてコマンドを実行。

$ ./xgodeproj
NAME:
   xgodeproj -

USAGE:
   xgodeproj [global options] command [command options] [arguments...]

VERSION:
   0.1.0

AUTHOR(S):
   Masato Ohshima

COMMANDS:
   list
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h       show help
   --version, -v    print the version

なんか、よさそう。

その2

Swift1.2でenumにジェネリクス使ってその型を束縛できないって言ってるやつの意味が分かった

少し前のことだけど、今のプロジェクトが始まった頃にコード書いてて理解できたのでメモ。

ジェネリクスがない場合

成功か失敗か返すのにEnumを作るとしてジェネリクスがないとこうなっちゃう。

enum Result {
    case Success(AnyObject)
    case Failure(NSError)
}

使う方としては、

let result: Result = .Success("hoge")

switch result {
case .Success(let obj):
    if let str = obj as? String {
        // objがAnyObjectなのでキャストできるか調べないとダメ
        // しかも今回はStringが返ってくるってことを覚えておかないといけない
        println(str) 
    }
case .Failure(let error):
    // こっちはNSErrorって分かってる
    println(error.description)
}

ジェネリクスの場合

こうしたい。

enum Result<T> {
    case Success(T)
    case Failure(NSError)
}

使う方としてはこうあってほしい。

let result: Result = .Success("hoge")

switch result {
case .Success(let str):
        // strはString型って分かってるからキャストいらない
        println(str) 
    }
case .Failure(let error):
    // こっちはNSErrorって分かってる
    println(error.description)
}

でも、コンパイルエラーでダメ

Unimplemented IR generation feature non-fixed multi-payload enum layout

ジェネリクスに渡す値をクラスにする

堅牢で使いやすいAPIクライアントをSwiftで実装したいを参考にBoxを用意してあげる。こう書く。なるほど。

public class Box<T> {
    public let value: T
    
    public init(_ value: T) {
        self.value = value
    }
}

public enum Result<T> {
    case Success(Box<T>)
    case Failure(Box<NSError>)
    
    public init(_ value: T) {
        self = .Success(Box(value))
    }
    
    public init(_ error: NSError) {
        self = .Failure(Box(error))
    }
}

使うほう

let result: Result = .Success(Box("hoge"))

switch result {
case .Success(let box):
        // boxに包まれてる値であるvalueはStringて分かってる
        println(box.value) 
    }
case .Failure(let box):
    // こっちはNSErrorって分かってる
    println(box.value.description)
}

SwiftでClassを作るときにNSObjectを継承するべき?

どっちを採用するべき?

class Animal {}
// or 
class Animal: NSObject {}

こっちのにしておけばとりあえず間違いないとか、こっちを使うとこんな時に困るとかそういう指針が知りたい。

よさそうな回答

stackoverflow.com

  • NSObjectを採用する時

  • Nativeクラスでいいとき

Swift classes that are subclasses of NSObject:

  • are Objective-C classes themselves
  • use objc_msgSend() for calls to (most of) their methods
  • provide Objective-C runtime metadata for (most of) their method implementations

Swift classes that are not subclasses of NSObject:

  • are Objective-C classes, but implement only a handful of methods for NSObject compatibility
  • do not use objc_msgSend() for calls to their methods (by default)
  • do not provide Objective-C runtime metadata for their method implementations (by default)

Subclassing NSObject in Swift gets you Objective-C runtime flexibility but also Objective-C performance. Avoiding NSObject can improve performance if you don't need Objective-C's flexibility.

With Xcode 6 beta 6, the dynamic attribute appears. This allows us to instruct Swift that a method should use dynamic dispatch, and will therefore support interception.

public dynamic func foobar() -> AnyObject {}

Objective-CからSwiftを利用するときには必ずNSObjectをみたいな話

stackoverflow.com

No need for NSObject. Just say public init() {}. – matt Feb 15 at 5:24

これ見るとそんなことする必要ないよって書いてある。

まとめ

native classのままでその方がパフォーマンスがよさそう。なので基本的にNSObjectを継承しない方向で必要な場合は継承するっていうスタンスでOKぽい。

実プロジェクトにおけるSwiftっぽい書き方集が欲しい

勉強会とか開かれないかなー。こんなのを知りたい。自分用メモ。

  • UITableViewのDataSource、DelegateジェネリクスSwiftっぽく分離する。
  • UITableViewのSectionをEnumでうまいこと扱う
  • Structの使いどころ
  • Protocolの使いどころ
  • カテゴリ拡張をProtocol Extensionで代替する
  • viewWillAppearでviewDidLoad並みに1回だけ処理する
  • モックとかスタブとかをライブラリ使わずに書けるみたいなこと
  • Stringで指定してる部分をEnum
  • Enumに値をバインドして使うのはどんなとき
  • 異なるサブクラスreturnする関数
  • ArrayにProtocolを満たしたものだけを突っ込む

・・など

ヒントとなりそうな記事

Swiftでprivateメソッドのユニットテストをどうするか

こんな話は何年も前から語られてて答えも出てると思うけど、色々な意見を聞いて自分の考えが少しずつまとまってきたので残しておく。

聞いた意見

  • publicメソッドをテストすればprivateメソッドのテストも行われていることになる
  • リフレクションやモックなど使えばテストできるかもしれないけどテストをされている状態のほうがマシなのでpublicにしてしまうという手もある
  • そもそもテストしたいと思うということはそこに重要なロジックが入ってる
  • privateなメソッドがテストできないことに悩むのはTDDじゃない証拠
  • 変更後の状態をassertできない場合は確認するためのpublicメソッドを作る。例えアプリからは使われていなくてもテストからは利用してるのだから、テストも一つのアプリとして考えれば良い。

なんとなくまとめ

こういう話を聞いてなんとなく、もっとテストのために楽してもいいんだなという気になった。テストのためにメソッド作るとか元も子もないのかとか考えちゃってたけどテストされてないよりは全然いいよなと。 なのでテストのための確認メソッド作ったり、privateメソッドをpublicにすることにあまり抵抗を感じないでやっていこうという考えに至った。

Swiftでreduceを使ってArrayからDictionaryを作る方法

how to convert array to dictionary swiftとかでググってもあんまでてこないから書いた。 以下、Qiitaに書いたやつ。

qiita.com

Realm meetup#3に行ってきた

概要

http://realm.connpass.com/event/14954/

Realmユーザー、これからRealmを使ってみようという人、その他Realmのテクノロジーに興味のあるひとの情報共有を目的とした会です。

定期的にミートアップやコーディングクラブなどを開催する予定です。

Realm Recent Updates

岸川克己 (@k_katsumi) @ Realm

Realm Cocoa 0.92.3

RealmSwiftがリリース

でも、iOS8からしか使えない プレフィックスがなくなった let realm = Realm()みたいな感じで使える

Transaction中か確認する

-[RLMRealm inWriteTransaction] プロパティを見てわかるようになった。

Realm Java 0.80.1

support for null strings

reoy

吉田 (@reoy_) さん @ リクルートマーケティングパートナーズ (英単語サプリなどのアプリでRealmをお使いいただいています)

質問

データのテーブルの数や構造はどんな感じですか?

  • 8000語
  • 10個ぐらいです

マイグレーションが辛いと言ってたけど、あんまり辛かった印象がない。どう辛かったの?

個人で開発していると仕様変更はないけれど、仕様変更のたびにデータベースの設計のたびに、アプリを削除してもらって配布するというのがコミュニケーションが大変だった。急に落ちる。バージョンのコードを書くのがめんどくさかったという話です。

岸川さんより

  • カスケード削除は、マルチスレッド対応は優先度高で対応している。
  • 純正ブラウザの対応は低めだが、Mac App Storeから配信される予定。
  • マイグレーションも検討中で、一からやり直せるような設定はいれようかと考えている。

shotAlertでRealmを使った話

平塚さん @shunsuke_h2006 株式会社インディバル

shotAlertというアプリ showtworksというサービスのアプリ

初期データを用意して、importプロジェクトを用意して、.realmファイルを作成してiOS/Androidに読み込んでる。

採用した理由 - 初期データインポートのプロジェクトがよういされている。 - Select系の処理がsqliteより高速

初期データのサンプル https://github.com/realm/realm-cocoa/tree/master/examples/osx/objc/JSONImport

マイグレーションの書き方

if (oldVersion > 1) {
[migration enumerateObjects:Area.className
             block:^(RLMObject *oldObject, RLMObject *newObject) {
             }];
}
if (oldVersion > 2) {
   // ほげほげ
}

こういうやつをバージョンごとに書いていく。

Realmの制限

  • Limitがない
    • Limitがなくても全件メモリにのるわけではない
  • Distinctがない
    • 今後追加予定

SansanのEightアプリでRealmを導入した件

今城 (@yimajo) さん @ Sansan株式会社

開発のやり方

プルリクに対して岸川さんがコメントする

なぜRealmを使うか

  • UIViewControllerでNSDictionaryを持って独自でMappingしてた
  • Android版もあった

Realmあるある

サンプルコードが参考になる

https://github.com/realm/realm-cocoa/tree/master/examples/ios/objc/TableView

JSONExportというツールがある

https://github.com/Ahmed-Ali/JSONExport

NSDateが使いにくい

Realmはnullを保存できない

  • 型はNSDateでダミー用のデータをnullにする
    • 設計的にいけてないよね
  • 型をdoubleにしてUNIX時間
  • 日付の項目がnullだというフラグ
    • もし対応がされたとき消すの面倒
  • 型をNSStringで保存する
    • ISO8601形式で保存
    • 利用時にNSDateにパースするみたいな感じ

感想

Realm Swift使ってみたいと思った。

Swiftのif文でwhereを使ったときのelseの条件

ifとwhere書いてるときにふと迷ったので書いた。

qiita.com

SlackにRubotyをいれてみた

同僚がQiita:Teamに導入の仕方を書いてくれていて、すごく簡単そうだったので自分でもやってみようと思って試してみた。RubotyRuby製のBotフレームワーク。Hubotと同じようなもの。

1. 自分用のSlackを作る

どうなってもいいようにと、自分用のSlackを作るところから。SIgn OutしたあとSign up for Freeして、Get a magic emailの方をクリックすれば、メールが送られてきて、自分用のスラックが作れた。

f:id:masato47744:20150315012959p:plain

2. Bot用のアカウントを作る

自分のアカウントからsend invitationsする。

f:id:masato47744:20150315013129p:plain

3. XMPPを有効にする

自分のアカウントでこれをしておかないとダメ。

f:id:masato47744:20150315014410p:plain

4. Botアカウントの認証情報を取得する

Botアカウントでログインする。

Your Accoutn > Settings > Gateway configurationにアクセスする。

f:id:masato47744:20150315014633p:plain

Getting Started: XMPPにある、UserとPassの値をコピーする。

5. r7kamura/ruboty-templateのHerokuボタンからHerokuにデプロイする

Deploy to Herokuボタンをクリックする。

f:id:masato47744:20150315020059p:plain

アプリ名を入れる

f:id:masato47744:20150315020122p:plain

SLACK_TEAMはチーム名、SLACK_PASSWORDSLACK_USERNAMEは4でコピーしたUserPassの値を入力する。

f:id:masato47744:20150315020224p:plain

Deploy for Freeボタンをクリックする。すると、デプロイが開始される。

f:id:masato47744:20150315020314p:plain

make your first editのlinkから設定画面に飛んでDynoを1にする

f:id:masato47744:20150315020415p:plain

マジでできてた

f:id:masato47744:20150315020455p:plain

f:id:masato47744:20150315020511p:plain

感想

簡単すぎワロタw
Helokuボタンって凄いな。一体何が起きてるんだろう??そして、これ金かかったりしないのかな?w

Unityを学び始めたときのメモ

Unityではゲーム全体をプロジェクトと呼ぶ。 そしてプロジェクト複数シーンを持つことができる。 シーンには、ゲームオブジェクトという要素を配置する。

メインカメラシーンをどの角度でどの位置から見るかを定義して、照明を配置する。

一つ一つのゲームオブジェクトに対して、スクリプトを設定できる。 スクリプトC#javascriptで書くことができる。 startメソッドupdateメソッドが定義されていて、そこに処理を書いていく。

直線運動、バウンドなど物理エンジンは自分で計算する必要はない。 物理計算を行うオブジェクトが用意されていて、 そのオブジェクトのことをコンポーネントと呼ぶ。

例えば、音を出す機能もコンポーネントという単位で作成する。 ゲームオブジェクトコンポーネントを合成していって、 ゲームを作っていく

色も同じ考え方でマテリアルというオブジェクトがあって、 ゲームオブジェクトに対してマテリアルを登録していく。

iOSでのKenBurns効果

ポテチでKenBurns効果の発表がされてていいなと思ったのでちょっと調べたメモ。

発表の時に映画が趣味でとおっしゃっていて、だからこれに気づいたとのこと。そういう一見アプリ開発と関係ないことも何かに生きてくるってのはいい話だなぁーと思いながら聞いてた。

はじめてのKenBurnsEffect // Speaker Deck

KenBurns効果調査

JBKenBurnsにあったKen Burns効果のデモ

JBKenBurns

Ken Burns効果のYoutube


&quot;Ken Burns Effect&quot; demonstration - YouTube


Ken Burns Effect Slideshow for iOS - YouTube

iOS (1:02あたりから)

iOS実装 Stackoverflow

iOSでカメラと写真の利用許可の確認方法

そもそもどんな制限が可能か

  • 機能制限(デバイス全体で使わせない)
  • プライバシー制限(アプリごとに選べる)

iOS7とiOS8でブライバシーの概念が違う

要はカメラの利用をアプリにさせるかってのはiOS8からできるようになったって話。

OS カメラ 写真(ライブラリ)
iOS7 制限なし ユーザーが選べる
iOS8 ユーザーが選べる ユーザーが選べる

カメラの利用制限の確認方法

カメラ機能制限の確認

機能制限の確認方法なんだけど、AVAuthorizationStatusRestrictedがあるからそれでいけんのかと思ったんだけど、そこに来ない。isSourceTypeAvailableで見るしかないっぽい。カメラの機能制限か、単純にデバイスにカメラが存在しない(昔のiPodとか?)の場合。ただしどちらかは判別不可能。

if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
    // do something
}

カメラプライバシーの確認

AVAuthorizationStatusがあるからそれで簡単にわかる。インストール直後は、AVAuthorizationStatusNotDeterminedに来るのでそこでユーザーに確認するダイアログを表示して、そこにブロックで処理を書いておく。

AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    
switch (status) {
    case AVAuthorizationStatusAuthorized:
        // プライバシー設定でカメラの使用が許可されている場合
        break;
    case AVAuthorizationStatusDenied:
        // プライバシー設定でカメラの使用が禁止されている場合
        break;
    case AVAuthorizationStatusRestricted:
        // 機能制限の場合とあるが、実際にこの値をとることがなかった
        break;
    case AVAuthorizationStatusNotDetermined:
        // 初回起動時に許可設定を促すダイアログが表示される
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if (granted) {
                // 許可された場合の処理
                dispatch_async(dispatch_get_main_queue(), ^{
                    // do something
                });
            } else {
                // 許可してもらえない場合
                dispatch_async(dispatch_get_main_queue(), ^{
                    // do something
                });
            }
        }];
        break;
    default:
        break;
}

写真の利用制限の確認方法

写真の方は機能制限もプライバシーもALAuthorizationStatusで素直に確認できた。

ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];

switch (status) {
    case ALAuthorizationStatusAuthorized:
        // 写真へのアクセスが許可されている状態
        break;
    case ALAuthorizationStatusNotDetermined:
    {
        // 初回起動時に許可設定を促すダイアログが表示される
        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        [library enumerateGroupsWithTypes:ALAssetsGroupAll
                               usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                                   // 許可された場合
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                       // do something
                                   });
                                }
                              failureBlock:^(NSError *error) {
                                   // 許可してもらえない場合
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                   });
                               }];
    }
        break;
    case ALAuthorizationStatusDenied:
        // プライバシーで許可されていない状態
        break;
    case ALAuthorizationStatusRestricted:
       // 機能制限されている場合
        break;
    default:
        break;
}

iOS8から設定画面にアプリの設定画面に飛べるようになった

NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:settingsURL];

NotDetermindのテストが面倒過ぎてヤバいと思ったけど・・!!

Stackoverflowで探しててアプリ消して再起動して時間を動かしてやらないといけないって書いてあって死ぬかと思ってた。でも、コメントにちょう簡単な方法があった。iPhoneの設定画面からリセットできる。開発端末ならいくらでもリセットしても気にならないしね。

rmaddyさんのコメント

Why not simply do it the easy way? Settings -> General -> Reset -> Rest Location & Privacy – rmaddy Jan 1 at 0:23

これでリセットできます。

Settings -> General -> Reset -> Rest Location & Privacy

参考記事

  1. Xcode5によるiOS8対応 - Qiita
  2. iOS Tips #6 写真のアクセス制限 | Developers.IO
  3. カメラ, カメラロール, フォトアルバムから画像を選択してメール添付する - Javaな日々