Gitについて自分なりに説明してみた
友達に説明する必要があったのでGitの使い方を誤解を恐れずメモしておきます。自分の知識の整理も兼ねています。
この記事ではローカルマシン(一台のPC)での操作で完結しています。リポジトリの作成、コミットの作成、ブランチの作成、マージまでを範囲としています(これらの単語は順を追って説明していけたら、と思います)(ブランチの扱いは、最終的なリポジトリの形としてはgithub-flow
です。)
Gitの概要
Gitは 分散型バージョン管理システム です。というとやばく聞こえますが、Gitを使う目的は 「大勢で1つのアプリケーションを一緒に作ること」 です。なのでそのために必要そうなバージョン管理機能がちゃんと備わっています。これあるべきでしょうとか予想しながら読んでいっていただけるといいかと思います。以下チュートリアルを通して概念と機能を説明していきます。
この記事を読み進めるために
- 環境: Mac (他の環境の場合、概念と機能の説明は問題ありませんが、手順に関して読み替えが必要です)
- 知識: ターミナルの基本的な操作 (自信がなくなった場合、こちらかこちらを適宜参考にしながら読んでいただければ幸いです)
チュートリアルを始める前にちょっとした下準備を行います。
(下準備)gitコマンドがインストールされているか確認
今回は CLI での使い方を説明していきます。他の使い方としては、GUIアプリケーションなど沢山開発されているのでそれ経由で操作するのもありです。(その場合GUIアプリの解説ページに色々書いてあると思うので、この記事は閉じてしまって大丈夫です!)
Macを使っている場合、すでにGitがインストールされているかと思います。アプリケーションフォルダのユーティリティの中にある ターミナル.appを開いて、Gitがインストールされていることを確認してみましょう。ターミナルを開いたらgit --version
と入力して、実行してください。
$ git --version git version 2.13.6 (Apple Git-96)
git version ...
のような表示が出たらインストールされています。
$ git --version -bash: git: command not found
と出たらインストールされていないので、ググってインストールしてください。
(下準備)Gitの初期設定
以下のコマンドを実行して、Gitのユーザー名、アドレスが登録されていることを確認してください。
$ git config user.name ushumpei $ git config user.email mail@ushumpei.com
登録されていない(実行しても何も表示されない)場合は次のコマンドを実行してユーザー情報を登録しておいてください。
$ git config --global user.name "あなたの名前" $ git config --global user.email あなたのアドレス
チュートリアル
リポジトリ作成
Gitによるバージョン管理はファイル単位で行われます。と言ってもコンピューターすべてのファイルを管理するわけではなく、管理する範囲は限定することができます。限定できる範囲はディレクトリ(フォルダ)単位で、Gitによって管理されるのはそのディレクトリに含まれているすべてのファイルになります。この限定された範囲を リポジトリ(repository) と呼びます。
実際にリポジトリを作ってみましょう。デスクトップにgit_tutorial
リポジトリを作ります。
ターミナルで 1.デスクトップに移動
2.git_tutorialディレクトリの作成
3.新規ディレクトリ内へ移動
4.ディレクトリのgitリポジトリ化
を行います。TAB
キーでファイル名を補完したりして頑張ってください。
~$ cd ~/Desktop/ ~/Desktop$ mkdir git_tutorial ~/Desktop$ cd git_tutorial ~/Desktop/git_tutorial$ git init Initialized empty Git repository in /Users/ushumpei/Desktop/git_tutorial/.git/
Initialized empty Git repository in ...
が出たら成功です。リポジトリが完成しました。
注意: もしかすると日本語環境の場合~/Desktop
がないかもしれないです(この辺りは自信ないです)。~/デスクトップ
など試してみてください。
git status
というコマンドを実行して見てください。このコマンドは現在のリポジトリの状態を表示してくれるものです。 とてもよく使います 、他のコマンドを打つ際もこのコマンドで実行前、実行後の変化の確認をしながらやると何をしているかわかってくると思います。
~/Desktop/git_tutorial$ git status On branch master Initial commit nothing to commit (create/copy files and use "git add" to track)
各行の意味は以下です。不明な単語については後ほど説明いたします。
- 現在は
master
という ブランチ(branch)にいる - まだ コミット(commit) が存在しない
- コミットの対象に含まれているものはない
Initial commit
管理下にあるファイルに対して、「誰が、いつ、なぜ、どのファイルの、どこの部分を、どのように変更したか(変更に関する5W1H)」という変更情報を管理するのがGitの大きな役割です。この変更情報は コミット(commit) と呼ばれます。コミットは開発者が好きな粒度で作成することができます、複数ファイルの変更を1つのコミットに含めるのが一般的です。
(コミットのイメージ) 変更理由: モバイルブラウザでトップページの挨拶文の表示が崩れてしまっていたので修正 日付: 2017/11/27 変更者: ushumpei index.htmlというファイルの12行目を`<p>こんにちは</p>`から`<h1>こんにちは</h1>`に変更 stylesheet/mobile.cssというファイルを追加
コミットを作成する手順は次のようになります;
- 変更: リポジトリ内でファイルに関する変更を行う(作成、更新、削除のどれか)
- ステージ:
git add
、git rm
などのコマンドで、どの変更内容を履歴に残したいかGitに知らせる - コミット:
git commit
コマンドで変更履歴を作成する
超個人的なイメージではGit管理下のファイルを変更したら「変更内容の紙が出てくる(変更) -> 変更に含めたい紙を束ねる(ステージング) -> キリのいいところで束を箱に入れる(コミット)」ということをやっている感じです。
では実際にファイルを作り、Gitの管理下に追加してみます。1.ファイルの作成
2.ファイルを追加対象に含める
3.追加
の流れになります。git status
を使いながら1つ1つ変化を追って確認していきましょう。
~/Desktop/git_tutorial$ git status On branch master Initial commit nothing to commit (create/copy files and use "git add" to track)
先ほどと同じ状態です。
~/Desktop/git_tutorial$ touch index.html # index.htmlが作成されたので、好きなエディタで開いて以下の内容を記述して保存してください(# は不要です); # <html> # <head> # <title>git_tutorial</title> # </head> # <body> # <p>Git Tutorial</p> # </body> # </html> ~/Desktop/git_tutorial$ git status On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) index.html nothing added to commit but untracked files present (use "git add" to track)
git status
によると、依然としてコミットの対象に含まれているものはないですが、新しくindex.html
というファイルが作成されていて、まだGit管理下に入っていないという情報が表示されています。
Git管理下に含めるにはgit add
コマンドを実行してくださいという指示が括弧中に記載されているので実行します。
~/Desktop/git_tutorial$ git add index.html ~/Desktop/git_tutorial$ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: index.html
git status
によると、コミットの対象に「index.htmlを新規作成した」という変更を追加する準備ができたことが表示されています。この状態をindex.html
の変更が ステージ(stage) されていると言い、この変更のステージを取りやめることを アンステージ(unstage) すると言い、git rm --cached index.html
(場合に応じてgit reset HEAD index.html
)でGitに知らせることができます。
最後にステージされている変更からコミットを作成してみましょう。
~/Desktop/git_tutorial$ git commit -m "Initial commit" [master (root-commit) c04dc8f] Initial commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 index.html ~/Desktop/git_tutorial$ git status On branch master nothing to commit, working tree clean
上記のように、追加に関する情報が表示されたら成功です。git status
の結果は変更を全く行なっていない状態に戻っているかと思います。
これで 変更->ステージ->コミット の1サイクルが終わりました。Initial commitの部分は変更理由を記述します。慣習としてリポジトリを作成した直後のコミットはInitial commitと書くことが多いです。
作成したコミットを見る: git log
作成したコミットを見て見ましょう。git log
、git log -p
を実行してください。
~/Desktop/git_tutorial$ git log commit c04dc8ffd000dd7ba23b20177c208e9095901d19 (HEAD -> master) Author: ushumpei <shumpei.uzawa@gmail.com> Date: Tue Nov 28 00:59:56 2017 +0800 Initial commit ~/Desktop/git_tutorial (master)$ git log -p commit c04dc8ffd000dd7ba23b20177c208e9095901d19 (HEAD -> master) Author: ushumpei <shumpei.uzawa@gmail.com> Date: Tue Nov 28 00:59:56 2017 +0800 Initial commit diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29
上記のコマンドで過去に行なったコミット内容が全て確認できます。変更の詳細がいらない場合はgit log
、詳細を見たい場合はgit log -p
変更履歴が確認できます。
更新
続いてindex.html
を更新してコミットして見ましょう。index.html
をエディタで開いて以下のように修正してください。(6行目のpタグをh1タグに変更します)
... <h1>Git Tutorial</h1> ...
git status
を実行して変化を見てみてください。
~/Desktop/git_tutorial$ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: index.html no changes added to commit (use "git add" and/or "git commit -a")
index.html
が変更されたことが表示されています。もしstageしたいなら先ほどと同様にgit add index.html
、変更を破棄して最後のコミットの状態に戻したいならgit checkout index.html
を実行してくださいという指示が書かれています。
ここでどんな変更が行われたかを確認するコマンドgit diff
を使ってみましょう。
~/Desktop/git_tutorial$ git diff diff --git a/index.html b/index.html index 135d8e3..243ca88 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,6 @@ <title>git_tutorial</title> </head> <body> - <p>Git Tutorial</p> + <h1>Git Tutorial</h1> </body> </html>
index.html
に対する変更が表示されています。先ほど行なった通り、pタグの行が削除され(- <p>Git Tutorial</p>
)、h1タグの行が追加され(+ <h1>Git Tutorial</h1>
)ていることがわかります。そのまんまですが、行頭の-
は削除、+
は追加の意味です。(差分は文字単位ではなく、行単位での表示となります)
この変更で問題ないのでステージしてコミットしましょう。
~/Desktop/git_tutorial$ git add index.html ~/Desktop/git_tutorial$ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: index.html ~/Desktop/git_tutorial$ git commit -m "ページタイトルなのでh1にタグを変更" [master 081f264] ページタイトルなのでh1にタグを変更 1 file changed, 1 insertion(+), 1 deletion(-) ~/Desktop/git_tutorial$ git status On branch master nothing to commit, working tree clean
1サイクルが終わりました。これまで行なった作業を確認するために、git log --graph
を実行してみてください。2つのコミットが追加されていることが確認できるかと思います。
~/Desktop/git_tutorial$ git log --graph * commit 081f264cc85e5074a93f7304ba2febf9efb90bd9 (HEAD -> master) | Author: ushumpei <shumpei.uzawa@gmail.com> | Date: Tue Nov 28 10:17:57 2017 +0800 | | ページタイトルなのでh1にタグを変更 | * commit b80d4812cbde6655bee82bfa20127519e8826b81 Author: ushumpei <shumpei.uzawa@gmail.com> Date: Tue Nov 28 10:02:38 2017 +0800 Initial commit
--graph
オプションをつけて実行したので、2つのコミットが線で繋がって表示されています。これは ブランチ(branch) と呼ばれる概念に関係しています。
ブランチ(branch)
ブランチ(branch) について説明します。ブランチは連続したコミットの集まりです。先ほど作った2つのコミットはmaster
ブランチの先頭に追加されています、どんどんコミットを重ねてブランチ(枝)を伸ばしていくイメージです。
ブランチと呼ばれるからには 枝分かれ させることができます、ブランチの途中から別の ブランチを生やして そちらを伸ばしていくことができます。この機能により、ブランチを分けることによって複数の開発者が互いに影響を受けずに並列に開発を行っていくことができます。(あとで出てきますが、枝分かれしたブランチは好きなタイミングで他のブランチに合流させることができます)
リポジトリにどんなブランチがあるのかはgit branch
コマンドで確認できます。
~/Desktop/git_tutorial$ git branch * master
Gitリポジトリを作った直後、ブランチはmaster
しかありません。先ほどのindex.html
に関する変更はmaster
に追加していました。master
から違うブランチを生やしてコミットを追加してみましょう。ブランチの作成はgit branch ブランチ名
です。
# 一行目より現在masterブランチにいることがわかります ~/Desktop/git_tutorial$ git status On branch master nothing to commit, working tree clean # stylingブランチを作成 ~/Desktop/git_tutorial$ git branch styling # ブランチ一覧にstylingが表示されています、「*」は現在いるブランチを表しています ~/Desktop/git_tutorial$ git branch * master styling # git checkout ブランチ名でstylingブランチに移動します ~/Desktop/git_tutorial$ git checkout styling Switched to branch 'styling' # 一行目より現在stylingブランチにいることがわかります ~/Desktop/git_tutorial$ git status On branch styling nothing to commit, working tree clean # ブランチ一覧でも同様に確認できます ~/Desktop/git_tutorial$ git branch master * styling
それではstyling
ブランチの先頭にコミットを追加します。スタイルシートファイルstyle.css
を作成して、index.html
でそれを読み込んでスタイルを適用しましょう。
~/Desktop/git_tutorial$ touch style.css # style.cssを好きなエディタで開いて以下の内容を記述して保存してください; # h1 { # color: #727272; # font-family: monospace; # } # # index.htmlは次のようにheadタグ内にlinkタグを追加してください; # <html> # <head> # <title>git_tutorial</title> # + <link rel="stylesheet" href="./style.css" /> # </head> # ... ~/Desktop/git_tutorial$ open index.html # ブラウザで表示してみてもいいかもです # 変更の確認をしましょう。index.htmlが変更され、style.cssが追加されています ~/Desktop/git_tutorial$ git status On branch styling Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: index.html Untracked files: (use "git add <file>..." to include in what will be committed) style.css no changes added to commit (use "git add" and/or "git commit -a") # index.html、style.cssをステージします ~/Desktop/git_tutorial$ git add . # 2つのファイル変更がステージされています ~/Desktop/git_tutorial$ git status On branch styling Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: index.html new file: style.css # コミットします。現在stylingブランチにいるのでコミットはstylingブランチの先頭に追加されます(stylingブランチが1つ伸びます) ~/Desktop/git_tutorial$ git commit -m "スタイルシートを追加" [styling 59df46b] スタイルシートを追加 2 files changed, 5 insertions(+) create mode 100644 style.css
コミットが追加されました。ブランチの形を確認してみましょう。
~/Desktop/git_tutorial$ git log --graph * commit 59df46ba1df6e9a29584dd2ad2d95b79248f0409 (HEAD -> styling) | Author: ushumpei <mail@ushumpei.com> | Date: Tue Nov 28 11:10:48 2017 +0800 | | スタイルシートを追加 | * commit 081f264cc85e5074a93f7304ba2febf9efb90bd9 (master) | Author: ushumpei <mail@ushumpei.com> | Date: Tue Nov 28 10:17:57 2017 +0800 | | ページタイトルなのでh1にタグを変更 | * commit b80d4812cbde6655bee82bfa20127519e8826b81 Author: ushumpei <mail@ushumpei.com> Date: Tue Nov 28 10:02:38 2017 +0800 Initial commit
3つめのコミットが今追加したものです。今は直線で表示されているので枝分かれ感が薄いですが、2つめのコミットからstylingブランチがmasterブランチから分かれて、1コミット分伸びたという情報が表示されています。このコミットの変更はmasterブランチには影響しません。そのことをmasterブランチに移動して確認してみましょう。ブランチの移動はgit checkout ブランチ名
です。
# masterブランチへ移動 ~/Desktop/git_tutorial$ git checkout master Switched to branch 'master' # 状態の確認 ~/Desktop/git_tutorial$ git status On branch master nothing to commit, working tree clean # ファイルの確認 ~/Desktop/git_tutorial$ ls index.html
先ほど追加したファイルstyle.css
が存在しないことがわかります。またmasterブランチ上でgit log
を行うとまだコミットが2つしかないことが確認できます。
~/Desktop/git_tutorial$ git log commit 081f264cc85e5074a93f7304ba2febf9efb90bd9 (HEAD -> master) Author: ushumpei <mail@ushumpei.com> Date: Tue Nov 28 10:17:57 2017 +0800 ページタイトルなのでh1にタグを変更 commit b80d4812cbde6655bee82bfa20127519e8826b81 Author: ushumpei <mail@ushumpei.com> Date: Tue Nov 28 10:02:38 2017 +0800 Initial commit
(補足) なぜブランチを分けるか?全てmaster
にコミットしないのはなぜ?
それは多くの場合、master
ブランチは実際にユーザーが使っているアプリケーションのソースコードになることが多いからです。レビュー、テストを経て開発者みんなで合意したソースコードのみがmaster
に存在します。なのでmaster
ブランチへの変更は完全な状態で行われなければいけません。作業途中のコードを追加するといったことは避けるべきです。しかし開発作業では区切りのいいところでコミットしていく方が、全てのコードを書き終わってからコミットする(ゲームをセーブしないでクリアするようなものです)よりも断然楽です。なので開発作業を行う場所として新たにブランチを作成し、完成したらmaster
にマージするというのがより優れた方法です。
マージ(merge)
現在このリポジトリには2つのブランチが存在しています。master
とstyling
です。流儀にもよりますが、基本的にGitではmaster
ブランチに全ての作業が集約していきます。スタイル設定作業が一旦落ち着いたのでstyling
で行なった仕事をmaster
ブランチに適応しましょう。この別のブランチの変更を別のブランチに適応する作業を マージ(merge) と呼びます。分かれていた枝をくっつけるイメージです。超個人的なイメージでは、master
ブランチからstyling
ブランチの先っぽを掴んで、master
ブランチにくっつけてぎゅっとコブを作る感じです。
styling
をmaster
にマージします。コマンドはgit merge --no-ff styling
になります(--no-ff
はマージした時にコブを作るおまじないです。マージには3つくらい種類がありますが、この方法が複数人の開発では重宝されています)。この時どちらがどちらにマージされるかというのは重要で、自分がmaster
ブランチにいることを確認した上でマージを行なってください。
~/Desktop/git_tutorial$ git status On branch master nothing to commit, working tree clean ~/Desktop/git_tutorial$ git merge --no-ff styling # 実行後適当なエディタが開かれます。使い方が不明なら、vimなら`ZZ`、nanoなら`Ctrl + x`を入力してください Merge made by the 'recursive' strategy. index.html | 1 + style.css | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 style.css
マージが実行されました。git log --graph
で履歴を見ると、4つのコミットが表示されています。このことはstyling
ブランチにあった「スタイルシートを追加」のコミットがmaster
ブランチに適応されたこと、マージしたという事実を示す新しいマージコミット(merge commit)が作成されたこと、を示しています。
~/Desktop/git_tutorial$ git log --graph * commit c45179e33ebf642db092e04130bbd981078cb6f9 (HEAD -> master) |\ Merge: 081f264 59df46b | | Author: ushumpei <mail@ushumpei.com> | | Date: Tue Nov 28 11:46:57 2017 +0800 | | | | Merge branch 'styling' | | | * commit 59df46ba1df6e9a29584dd2ad2d95b79248f0409 (styling) |/ Author: ushumpei <mail@ushumpei.com> | Date: Tue Nov 28 11:10:48 2017 +0800 | | スタイルシートを追加 | * commit 081f264cc85e5074a93f7304ba2febf9efb90bd9 | Author: ushumpei <mail@ushumpei.com> | Date: Tue Nov 28 10:17:57 2017 +0800 | | ページタイトルなのでh1にタグを変更 | * commit b80d4812cbde6655bee82bfa20127519e8826b81 Author: ushumpei <mail@ushumpei.com> Date: Tue Nov 28 10:02:38 2017 +0800 Initial commit
まとめ
長い文章を読んでくださってありがとうございました。またはまとめを見てくださってありがとうございます。とりあえず基本的な概念の説明を自分なりにまとめて見ただけです。わかりにくい部分、間違っている部分、お気づきになりましたらお知らせ下さい。(コメント、メール、twitter#など最大限対応します)
おまけ
- リポジトリ(repository)は、枝分かれしたブランチ達を持っている(木みたいなもの)
- ブランチ(branch)は、連続したコミット達から成り立っている(枝みたいなもの)
- コミット(commit)は、複数ファイル達に対する変更履歴で出来ている(コブみたいなもの)
といった形です。Gitはこれら3つの対象に対して、様々な操作を行える機能を提供してくれるツールです。Gitは 分散型バージョン管理システム でその目的は 「大勢で1つのアプリケーションを一緒に作ること」 です。今回は バージョン管理システム の側面をクローズアップしてましたので、次回は 分散型 と言われる所以となる機能について書きたいと思います。
最低限必要なGitコマンド
友人に説明する必要性が出てきたので頭の整理を兼ねてメモしておきます。どんなものがあるでしょうか?自分的には最低限以下のやつらかと思います。(merge
とかはプルリクエストで行うので直接打たない想定)
status
, log
, diff
, pull
, push
, checkout
, add
, commit
, reset
めちゃくちゃ主観的なワークフロー的なものを描いて見ました。基本的には、新しいブランチ作る
、コミットいくつか作る
、リモートにブランチをプッシュする
の一連の流れができればいいという話かと思います。
# 現状把握系(随時、癖のように打つ) $ git status # 現在のワーキングディレクトリ、ステージエリアの状態みる $ git log --graph --oneline # ブランチの状態確認 $ git log --graph --oneline --all # リポジトリの状態確認 $ git log -p # 直近何やってたか確認 # ローカル最新化 $ git checkout <branchname> # 最新化の準備(大体master) $ git pull # 最新化 # ブランチ作成とチェックアウト $ git checkout -b <newbranchname> # 作成してチェックアウト $ git branch -d <newbbbbranchname> # タイポしたので削除 # コード書いてコミットを繰り返す $ git diff -w <filename> # 差分確認 $ git add <filename> # ステージ $ git reset HEAD <filename> # ステージやめる $ git checkout <filename> # 変更戻す $ git diff HEAD # コミット前最終確認 $ git commit # コミット # プルリクエスト準備のため、ローカルブランチをリモートにプッシュ $ git push -u origin newbranchname
感想
本当に最低限なので、clone
とか一度やったらあとはやらない系はのぞいています。ファイルの変更取り消し、ステージの取り消し、ときたのでコミット取り消し(reflog
やreset --hard
)もいるかと思ったのですが、余裕ができたらでいい気がします。
参考リンク
あとGitに関する大まかなイメージを持っていると楽かもしれないです。
公開Slackのリンク
今更だけど調べたのでメモ。
公開Slackの作り方は以下のリンクです。Slackin、SlackinをHerokuで(簡単)、Google Apps Script、 Arukas.ioとかが使えそうです。
- slackin: rauchg/slackin: Public Slack organizations made easy
- Heroku + slackin: SlackinでSlackに誰でも参加できるチームをつくる
- Google Apps Script: 公開Slack用 自動招待フォーム by GAS
- Arukas:
Arukas.io で Slack 自動招待フォームβサービス終了で今使えないみたいです(確認: 2017/11/27 14:02)
Google Apps Scriptでやります ちょっとやり方考えます。
追記
Gitで大雑把にConflict解消
こんにちは。
Gitのコンフリクトの解消方法毎回忘れるのでメモしておきます。エディタで修正するのは手間でしたので・・・
$ git checkout --ours filepath # 変更しない $ git checkout --theirs filepath # マージ先のファイルで上書き $ git checkout -m filepath # よくわからなくなったので、コンフリクトが発生した直後の状態に戻す
まとめ
コンフリクトのdiff
が読みにくすぎてやばいです。<<<<<<< HEAD
にHEAD
とか書いてありますがだいたい文字とか見るの面倒なので、diffの囲み(<<<<<<< ... ======= ... >>>>>>>)の中にあるやつは上にあるのが僕らの、下にあるのが奴らのと覚えてます。
<<<<<<< ours(僕らの) ======= theirs(奴らの) >>>>>>>
よくわからなくなったらgit checkout -m .
でやり直しちゃえばいいかなと思います。
あと、個別のファイルに対して変更するかしないかを判断するのはかなりきついな。。。という感想を持ちました。
願わくばコンフリクトが起きない方がいいですが、コンフリクト恐れすぎるのも良くないので、mergetool
とか駆使していきたいです。(Gitはいくらでもやり直しがきくのでやってみてから考えるでいいかと思います)
BitbucketのPipelinesでmasterブランチにPull Request後、自動デプロイ
いい加減デプロイ自動化しないといけない。
プライオリティ低くなってしまっていて放置気味でしたが、毎回サーバーに入ってgit pull
することに飽きてきました。
BitbucketのPipelinesについて調べたことを書いていきます。
無料で使用できますが月50分までの使用制限があるそうなので、そんなにがっつりは使えない気もします。(2017/10/31 時点)
また、この記事ではPipelinesを使いますが、他の選択肢としてはWebhookとかあります。 その場合サーバー側にエントリポイント作って、POST受け取ってあれこれするという風になるかと思います。 状況に応じてWebhookも検討してみると良いかもしれないです。
概要
BitbucketのPipelinesを使えばリポジトリごとに 特定のブランチへのコミットを検知したタイミング で行いたい処理、テスト実行とか通知とかデプロイスクリプト起動とか、を設定できます。
Pipelinesはコミット検知したら裏側でDocker立ち上げているので、私たちはコンテナ内で起動するコマンドを書いていく感じです。立ち上げるDockerイメージも指定できます。
Pipelinesの使い方は簡単で、 bitbucket-pipelines.ymlというYAMLファイルに記述してレポジトリルートにおいておくと勝手に読んでくれます。
プルリクエストのマージじゃなくて 特定のブランチへのコミットを検知したタイミング でいいの?
とか思ったのですがよく考えたら、デフォルトだとプルリクエストをマージするとマージコミットが生成されるため(fast-fowardでもコミット追加されますし)、 masterブランチの設定を記述しておけば、masterへのプルリクエストが閉じられたら本番環境にデプロイ、とか出来る様になります。なるはずです!
やること
- Pipelinesの有効化とbitbucket-pipelines.ymlの記述
- デプロイ用ユーザーを作成
- deploy.shの記述
今回は最小限、デプロイだけです。テストとかロールバックとかについては考えてないので、お気をつけください。
Pipelinesの有効化とbitbucket-pipelines.ymlの記述
Bitbucketへ行き、対象のリポジトリ管理画面の「Settings > Pipelines > Settings」からPipelinesを有効化します。
(リファレンスは次です。参考ページ)
YAMLを書きます。私は次のように記述しました。Dockerイメージは現在使っているサーバーに合わせてcentosにしています。sshできるようにしてデプロイスクリプトを叩くだけです。スクリプトは後で書きます。
image: centos:latest pipelines: branches: master: - step: name: Deploy script: - yum -y install openssh-clients - ssh -p $SERVER_PORT $DEPLOY_USER@$SERVER_IP bash < deploy.sh
$SERVER_PORT, $DEPLOY_USER, $SERVER_IPなどの環境変数を、 「Settings > Pipelines > Environment variables」 から設定します。参考
作成したYAML
ファイルをどうにかmaster
に取り込んでください。好きな方法でいいです。add, commit, pushとか。
デプロイ用ユーザーを作成
デプロイサーバー -> Bitbucket
デプロイサーバーからBitbucketのリポジトリにアクセスするユーザーを作成します。サーバーに接続してbitbucketユーザーを作成します。($DEPLOY_USERと同じもの)
次に新しいユーザーのSSHキーペアを作成してください、これはBitbucketからgit pull
するときに使用します。参考: Creating SSH keys
Bitbucketのサイトから作成した公開鍵を、リポジトリのデプロイキーに設定します。
このユーザーでリモートリポジトリにアクセス(fetch
とか)できたら成功です。
Pipelines -> デプロイサーバー
PipelinesのDockerコンテナからデプロイサーバーにssh接続するために、BitbucketからSSHキーペアを作成します。参考: Use SSH keys in Bitbucket Pipelines
「Settings > Pipelines > SSH keys」 からキーペアを作成してください。
サーバーに戻り、先ほど作ったユーザーの~/.ssh/authorized_keys
に公開鍵を貼り付けます。
またsshが困らないように、この画面の下部にある Known hosts の追加も行っておきましょう。(ポート付きの場合XXX.XXX.XXX.XXX:PORT)
deploy.shの記述
#!/bin/bash REPOSITORY_PATH=/repository/path # 適当に変えてください cd $REPOSITORY_PATH git pull
リポジトリパスに移動してgit pull
するだけのスクリプトです。注意としては、リポジトリのブランチが常にmaster
になっていないと予期せぬ挙動が起こると思います。
完了
適当にブランチ切って修正してコミットしてプッシュしてプルリクエストしてマージして、Pipelinesメニューからタスクが実行されているのが確認できるかと思います。
一旦流れを作っておけば、後からテストを追加とかもできるはずなので、一歩前進というところです。
内容に関して何かございましたらお知らせいただきたいです。こっちの方がいい、ここ間違っている、これめっちゃ危ない、根本的に勘違いしている、ちゃんとリファレンス読みなさい、など指摘いただけると大変助かります。
ハマった
- リポジトリの権限
- Permission denied
ssh-agent
が止まっているとか~/.ssh
周りの権限とか所有者が適切ではなかったり- どの時点での権限不足なのか、サーバー3つ(Pipelines, Deploy, Bitbucket)なのでわかりにくかった
- Pipelinesを使い始める順番
React NativeでARを体感する、Introducing Expo AR
9月末にExpo
がアップデートされてiOS
のAR Kit
対応が行われました。チュートリアルが公開されたのでちょっと手を出してみました。使用する要素としては、
- expo
- three
- expo-three
のみです。3Dオブジェクトの生成はThree.js
、レンダリングはExpo
、Expo Three
という構成になっています。
Introducing Expo AR: Three.js on ARKit
デモ
感想
Expoの制限はあるもののARアプリをJavaScript
でかけるというのは前提となる知識量のハードルが下がるかと思います。
チュートリアルやると満足感が得られてしまうのでやや危険ですね。色々いじってみるのは大切な一歩だと思いますが、一歩踏み出した後に自分の発想が広がっていかないのがなかなか辛いところです。
サービスがSSL化するとき
ものすごく短い話ですが、サービスがSSL化するときの対応を想定してみました。近々知り合いのサイトにSSL入れられることになりそうなのが理由です。そういえばはてなブログもSSL化するそうですね!
概要
http
をhttps
に書き換えるの面倒ですよね。省略できた気がします。(確かにできるみたいです参考: リンクのhttp:
やhttps:
を省略して現在のプロトコルでリンク先にアクセスさせる)。ただ参考リンクのタイトル通り、通信は現在のスキームになるのでほおっておくと 本来SSLが必要なページでもhttp
で接続されてしまいます。というのはかなり問題だと思うので、移行するときは以下の切り分けをするかもしれないです。
切り分け方法: 追記(2017年10月3日)
- 内部
- リソースとかユーザーの情報を扱わないリンクに関しては、スキーム省略
- 明示的にssl化したページはしっかり
https
書いておく
- 外部
- リンク先に合わせたスキームを書く
感想
でも切り分けるくらいならhttps
に置換しちゃったほうが早くない?というと、それもそうだと思います。前に、「画面動かない!直して!->ssl
切れちゃってました」、というhttps
-> http
現象があったので対応減らせたらいいなと思った次第です。
混在コンテンツ(Mixed Content)に対しても効果があるそうです。
配列でsplitもどき(JavaScriptメモ)
JavaScriptに関するメモです。配列(Array)を適当な部分で分割したかったのでsplit
もどきの関数を書きました。文字列のsplit
をちゃんと配列版にしたわけではないので「もどき」と言っています。
function split(array, separator) { return array.reduce(function(p, n, i) { if (n !== separator) { p[p.length - 1].push(n); } else { if (i !== 0 && i !== array.length - 1 && array[i - 1] !== separator) p.push([]); } return p; }, [[]]) }
// 's'で分割する split(['a', 'b', 's', 'c', 'd', 's', 'e'], 's') => [ ["a", "b"], ["c", "d"], ["e"] ] // 配列の最初と最後に区切り文字があるときは、空配列を作らずに無視する // 連続している区切り文字も1つとみなし、空配列は作らない split([1,2,3,3,3,3,3,4,5,4,3,2,2,1,1,2,3,3,4,1], 1); => [ [2, 3, 3, 3, 3, 3, 4, 5, 4, 3, 2, 2], [2, 3, 3, 4] ] // 区切り文字が配列に含まれていないときは、配列そのものを要素に持った配列を返す split([1,2,3,3,3,3,3,4,5,4,3,2,2,1,1,2,3,3,4,1], 0) => [[1, 2, 3, 3, 3, 3, 3, 4, 5, 4, 3, 2, 2, 1, 1, 2, 3, 3, 4, 1]]
感想
必要に駆られて書いてみたので、文字列のsplit
の配列版にはなっていないです。スプレッドシートがExcel方眼紙状態になっていて、そこからデータを取得しなきゃいけない、というときに使用しました。
配列版split
を作る場合、配列を区切り文字にして配列を分割できるようにするのが自然だと思います。文字列のsplit
をうまく使って配列版をかけないか?と思ったのですが、空文字とかの扱いが微妙です。
なんか圏論とか使うと整理できそうな気がしました。文字列対象から配列対象への射を、配列関手で写した先みたいな。全然よくわかっていないです。圏論ということはHaskellでの実装を見ると面白いかもしれないです。(多分split
的なものがある?)
ページに目次をつけて、項目を押したらスクロールする
ものすごい小さい話ですが、毎回忘れてしまうのでメモします。
$('html,body').animate({ scrollTop: window.pageYOffset + target.getBoundingClientRect().top - 8 // 要素がブラウザ上部ぴったりになるので少し隙間をあけたり }, 500, 'swing');
まずwindow.pageYOffset
について、これはwindow.scrollY
のエイリアスだそうです。ページの一番上を0として、どのくらい下にスクロールしているかのピクセル値を返します。
次にelement.getBoundingClientRect().top
ですが、現在クライアントが見ている画面の上端を0として、そこからどれくらい要素が離れているかをピクセル値で返します。例えば要素をスクロールして通り過ぎていたら、負の値が返ってきます。
つまり、ページの一番上から現在表示している画面上端までの距離
と現在表示している画面上端から要素までの距離
を足すことでページの一番上から要素までの距離
を計算しているということになります。(多分絵で描くとわかりやすい???)
ページの一番上から現在表示している画面上端までの距離
現在表示している画面上端から要素までの距離
まとめ
多分覚えたのでもう忘れないはずです。ぶっちゃけていうとすでにここに書いてありました。。。
おまけ
デモです。
See the Pen MEbNmj by ushumpei(@ushumpei) on CodePen.
一枚にまとめたやつ
<html> <head> <style type="text/css"> .container { display: flex; flex-direction: row; justify-content: space-between; } .menu { background-color: #eee; display: flex; flex-direction: column; flex: 1; height: 120px; justify-content: space-around; padding: 8px; position: sticky; top: 8px; } .content { display: flex; flex-direction: column; flex: 3; justify-content: space-between; margin-left: 8px; } .section { height: 100vh; } .section > h1 { background-color: #eee; padding: 8px; margin-top: 0; } </style> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script> window.addEventListener('load', function() { var menuItems = Array.from(document.getElementsByClassName('menu-item')); var sections = Array.from(document.getElementsByClassName('section')); menuItems.forEach(function(item, i) { var target = sections[i]; item.addEventListener('click', function() { $('html,body').animate({ scrollTop: window.pageYOffset + target.getBoundingClientRect().top - 8 // 要素がブラウザ上部ぴったりになるので少し隙間をあけました }, 500, 'swing'); }); }); }); </script> </head> <body> <div class="container"> <div class="menu"> <a href="#" class="menu-item">section1</a> <a href="#" class="menu-item">section2</a> <a href="#" class="menu-item">section3</a> <a href="#" class="menu-item">section4</a> <a href="#" class="menu-item">section5</a> </div> <div class="content"> <div class="section"><h1>section1</h1></div> <div class="section"><h1>section2</h1></div> <div class="section"><h1>section3</h1></div> <div class="section"><h1>section4</h1></div> <div class="section"><h1>section5</h1></div> </div> </div> </body> </html>
google app scriptで背景色の置換
google app scriptで背景色の置換スクリプトを書いたのでメモします。以下の内容が含まれています。
- 独自(「拡張ツール」)メニューの追加
- モーダル(背景色置換モーダル)の表示
- 初期値の挿入(テンプレートhtmlの使用)方法
概要
主にgoogleのガイドを参考にして作成しました。とりあえずできることと、コードを記載します。
できること;
- スプレッドシート内での背景色の置換、置換実行モーダルの表示
- 選択したセルの背景色を置換元としてデフォルト値として挿入
そんなこといいから、という方は、スプレッドシートを開いて、「ツール」>「スクリプトエディタ」を選択し、スクリプトエディタのgs
ファイルにjavascriptコード、新規作成でhtml
ファイルを作成しテンプレートファイル(index.html)を貼り付けてください。貼り付けたあとリロードするとメニューに「拡張ツール」が現れるかと思います。現れない場合はコメント頂けると幸いです。。。
コード;
main.gs
function onOpen() { SpreadsheetApp.getUi() .createMenu('拡張ツール') .addItem('背景色置換', 'showModal') .addToUi(); } function showModal() { var background = SpreadsheetApp.getActiveSheet().getActiveCell().getBackground(); var template = HtmlService .createTemplateFromFile('index.html'); template.from = background; var html = template .evaluate() .setWidth(300) .setHeight(150) .setSandboxMode(HtmlService.SandboxMode.IFRAME); SpreadsheetApp.getUi().showModalDialog(html, 'Replace background color'); } function replaceBackgroundColor(from, to) { var sheet = SpreadsheetApp.getActiveSheet(); var data = sheet.getDataRange(); var backgrounds = data.getBackgrounds(); var replacedCount = 0; for(var row = 0; row < backgrounds.length; row++) { for(var col = 0; col < backgrounds[row].length; col++) { if (backgrounds[row][col] != from) continue; sheet.getRange(row + 1, col + 1).setBackground(to); replacedCount++; } } ui = SpreadsheetApp.getUi(); ui.alert('Successfully replaced!', replacedCount + ' cell\'s background color replaced to ' + to + ' from ' + from, ui.ButtonSet.OK); }
index.html
<!DOCTYPE html> <html> <head> <base target="_top"> <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css"> <script> function handleClick() { document.getElementById('replace').setAttribute("disabled","disabled"); var form = document.getElementById('form'); var from = form.querySelector('input[name="from"]').value; var to = form.querySelector('input[name="to"]').value; if (!from || !to) return showError('Input #from and #to'); google.script.run .withSuccessHandler(closeModal) .withFailureHandler(showError) .replaceBackgroundColor(from, to); } function closeModal() { google.script.host.close(); } function showError(message) { document.getElementById('error').innerHTML = 'Error: ' + message; document.getElementById('replace').removeAttribute("disabled"); } </script> <style type="text/css"> html, body, #form { height: 100%; } </style> </head> <body> <div id="form" style="display: flex; justify-content: space-between; flex-direction: column;"> <div style="display: inline-flex; justify-content: flex-end"> <label for="from" style="flex: 1">From: </label> <input id="from" type="text" name="from" placeholder="from" value="<?= from ?>" style="flex: 3"> </div> <div style="display: inline-flex; justify-content: flex-end"> <label for="to" style="flex: 1">To: </label> <input id="to" type="text" name="to" placeholder="to" style="flex: 3"> </div> <button id="replace" onclick="handleClick()">Replace</button> <p id="error" style="color: red"></p> </div> </body> </html>
解説
下準備
onOpen
でスプレッドシートファイルを開いたときに実行する関数を指定できます。ここではスプレドシートのUIのAPIを呼び出して、メニューの追加と、メニュー項目と選択時の実行関数の登録を行なっています。
メニュー項目が選択された際に実行される関数は、選択中のセルの背景色取得、テンプレートへの値挿入、モーダルの表示などです。対応するコードを以下に記載します;
まず現在選択中のセル(光っているセル)の背景色を取得するためにgetActiveCell
を使ってセルを取得します。そのあとにgetBackgroud
で#??????
形式のカラーコードを取得し格納します。
var background = SpreadsheetApp.getActiveSheet().getActiveCell().getBackground();
次に、作成済みのhtml
テンプレートを読み込み、プレースホルダーfrom
に対し背景色を割り当てます。プレースホルダーの指定は、
<input id="from" type="text" name="from" placeholder="from" value="<?= from ?>" style="flex: 3">
のようにvalue=<?= form ?>
となっていて、この仕組みを使って置換前の背景色のデフォルト値、として現在選択中のセルの背景色を割り当てます。参考
var template = HtmlService .createTemplateFromFile('index.html'); template.from = background;
最後にモーダルの表示処理を行います。
var html = template .evaluate() .setWidth(300) .setHeight(150) .setSandboxMode(HtmlService.SandboxMode.IFRAME); SpreadsheetApp.getUi().showModalDialog(html, 'Replace background color');
evaluate
メソッドを呼び出すことで、テンプレートからhtml
オブジェクトを生成します、モーダルとして表示するためにサイズを設定し、実行方式をiframe
に指定しました。実行方法に関しては他に選択肢があるようで、調べきれていないです。なので特に深い意味はありません、チュートリアルに沿って、iframe
を指定しただけです。
モーダル
モーダルの記載方法に関しては1ページのhtml
を書いて行く感じです。height
とwidth
が決まっている以外はwebと同じ感覚だと言えます。ただし、少し違うのは、スクリプトエディタで定義した関数を呼び出す方法があるということです。方法としては提供されているAPIを使用します;
function handleClick() { document.getElementById('replace').setAttribute("disabled","disabled"); var form = document.getElementById('form'); var from = form.querySelector('input[name="from"]').value; var to = form.querySelector('input[name="to"]').value; if (!from || !to) return showError('Input #from and #to'); google.script.run .withSuccessHandler(closeModal) .withFailureHandler(showError) .replaceBackgroundColor(from, to); }
handleChlick
関数はユーザーが置換実行する際に押下するボタンに紐づいている関数です。概要としては、表示されたモーダルに対象の背景色、変換後の背景色を入力し、「置換」を押下したら、シート内の全てのセルに対して、背景色の置換を行うためのものです。処理の流れとしては二回連続のクリック禁止、入力した値の取得、軽いバリデーション、google.script.run
APIを使用した背景色置換スクリプトの実行です。
詳細が必要なのはgoogle.script.run
かと思います。このAPIはgoogle.script.run.hogehoge()
と記載することで、スクリプトエディタで定義したhogehoge
関数を呼び出すことができます。またここでは、実行の成功時(withSuccessHandler
)、失敗時(withFailureHandler
)にコールバックを指定しています。コールバック関数は index.html
で定義したものが指定可能です。
以下はモーダルに値を入力して実行した際に呼び出されるスクリプトです。
function replaceBackgroundColor(from, to) { var sheet = SpreadsheetApp.getActiveSheet(); var data = sheet.getDataRange(); var backgrounds = data.getBackgrounds(); var replacedCount = 0; for(var row = 0; row < backgrounds.length; row++) { for(var col = 0; col < backgrounds[row].length; col++) { if (backgrounds[row][col] != from) continue; sheet.getRange(row + 1, col + 1).setBackground(to); replacedCount++; } } ui = SpreadsheetApp.getUi(); ui.alert('Successfully replaced!', replacedCount + ' cell\'s background color replaced to ' + to + ' from ' + from, ui.ButtonSet.OK); }
特に目新しいことはないかと思います。alert
はgetUi
でUIから使用可能です。
感想
まず不安に思ったこととしては、「それ標準機能であるよ」と言われることです。その辺どうなのでしょうか、、、たまにコード書きたい欲がまさってしまい、ちゃんとした検索を行わないという傾向があります。
すごく感心したのが、google app script
のモーダルでhtmlを表示できること、テンプレートを使用できることです。テンプレートを使用できるということは、webアプリケーションに近い何かを作れることだと思います。例えばSQLドライバーを作ってみたりすると楽しいかもです、スプレッドシートをデータベース、シートをテーブルと見立てて、とか思ったりします。
javascriptでユーザーインプットを受け取る方法は、alert
とかprompt
とか柔軟性がなくて困ることがありました。webだと画面側をリッチにすればいいのですが、spreadsheetだとそうもいかないので、それを解消するためにガイドが役に立つのだと思います。
PhRUG(Philippine Ruby Users Group) September 2017 Meetupに行ってきた
最近フィリピンに滞在しています。2017/09/21にフィリピンのRubyユーザーグループのイベントが行われるということで、海外勉強会ってどんな感じだろう?日本と違いがあったりするのかな?とか思い参加してきました。たくさん写真撮ってくるの忘れました。
とりあえず結論、強く思ったのは、フィリピンでも日本でも、技術系の勉強会は大体同じ雰囲気、ということです。
概要
イベントはmeetupから発見しました。PhRUG September 2017 Meetup 会場のホストはShield Foundryというベンチャー企業で、HPも綺麗な感じです、ただHPを見ても何を作っているかはよくわかりませんでした。場所はShield Foundryのオフィス、マニラのBGC(Bonifacio Global City)にあります。BGCにはいったことなかったですが、未来っぽい町でした。外資系企業がたくさんあるみたいで、金持ちが多いみたいな話です。
余裕を持って会場へ向かったのですが、交通渋滞、ビルを間違える、などのアクシデントで、会場に着いたのは開始30分後くらいになってしまいました…
会場のShield FoundryはいかにもITベンチャーといった感じでした。広さは3~40畳くらいで、オフィススペースとキッチンスペースに分かれています。オフィススペースには長机に沢山の椅子(アーロンチェア?)、各椅子の前の机にiMacとモニターがずらりと並んでいます。それぞれ漫画とか本とかも乗っていました。その他、バランスボールあり、サッカーボールあり、あとサッカーのアレ(二人でバーを回してボールを蹴り合うゲーム)もありました。テック系は世界共通なんだな、と少し安心しました。
受付を済ませてプレゼンしているキッチンスペースに入ると、ビール手渡され、真ん中のダイニングテーブルにはピザが置いてあり、ご自由にどーぞといった感じでした。
イベント
最初の発表は、会場に到着したときは既に始まっていて、ちょっと詳細がわからなかったです…。発表者はエンジニアの一日、のようなスライドを表示していました。とりあえず自分はタガログ語が一切できないので、英語でプレゼンしていてホッとしました。ただ英語がそこまでできるわけでもないし、トピック不明だったのでなんとなく頷いたりして過ごしました。
2番目の発表はrails
のパフォーマンス改善やテストなどの発表でした。bootsnap
, PhantomJS
, rack-mini-profiler
, New Relic
, その他諸々、をそれぞれいい感じに使って、計測、改善したよ!という感じです。そうですよね。納得な感じです。
3番目の発表はjupyter/notebook
を使って見た系の発表。マークダウン + コードでドキュメントかけるやつです。これ自体は先輩に教えてもらっていて知っていたのですが、バグレポートとかに使って、楽に可視化していこうよ!といった、やっぱりそう考えるよね的な共感を持ちました。
4番目の発表は、開発Tipsの紹介でした。git
のprecommit-hook
でコミット前にrubocop
してます、とか。rails
のサーバープロセスの優先順位あげたらなおった、とか。やっぱりコミット前にちゃんとしておきたいと思うよね!という再度、強い共感を持ったりしました。
感想
会が終わった後にエンジニアの方にフィリピンのプログラミング言語事情について尋ねて見たところ、大企業とか、求人の多さで言えばjava
, php
。ベンチャーはruby
, python
といった感じです。scala
はとてもいい言語だと思うけど学習コストが高いのと、まだ事例をそこまで聞かない、とのことでした。日本では既に事例があるので、このあたりの情報共有とか何かできるかも(逆もあるだろうし。そういえばtwitterは?scalaの事例としてはどう捉えていたのだろう?国内事例がない、という感じなのだろうか?)。
フロントエンドのjavascriptフレームワークに関しては、angular2
, react
。javascriptフレームワークはリッチすぎて、本当に欲しいものがその中の1つだったりする、という日本でも割と聞く話を伺うことができました。ionic
でネイティブを、とかいう話題もちらほら聞くようです。
ここまで書いて、強いて挙げるならフィリピンと日本のIT界隈の相違点はなんなのだろうか?という方向に思考が向かいました。フィリピンの方が学歴に関して厳しいのかな?という予想を最近持っていますが、まだちゃんと議論したことがないので結論としては不十分と思います。さらに交流を重ねなければと思います。
参加する前はブログを書くつもりがなかったため、少々微妙なレポートになってしまいました。来月もグループがあるそうなので、もし行くとなればもっといい感じにレポートしたいと思います。meetup便利です。
iOS11アップデートについていくためのSwift入門(主観)
2017/09/20に、iOS11がリリースされました。今回のアップデートでは機械学習やSiriアプリ、ARなど様々な新機能が使えるようになりました。リンクを見ていて、「ちょっとSwift読めるようになっておかないと、おそらく半年くらいつまらなくなってしまう…上がってくるニュースを見ているだけになってしまう…」と思ったのがこの記事を書こうと思ったきっかけです。
リファレンスとしてはAppleのサイト、The Swift Programming Language (Swift 4): A Swift Tourを使います。学習の目的はSwiftのコード(主に今後発表されるであろう「iOS11で追加された〇〇を使って見た系の記事のコード」)を読めるようになることです。
Hello, world!
を目標、と思ったのですが、ベタ書きで終わってしまいました。
print("Hello, world!")
概要
リファレンスの内容は、Swiftを書き始めるのに十分な知識を得るためのツアーです。記事がSwiftのプロジェクトとしてまとめられていて、ダウンロードできるのでそれを使って学習していきます。
セクションは次のようになっています;
- Simple Values
- Control Flow
- Functions and Closures
- Objects and Classes
- Enumerations and Structures
- Protocols and Extensions
- Error Handling
- Generics
本記事ではリファレンス内に記載されている情報で、主に書き方に関することをメモしていきます。すでに序文の時点で以下の二点が記載されています;
- グローバルスコープの処理は勝手に実行されるので、エントリーポイントとしてのmain関数が必要ない
- 文末にセミコロンは必要ない
Simple Values
- 変数宣言について、定数は
let
、通常の変数はvar
で宣言する:var hoge = 1
とか。 - 明示的な型の宣言は
let piyo: Double = 70
(後置型宣言) \()
で文字列に変数を埋め込める。"template \()"
- 3ダブルクォート
"""
で複数行の文字列を囲むことができる - 配列リテラル
["hoge", "piyo",]
が使用可能 - [“hoge”: 1, “piyo”: 2]辞書も可能
- 空の辞書は
[:]
Control Flow
- if, switch, for-in, whileとかありますがこの辺は不思議なさそう
型?
でOptionalな変数を宣言できる(例var hoge: String? = "hoge"
)if let hoge = { ... }
でOptional変数から値を取り出すことができる、取り出すことができたらブロック内の処理を実行するという記法if var piyo = { ... }
ともかけるが、多分そんなに使わない- 取り出した変数のスコープはブロック内
- その他にOptional変数を扱う方法としては、
??
を使って、defaultValue ?? optionalValue
と言うように記述すると、値が取り出せる時のみ使用される switch
はcase
とdefault
で構成していきます。break
は不要、default
は必須です。0..<4
で、0,1,2,3の範囲のレンジを作成できる
Functions and Closures
- 関数定義は
func
、引数の定義はhoge: String
(後置型宣言)、アロー->
で戻り値の型を記述:func hoge(hoge: String) -> String { ... }
- 引数を名前付きで渡すことができる
hoge('fuga', piyo: 'giyo')
これは定義時にfunc hoge(_ hoge: String, piyo fuga: String)
と言うようにする。引数のラベル
と言うもので、_
はラベルなしを意味する。 - 高階関数も作れる:
(Int) -> Int
とかで型を宣言 - クロージャ使える:
{ (hoge: Int) -> Int in // クロージャの型と変数名 return hoge * 2 }
- 場合によっては型も省略できるらしいので、
{ hg in ... }
とかブラケットとin
が出てきたらクロージャとして読む。引数も$0, $1
と順番で記載されることがあるので、{ $0 < $1 }
とか書かれていても混乱しないように。
Objects and Classes
- クラスのインスタンス化は
ClassName()
- フィールドはベタ書きでいいが、外部からアクセスするには
getter
メソッドが必要 - イニシャライザー(コンストラクター)は
init
、クラス内のメソッドでインスタンス自身を参照するにはself
、デイニシャライザーはdeinit
- 継承は
class SubClass: SuperClass { ... }
setter
定義時のnewValue
パラメーターは予約語method?.hoge
でmethod
の戻り値があればhoge
を呼び出すと言うことができる
Enumerations and Structures
- enumはcaseで値を振っていく。ラベルから数値を取り出すのは
rawValue
、数値からラベルを作成するにはイニシャライザを使う(この時の値はOptional)。 - structで構造体を定義。だいたいの機能はクラスと同じだが、構造体は常に値渡しされる。
- class, struct, enumは似ている
Protocols and Extensions
protocol
でインタフェースのようなものが定義できる- メソッドに
mutating
宣言することで、インスタンスプロパティを変更するメソッドを定義できる(クラスの時は不要、struct,enumの時だけ) extension
で既存の型に機能拡張を行える、Protocolの機能を追加するのにも使える
Error Handling
do...catch
構文でかく、怪しいところにtry
をかくtry?
でOptional値を取り出す
Generics
- ジェネリクス
<Item>
で、Item
を変数のようにしようできる(数学でいう変数的な、x的な):var hoge = [Item]()
でItem
型の配列を宣言とか。 where
句もジェネリクス関連、親、子などの型の検証を記述できる
感想
以上、自分がSwift
を読む際に詰まりそうなところをまとめ終えました。逆に言えば詰まらなそうなところは、私の主観の範囲で無視しています。
Optional型の存在がちょっと気になりました。だいぶ面白そうです。またクラス、構造体、列挙型に関する扱いも違った見方を得られて興味深いです。自分としては列挙型はそこまで重要視していなかったのですが、Swift
をかくことでもっと本質をつかむことができるかもと、少し前のめりになりました。
iOS`界隈の盛り上がりに少しでもついていきたいと思った次第です。
文法の勉強のため簡単なObjective-CのコードをXcodeで実行してみた
前回、React Nativeのソースコードを読もうとして挫折したので、基礎文法を勉強するためにObjective-C
で何か書いて見ます。
その前にObjective-C
のwiki
を少し読んで感じをつかみます。とりあえず読むために必要そうなこと3つです。
書いて見る
環境: Xcode: Version 8.3.3 (8E3004b)
ではとりあえずhello world
します。別にアプリケーションが書きたいわけではないので、コマンドラインツールプロジェクトとして書きます(もっと言えばコマンドラインツールが書きたいわけではないですが、実行の構成方法が不明だったため一旦これでいかせていただきます)
まずXcode
を起動してFile > New > Project
を選択します。モーダルが表示されるので、macOS
タブのCommand Line Tool
を選択しNext
を押します。名前などは自由に設定します、ただし使用言語はObjective-C
にして、初期設定を終えます。
次にコードを書いていきます。すでにmain.m
(エントリーポイントとなるファイル)が生成済みだったので、そこに処理を記述していきます。クラスの定義の練習をするためにHello
クラスを作成しましたのでそれを呼び出します。(@
はコンパイラディレクティブの他にもリテラルを使用するときの接頭辞として使う、いやおそらくコンパイラディレクティブでリテラルを変換している?)
main.m
#import <Foundation/Foundation.h> #import "Hello.h" int main(int argc, const char* argv[]) { @autoreleasepool { Hello* object = [[Hello alloc] init]; NSString* message = @"Hello world!"; [object setMessage:message]; [object say]; NSLog(@"[object message]: %@", [object message]); } return 0; }
次にHello
クラスをNew > File
からmacOS
タブのCocoa Class
を選択します。こうするとHello.h
、Hello.m
ファイルが生成されます。内容を記述していきます。以下を定義しています、
- 挨拶文を保持する
message
変数 - 挨拶文を取得する
message
関数 - 挨拶文を格納する
setMessage
関数 - 挨拶文をコンソールに出力する
say
関数
です。
- Hello.h
#import <Foundation/Foundation.h> @interface Hello : NSObject { NSString* message; } -(NSString*) message; -(void) setMessage: (NSString*) s; -(void) say; @end
- Hello.m
#import "Hello.h" @implementation Hello -(NSString*) message { return message; } -(void) setMessage: (NSString*) s { message = s; } -(void) say { NSLog(@"say: %@", message); } @end
動かして見る
Xcode
のウィンドウ上部メニュー左辺りにある▶︎
(Runアイコン)を押してコードを実行します。
感想
コンパイラディレクティブをちゃんと覚えていけばある程度読めるようになると思いました。@autoreleasepool
はGCということでいいのかな?いやちゃんと覚えないといけないですね。とりあえず公式ドキュメントをこの辺から探して見ます。
書けるようになる必要があるか?という問いに関しては一瞬、「まあでもSwift
あるし、、、」とか思いましたが、まだまだ使われているため、やっておいて損はなさそうな気がします。
React Nativeの画像遅延読み込み(ライブラリのソースを読んで見る)
この記事を要約すると、「画像遅延読み込みの方法が知りたくて、ライブラリのソースコードを読んで、Objective-Cのコードにたどり着いて、次に進めなくなってしまい一旦諦めたけれど、これを糧にもっと勉強しようという気になった」という自己満話です。
概要
少し前までWebサービスのユーザーのインターネット回線速度をそこまで強く意識したことがありませんでした。
もちろんパフォーマンス計測はしていましたが、サーバー側の処理を改善することで対処していて、あくまで一般的なユーザーは回線がある程度の速度であることを仮定していましたし、回線が弱すぎるのはユーザー側の問題としている部分がありました。
この回線の強弱に関する考え方が私が感じた、モバイルアプリとWebの大きな違いでした。
モバイルでは回線が弱くなることが容易に起こりうるし、そのせいでアプリが操作不能になってしまうことは、ユーザーにとってストレスだと思います(操作不能にする方がいいケースもあると思います)。いや、といよりモバイルから閲覧するWebサービスとアプリとの違いでいうと、アプリだとオフライン状態でも使えるように作れる部分があるので、極力そうしていった方が親切だし、ユーザーも安心だよね、という話です。
多分モバイルアプリ開発者の方々からすると当然すぎることだと思うのですが、私がこのことに気がつくためには、いつまでたっても画像が読み込まれないまま動かない、という経験が必要でした。(頭が悪いです)
読んでみる
そんなこともあり遅延読み込みのライブライreact-native-image-progress
とかに触れたのですが、そもそもどうやって遅延読み込みを実現しているか、ソースを読んでみようというのが今回の内容です。
とりあえず一番はじめのリリース0.1.0
を読む;
- 基本的には
react-native
のImage
コンポーネント Image
の読み込み開始、進捗、終了イベントハンドラにコールバックを登録しているImage
のprogressイベントについて苦労したんだろうなと感じられる- 遅延読み込み方法は
Image
のコードを読まなければわからない
ということなのでreact-native
のImage
コンポーネントのソースを読む;
RCTImageView
を読むべきとわかる
ここからObjective-Cです、RCTImageView.m
を読む;
RCTDirectEventBlock
で詰まる…
すみません、詰まりました。
感想
知らないことがたくさん見つかりましたし、いかに色々なことを知らないままで使っていたかわかりました。予想として遅延読み込みに関してはjavascriptのfetch
メソッドとかで実装されてて、簡単に読めないかな、とか思って軽く記事を書き始めたのですが、普通にネイティブでした。
結局わからずじまいになってしまい、かなりダメダメなので、しばらくいろんなソースを読んでみることにすると思います。Objective-Cの基本的な構文も抑えて今回の詰まったところを解消したいです。あとReact Nativeの仕組みもちゃんと知りたくなりました。
余談ですがFacebookのライセンスの話、どう捉えるか考えなければ。。。
ハノイの塔
なんだか久しぶりにjavascriptでハノイの塔を解いて見ました。以下の関数は、number
枚のハノイの塔をtime
回操作した時の状態を計算するものです。特に目新しいものではないと思います。
const hanoiSnapshot = (number, time) => { // 円盤の移動が完了した以降の操作はエラーを表示(良いやり方かどうかは悩む) if (time > Math.pow(2, number) - 1) throw new Error(`${Math.pow(2, number) - 1}回目の操作時点ですでに塔は完成しています`); // 三本柱の配列データを定義 hanoi = [[], [], []]; // 1~number番目の円盤それぞれの場所を計算して柱に格納して行く for (let i = 1; i <= number; i++) { // 移動する方向 const direction = Math.pow(-1, number + i + 1); // 移動した回数 const moveCount = Math.floor((Math.pow(2, i - 1) + time) / Math.pow(2, i)); // 1番目の柱を位置0とした時の円盤の絶対位置 const absolutePosition = moveCount % 3; // 方向を加味して場所を0~2に正規化して決定 const position = (direction * absolutePosition < 0) ? (3 + direction * absolutePosition) % 3 : absolutePosition; // 柱に円盤を追加する hanoi[position].unshift(i); } return hanoi; }
感想
イメージとしては円盤は方向と速度を持っていて、3つの柱を移動しつつ、くるくる回って行る感じです。数学で三乗根の乗算を考えるときに書くような図をイメージするとわかりやすい気がします。