golangでコマンドラインツールを作る #4 ファイルをパースする
その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で外部コマンド実行するにはどうすればいいか調べる。ちゃんと用意されてるね。
あとは、コマンドを実行してみる。
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の型が分かってればそれをせっせと定義して、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の違いがよく分かってない。
参考URL
- json パッケージ - golang.jp
- JSONの処理 | build-web-application-with-golang
- simplejson - GoDoc
- Goで簡単にJSON形式を扱うパッケージ: go-simplejson - Qiita
- Go maps in action - The Go Blog
- Discussion of the Go Programming Language ()
- Go言語のパッケージのテストでプライベートな関数や変数を呼び出すには - memoメモ
- os パッケージ - golang.jp
*1:よく調べてみると同じか分からないけどperlのスクリプトのやつもあるっぽい。 plutil.pl for Windows/Linux
golangでコマンドラインツールを作る #3 ファイルの中身を読み込む
その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の文字列が来たら終わりにするみたいなことでセクションをパースしてみる。
逆引き文字列的なやつとリファランスを見つけた。
これで、まずは、文字列があるかないかをチェックするやつを見てみる。多分、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名だけ表示するようにしてみよう。
/* Begin
とsection */
を削除して表示すれば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になった。なぞ。
golangでコマンドラインツールを作る #2 ビルドしてコマンドを実行する
その1の続きです。
開発環境
早速開発してみる。まずは、開発環境を何にするか問題。 IDEはちょっと大変かなってことでatomでやってる人もいそうなので、それでやってみる。
を参考にとにかく入れてみる。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
して別シェル立ち上げてってのがなんか面倒だな。きっともっといい方法あるんだろうな。
あと、テストをどう書けばいいか想像つかないけどそれは後回しにしよう。
golangでコマンドラインツールを作る #1 gcliをインストールする
なぜ作ろうと思ったか
Go Tourは昔ちょっとやったことあったけどほとんど覚えてないってレベル。でも、これで発表することになったからやるぞってなった。
んで、題材が思いつかなかったけど最近iOSでxcodeproj/project.pbxprojがよくコンフリクトしてるから、あれをコマンドラインでさらっとパースできないかなと思ったので作ることにしてみた。
何から調べるか
なんか、go getとかGOHOMEとか色々後戻りできない感じがあったようななかったような感じがしたがなんかその辺調べるのめんどくなってきた。とりあえずコマンドラインツールが作れればいいのでと思ってこれを見つけた。
そういえば社内の勉強会でcodegangsta/cli · GitHubよりよさそうみたいなこと言ってた気がするのでこれでやってみるかということに。
まずはGitHubのレポジトリを作る
雰囲気出るし。xocdeprojとGolangを組み合わせて、xgodeprojにしてみた。ダサいw
そしたらこれで作ってみる
どうやってインストールすればいいのか。ってか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調べるとよさそうな記事発見。
$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
はなんか別のライブラリかな?
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
なんか、よさそう。
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 {}
こっちのにしておけばとりあえず間違いないとか、こっちを使うとこんな時に困るとかそういう指針が知りたい。
よさそうな回答
NSObjectを採用する時
- Objective-Cのクラス
- メソッドを呼ぶ時にobjc_msgSend()を使ってる
- Objective-Cのランタイム時に取得する情報を使ってる時
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をみたいな話
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に書いたやつ。
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
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書いてるときにふと迷ったので書いた。
SlackにRubotyをいれてみた
同僚がQiita:Teamに導入の仕方を書いてくれていて、すごく簡単そうだったので自分でもやってみようと思って試してみた。RubotyはRuby製のBotフレームワーク。Hubotと同じようなもの。
1. 自分用のSlackを作る
どうなってもいいようにと、自分用のSlackを作るところから。SIgn OutしたあとSign up for Freeして、Get a magic email
の方をクリックすれば、メールが送られてきて、自分用のスラックが作れた。
2. Bot用のアカウントを作る
自分のアカウントからsend invitationsする。
3. XMPPを有効にする
自分のアカウントでこれをしておかないとダメ。
4. Botアカウントの認証情報を取得する
Botアカウントでログインする。
Your Accoutn > Settings > Gateway configuration
にアクセスする。
Getting Started: XMPPにある、UserとPassの値をコピーする。
5. r7kamura/ruboty-templateのHerokuボタンからHerokuにデプロイする
Deploy to Heroku
ボタンをクリックする。
アプリ名を入れる
SLACK_TEAM
はチーム名、SLACK_PASSWORD
とSLACK_USERNAME
は4でコピーしたUser
とPass
の値を入力する。
Deploy for Free
ボタンをクリックする。すると、デプロイが開始される。
make your first edit
のlinkから設定画面に飛んでDynoを1にする
マジでできてた
感想
簡単すぎワロタw
Helokuボタンって凄いな。一体何が起きてるんだろう??そして、これ金かかったりしないのかな?w
Unityを学び始めたときのメモ
Unityではゲーム全体をプロジェクト
と呼ぶ。
そしてプロジェクト
は複数のシーン
を持つことができる。
シーン
には、ゲームオブジェクト
という要素を配置する。
メインカメラ
でシーン
をどの角度でどの位置から見るかを定義して、照明を配置する。
一つ一つのゲームオブジェクト
に対して、スクリプト
を設定できる。
スクリプト
はC#かjavascriptで書くことができる。
start
メソッドとupdate
メソッドが定義されていて、そこに処理を書いていく。
直線運動、バウンドなど物理エンジンは自分で計算する必要はない。
物理計算を行うオブジェクトが用意されていて、
そのオブジェクトのことをコンポーネント
と呼ぶ。
例えば、音を出す機能もコンポーネント
という単位で作成する。
ゲームオブジェクト
にコンポーネント
を合成していって、
ゲームを作っていく
色も同じ考え方でマテリアル
というオブジェクトがあって、
ゲームオブジェクト
に対してマテリアル
を登録していく。
iOSでのKenBurns効果
ポテチでKenBurns効果の発表がされてていいなと思ったのでちょっと調べたメモ。
発表の時に映画が趣味でとおっしゃっていて、だからこれに気づいたとのこと。そういう一見アプリ開発と関係ないことも何かに生きてくるってのはいい話だなぁーと思いながら聞いてた。
はじめてのKenBurnsEffect // Speaker Deck
KenBurns効果調査
JBKenBurnsにあったKen Burns効果のデモ
Ken Burns効果のYoutube
"Ken Burns Effect" 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