20191219のMacに関する記事は10件です。

Kubernetesクラスタ構築ツールのkindを使って見る

Kubernetesの挙動を確認したくなるたびに毎回kubeadmでクラスタを立ち上げるのがめんどくさかったので気軽に構築できるツールがないか探していたところ,kindという構築ツールを見つけたので試して行こうと思います.

環境

Mac OS :Catalina version 10.15.2

Kubernetesとは

Kubernetesについて軽く説明すると,オープンソースのコンテナオーケストレーションエンジンです.そしてコンテナはプログラム実行環境の仮想化方法の1つです.
Kubernetesを用いる事で複数台のコンピュータ上のコンテナを管理できます.
Kubernetesクラスタの基本的な構成は以下になります.

Kubernetesの構成4.png

KubernetesクラスタはMaster NodeとWorkerノードの2つのグループに別れます.Master Nodeでユーザからのコンテナ配置命令の受付,スケジューリングなどを行い,Worker Nodeで実際にコンテナを実行します.
コンテナのスケジューリングはPodという単位で行います.
Pod間通信を行いたい場合やクラスタ外からPodと通信したい場合はServiceを通して通信します.

kindとは

kind(kubernetes in docker)は,1台のコンピュータ上でのKubernetes構築ツールです.Kubernetes自身をテストすることを目的に作成されています.
Dockerコンテナを1つのノードとしてみなし,そのノードコンテナ上でDockerコンテナを実行することで,1台のコンピュータ上でマルチノードクラスタを構築できます.
kindの構成3.png

インストール

kindを動かすには,dockerが必要です.

$brew install docker

でインストールできます.
Kubernetesクラスタにコンテナを配置するため,Kuberenetesコマンドラインインタフェースであるkubectlをインストールします.

$brew install kubectl 

最後にkind自身をインストールします.

$brew install kind

kindでクラスタ作成

kindでクラスタを作成して見ます.Master Nodeの構成とWorker Nodeの構成をyamlファイルで記述し,--configオプションで渡します.渡さなかった場合はMaster Node1台のクラスタが作成されます.

$kind create cluster --config a-controller-a-worker-mapping.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.16.3) ?
 ✓ Preparing nodes ? 
 ✓ Writing configuration ? 
 ✓ Starting control-plane ?️ 
 ✓ Installing CNI ? 
 ✓ Installing StorageClass ? 
 ✓ Joining worker nodes ? 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Thanks for using kind! ?

使用ファイルは以下の通りです.今回は1台のMaster Nodeと1台の Worker Nodeの構成で作成します.
- role: control-plane
がMaster Nodeの設定で,
- role: worker
がWorker Nodeの設定です.

a-controller-a-worker-mapping.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    listenAddress: "0.0.0.0" #オプション defaultは "0.0.0.0"
    protocol: tcp # オプション defaultはtcp

extraPortMappingsでクラスタ外からコンテナへ通信できるようにします.
extraPortMappings3.png

コンテナのデプロイ

nginxをデプロイして見ます.

$kubectl apply -f nginx.yaml 
pod/nginx created
service/nginx-svc created

使用ファイルは以下の通りです.

nginx.yaml
apiVersion : v1
kind : Pod
metadata :
 name : nginx
 labels :
   app : nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx
  type: NodePort
  ports:
  - protocol: TCP
    targetPort: 80
    port: 80
    nodePort: 30000

Podに対応するServiceを作成する事で,クラスタ外からの通信が可能になります.

nginx_svc4.png

ブラウザからアクセスして見ます
nginx.png

無事デプロイできました.簡単ですね.

後片付け

nginxの削除

$kubectl delete -f nginx.yaml 
pod "nginx" deleted
service "nginx-svc" deleted

クラスタの削除

$kind delete cluster
Deleting cluster "kind" ...

kubeadmとの比較

Kubernetesクラスタ構築ツールとして有名なものにkubeadmがあります.kindとは違い複数のコンピュータ上でKubernetesクラスタを構築でき,本番環境にも使用できます.しかし,kindは1台分の作業でクラスタ構築できるのに対し,kubeadmは立ち上げるノード分の作業が発生するのでめんどくさいです.ですのでKubernetesの挙動を確かめたい時はkindの方が良いと思います.

minikubeとの比較

kindと同じ1台のコンピュータ上でのKubernetes構築ツールとして有名なものにminikubeがあります.クラスタ構築に必要な作業量はkindとほぼ変わりませんが1台の仮想マシンがMaster NodeとWorker Nodeの両方の役割を担っています.さらにノード数を増やす事ができず,挙動を試せる幅が狭くなります.例えばKubernetesの機能を使って複数台あるWorker Nodeの内のある特定のWorker Nodeにコンテナをデプロイするということを試せません.なのでKubernetesの挙動を確かめたい時はkindの方が良いと思います.

まとめ

kindを使用することで気軽にKubernetesの挙動を確認できます.なのでこれからKubernetesの挙動を確認したいときはkindを使っていこうと思います.

参考

kind
kubeadm
minikube
Kubernetes ドキュメント
Kubernetes実践ガイド クラウドネイティブアプリケーションを支える技術 (impress top gear)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kubernetes構築ツールのkindを使って見る

Kubernetesの挙動を確認したくなるたびに毎回kubeadmでクラスタ立ち上げるのめんどくさかったので気軽に構築できるツールがないか探していたところ,kindという構築ツールを見つけたので試して行こうと思います.

環境

Mac OS :Catalina version 10.15.2

Kubernetesとは

Kubernetesについて軽く説明すると,オープンソースのコンテナオーケストレーションエンジンです.そしてコンテナはプログラム実行環境の仮想化方法の1つです.
Kubernetesを用いる事で複数台のコンピュータ上のコンテナを管理できます.
Kubernetesクラスタの基本的な構成は以下になります.
Kubernetesの構成3.png

KubernetesクラスタはMaster NodeとWorkerノードの2つのグループに別れます.
Master Nodeでユーザからのコンテナ配置命令の受付,スケジューリングなどを行い,
Worker Nodeで実際にコンテナを実行します.
コンテナのスケジューリングはPodという単位で行います.
Pod間通信を行いたい場合やクラスタ外からPodと通信したい場合はServiceを通して通信します.

kindとは

kind(kubernetes in docker)は,1台のコンピュータ上でのKubernetes構築ツールです.Kubernetes自身をテストすることをを目的に作成されています.
Dockerコンテナを1つのノードとしてみなし,そのノードコンテナ上でDockerコンテナを実行することで,1台のコンピュータ上でマルチノードクラスタを構築できます.
kindの構成3.png

インストール

kindを動かすには,dockerが必要です.

$brew install docker

でインストールできます.
Kubernetesクラスタにコンテナを配置するため,Kuberenetesコマンドラインインタフェースであるkubectlをインストールします.

$brew install kubectl 

最後にkind自身をインストールします.

$brew install kind

kindでクラスタ作成

kindでクラスタを作成して見ます.Master Nodeの構成と,Worker Nodeの構成をyamlファイルで記述し,--configオプションで渡します.渡さなかった場合はMaster Node1台のクラスタが作成されます.

$kind create cluster --config a-controller-a-worker-mapping.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.16.3) ?
 ✓ Preparing nodes ? 
 ✓ Writing configuration ? 
 ✓ Starting control-plane ?️ 
 ✓ Installing CNI ? 
 ✓ Installing StorageClass ? 
 ✓ Joining worker nodes ? 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Thanks for using kind! ?

使用ファイルは以下の通りです.今回は1台のMaster Nodeと1台の Worker Nodeの構成で作成します.
- role: control-plane
がMaster Nodeの設定で,
- role: worker
がWorker Nodeの設定です.

a-controller-a-worker-mapping.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    listenAddress: "0.0.0.0" #オプション defaultは "0.0.0.0"
    protocol: tcp # オプション defaultはtcp

extraPortMappingsでクラスタ外からコンテナへ通信できるようにします.
extraPortMappings3.png

コンテナのデプロイ

nginxをデプロイして見ます.

$kubectl apply -f nginx.yaml 
pod/nginx created
service/nginx-svc created

使用ファイルは以下の通りです.

nginx.yaml
apiVersion : v1
kind : Pod
metadata :
 name : nginx
 labels :
   app : nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx
  type: NodePort
  ports:
  - protocol: TCP
    targetPort: 80
    port: 80
    nodePort: 30000

Podと同時にServiceを作成する事で,クラスタ外からの通信が可能になります.

nginx_svc3.png

ブラウザからアクセスして見ます
nginx.png

無事デプロイできました.簡単ですね.

後片付け

nginxの削除

$kubectl delete -f nginx.yaml 
pod "nginx" deleted
service "nginx-svc" deleted

クラスタの削除

$kind delete cluster
Deleting cluster "kind" ...

kubeadmとの比較

Kubernetesクラスタ構築ツールとして有名なものにkubeadmがあります.kindとは違い複数のコンピュータ上でKubernetesクラスタを構築でき,本番環境にも使用できます.しかし,kindは1台分の作業でクラスタ構築できるのに対し,kubeadmは立ち上げるノード分の作業が発生するのでめんどくさいです.ですのでKubernetesの挙動を確かめたい時はkindの方が良いと思います.

minikubeとの比較

kindと同じ1台のコンピュータ上でのKubernetes構築ツールとして有名なものにminikubeがあります.クラスタ構築に必要な作業量はkindとほぼ変わりませんが1台の仮想マシンがMaster NodeとWorker Nodeの両方の役割を担っています.さらにノード数を増やす事ができず,挙動を試せる幅が狭くなります.例えばKubernetesの機能を使って複数台あるWorker Nodeの内のある特定のWorker Nodeにコンテナをデプロイするということを試せません.なのでKubernetesの挙動を確かめたい時はkindの方が良いと思います.

まとめ

kindを使用することで気軽にKubernetesの挙動を確認できます.なのでこれからKubernetesの挙動を確認したいときはkindを使っていこうと思います.

参考

kind
kubeadm
minikube
Kubernetes ドキュメント
Kubernetes実践ガイド クラウドネイティブアプリケーションを支える技術 (impress top gear)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモリ不足でXcodeのValidateができない

Validateできない問題

XcodeでApp StoreにiPhoneアプリをアップロードした時のことです。

Archiveまではスムーズだったのに、「Validate App」を実行したらめちゃくちゃ待たされた上にPCがクラッシュしました。
Validateを無視してDistributeしてもクラッシュ。
ipaファイルをコマンドで作成してもクラッシュ。
何度繰り返してもクラッシュ。

エラーじゃないので調べても出てこず、めちゃくちゃ困ったので誰かの役にたつことを願って展開します。

環境

iMac (21.5-inch, Late 2012)
OS Mojave 10.14.5
Xcode 11.3
メモリ 8GB
外付けSSDで起動 (APFSでフォーマット)

原因はメモリ不足

アクティビティモニタを起動しながら再度実行。
メモリタブを確認すると、見事にメモリプレッシャーが真っ赤。

どうやら私のiMacのメモリ8GBでは不足している模様。
(物理メモリ8GBに対し使用メモリが7.8GB付近をうろうろした挙句勝手に再起動する)

どうするか

メモリを増設すれば解決。
ただし、私のiMac2012はがっつり分解しないと増設できないため最終手段とします。

そもそも8GBでも普通にみなさんリリースできているため、何か別に原因があると推測。

アクティビティモニタを眺めていると妙なことに気づく。
どれだけ高負荷になってもスワップ使用領域が0Bから変わらない。
そして8GBに達した瞬間にフリーズからのクラッシュ。
メモリスワップしてなくない?

解決策

どうやら、
APFSでフォーマットしたSSDを起動に使用しているとスワップ領域がマウントされない不具合があるらしい。
とりあえずValidateの間メモリが足りてくれればよいので、手動でマウントします。

ターミナルを起動し以下コマンドを実行。

diskutil list

一覧で出たVolumeのうち、Volume VMとなっているdiskを探す。
(私の場合は、disk1s4でした)

diskutil info diskXXX(diskの名前)

diskutil infoで対象のdiskの状態を確認します。
Volume Name: VM
Mounted: No
こんな感じでMountedがNoになっていたらマウントされていないためスワップできないので、以下コマンドでマウントします。
(su権限のパスワード入力を求められます)

sudo diskutil mount -mountPoint /private/var/vm diskXXX

Volume VM on disk1s4 mounted
こんな感じになればOK
再度diskutil infoするとmountedがYesになっていることがわかります。

結果

マウント完了後、再度Validateを実行するとスワップ使用領域が適切に使用され無事アップロードすることができました。

1つ注意点として、PCを再起動するとまたアンマウントされてしまいます。
重い処理を実施する前にはマウントされてるか確認したほうが良いかと思います。

再起動してもマウントを続ける手段は不明です。
フォーマット方式を変えるべきか…。

追記

Automator使って再起動するたびにマウントするようにしました。
起動時にパスワード入力を求められるのがちょっとうざいですが快適です。
(詳細な手順は参考リンクをご覧ください)

osascript -e 'do shell script "sudo diskutil mount -mountPoint /private/var/vm disk1s4" with administrator privileges'

参考

スワップ領域が確保されない - Apple コミュニティ
Macの起動ディスクをAPFSフォーマットのUSB外付けSSDにするとハマる罠

Mac OSXで起動時にスクリプトを実行する方法
Automatorでsudoする方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodebrewのセットアップ&操作方法(Mac)

「nodebrew」とは?

Node.jsのバージョンを管理するツールです。
rbenvやpyenvのNode.js版と考えるとわかりやすいです。

環境

  • OS:macOS Mojave 10.14.6
  • nodebrew:1.0.1

セットアップ

nodebrewのインストール

Homebrewからインストールします。

$ brew install nodebrew

nodebrewの初期設定

nodebrew setup を実行するのみです。

$ nodebrew setup
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================

出力内容に従って ~/.bash_profile に以下を追記し、パスを通します。

.bash_profile
export NODEBREW_DIR="${HOME}/.nodebrew"
if [ -d "${NODEBREW_DIR}" ]; then
  export PATH=$NODEBREW_DIR/current/bin:$PATH
fi

PATH$PATH:$NODEBREW_DIR/current/bin のように書くと、MacにプリインストールされているNode.jsが優先されることがあるようです。

~/.bash_profile をリフレッシュします。

$ source ~/.bash_profile

操作方法

Node.jsのインストール

以下のコマンドを実行し、任意のバージョンのNode.jsをインストールします。

# インストールできるNode.jsのバージョンを確認する
$ nodebrew ls-remote
v0.0.1    v0.0.2    v0.0.3    v0.0.4    v0.0.5    v0.0.6
…
v12.0.0   v12.1.0   v12.2.0   v12.3.0   v12.3.1   v12.4.0   v12.5.0   v12.6.0
v12.7.0   v12.8.0   v12.8.1   v12.9.0   v12.9.1   v12.10.0  v12.11.0  v12.11.1
v12.12.0  v12.13.0  v12.13.1  v12.14.0  

v13.0.0   v13.0.1   v13.1.0   v13.2.0   v13.3.0   v13.4.0   v13.5.0
…

# 最新版をインストールする
$ nodebrew install-binary v13.5.0

nodebrew setup を実行していないと、以下のエラーが発生します。

$ nodebrew install-binary latest
Fetching: https://nodejs.org/dist/v11.12.0/node-v11.12.0-darwin-x64.tar.gz
Warning: Failed to create the file
Warning: /Users/{ユーザー名}/.nodebrew/src/v11.12.0/node-v11.12.0-darwin-x64.tar
Warning: .gz: No such file or directory
0.0%
curl: (23) Failed writing body (0 != 1057)
download failed: https://nodejs.org/dist/v11.12.0/node-v11.12.0-darwin-x64.tar.gz

Node.jsのバージョン切替

nodebrew use コマンドでNode.jsのバージョンを設定します。

$ nodebrew use v13.5.0

Node.jsのバージョン確認

nodebrew list コマンドでインストールされているNode.jsの全バージョンを確認できます。
「current」が設定しているバージョンです。

$ nodebrew list
v13.5.0

current: v13.5.0

実際のNode.jsのバージョンも確認します。
currentとバージョンが異なる場合、nodebrewでインストールしているNode.jsが使われていない可能性があります。

$ node -v
v13.5.0

コラム:LTSとCurrent

Node.jsにはLTS(Long-term support)とCurrentがあります。
基本的に奇数バージョン(v11, 13など)はLTSにならないため、業務では偶数バージョン(v10, 12など)を使うことが多いと思います。
https://nodejs.org/ja/about/releases/

2019/12/19現在、LTSの最新バージョンが12.14.0、Currentの最新バージョンが13.5.0です。
https://nodejs.org/ja/

nodebrewでは stable でLTSの最新バージョン、 latest でCurrentの最新バージョンを指定できます。
つまり、業務でバージョンの細かい指定が不要なら nodebrew install-binary stable でインストールすればOKです。

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chromeの標準機能でWebページ全体をわずか3コマンドでスクリーンショットする

スクロールが必要な画面の全体をスクリーンショットする方法です。
拡張機能や設定は不要でデフォルトのまま使えます。

スクリーンショット

  • Command + option + i (デベロッパーツール)
  • Command + shift + p (Run command)
  • full を入力してEnter (Capture full size screenshot)

スクリーンショットはダウンロードフォルダに保存されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chromeの標準機能でWebページ全体をスクリーンショットする

スクロールが必要な画面の全体をスクリーンショットする方法です。
拡張機能や設定は不要でデフォルトのまま使えます。

スクリーンショット

  • Command + option + i (デベロッパーツール)
  • Command + shift + p (Run command)
  • full を入力してEnter (Capture full size screenshot)

スクリーンショットはダウンロードフォルダに保存されます。

お試し

https://www.apple.com/jp/mac ココのサイトのキャプチャを試します。

スクリーンショット 2019-12-19 16.00.41.png

Screenshot Capture full size screenshot を選択します。

www.apple.com_jp_mac_-min.png こんな感じのファイル名でスクリーンショットされてダウンロードされます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS Simulator(iPhone Simulator)をMacにインストールする方法

この記事は

Screenshot from Gyazo
MacではXcodeをインストールするとiPhoneやApple WatchのSimulatorを使うことができます。
この記事はその設定方法を書きました。

手順

① Xcodeのインストール

App Store、またはDevelopperサイトからXcodeをインストールします。
App Store
Developerサイト

② Simulatorのインストール

(※結構時間かかります & 1つあたり2.0 ~ 3.5GBぐらいあります)
1. Xcodeを起動する
2. メニューバー > Xcode > Preferenceをクリックする
3. Componentsタブを開く
4. 必要なiOSバージョンをクリックしてインストールする

image
image

③ Simulatorの起動

  1. Xcodeを起動する
  2. メニューバー > Xcode > Open Developer Tool > Simulatorをクリックする
    ※ SimulatorはDockに追加できるので、入れておくと便利かも。 image

④ 複数のSimulatorや、デバイス/OSを切り替えたい場合。

  1. Simulatorを起動する
  2. メニューバー > Hardware > Device 選ぶ
  3. iOS一覧とHardware一覧が表示されるので、使いたいものをクリックする
    ・ PCのSpecにもよりますが、同時起動は2 ~ 3台がよいかと。
    ・表示言語を日本語にしたりも可能です。Client証明書のインストールもできるし、もちろんSafariも使えます。 image

合わせて

・iOS バージョンと Safariバージョンの対応表
 https://qiita.com/yoshitake_1201/items/05a13fd77c18ff380eb6
Safariをつかいたけど、どのiOSを入れるか迷う方は↑を参考に入れてみると良いかもです。
WebKitが変わったり、SafariのMajor Versionが変わっているやつを入れたほうが容量的に優しいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Macで '/Library/Developer/CommandLineTools' is a command line tools instance って言われた

  • macOSMojave
  • バージョン10.14.6

雑メモ。

ターミナルで下記のように言われた。

$ opendiff
xcode-select: error: tool 'opendiff' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance

そこで

$ ls -la /Library/Developer/CommandLineTools/
total 0
drwxr-xr-x 6 root admin 192 8 15 11:16 .
drwxr-xr-x 4 root admin 128 9 8 17:43 ..
drwxr-xr-x 5 root admin 160 7 13 13:44 Library
drwxr-xr-x 3 root admin 96 7 13 13:11 Packages
drwxr-xr-x 4 root wheel 128 8 15 11:16 SDKs
drwxr-xr-x 7 root admin 224 7 13 13:44 usr
$ xcode-select -p
/Library/Developer/CommandLineTools

と環境を見てみたが、何が問題なのかよくわからんかった。

ググって出てきたこの記事
http://shinriyo.hateblo.jp/entry/2018/02/17/IonicのiOSのビルドのエラー

を参考にして

$ sudo xcode-select -switch /Library/Developer/CommandLineTools
$ sudo xcode-select -r

とやってみたら、

$ opendiff
2019-12-19 08:37:14.020 opendiff[53779:3245168] too few arguments
2019-12-19 08:37:14.024 opendiff[53779:3245168] usage: opendiff file1 file2 [-ancestor ancestorFile] [-merge mergeFile]

使えるようになった。
なお、どういう原理で問題が解消したかは把握してない。

今回の記事は以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoでインストールからHello Golang!!

概要

MacにGoをインストールして、「Hello Golang!!」を表示させるところまでを解説していきます。
また、僕はシェルにfishを使っているので、fish向けの環境構築についても記載します。

環境

macOS Catalina 10.15.1
Homebrew 2.1.15
fish 3.0.2

Goインストール

$ brew install go
$ go version 
go version go1.13.3 darwin/amd64

Homebrew使うとコマンド一つでインストールできるので、とても便利です。

GOPATHの設定

GOPATHとは、Goのソースをまとめているディレクトリを指定している環境変数のことであり、
GOPATHを利用することで、外部ライブラリの導入やビルド作業を非常に簡単に行うことができます。

bashでGOPATHを指定するには、

$ vim ~/.bash_profile

で設定ファイルに以下追記をします。

export GOPATH=$HOME/go(←ワークスペースにしたい場所)
export PATH=$PATH:$GOPATH/bin

僕のようにシェルにfishを使っている人は、

vim ~/.config/fish/config.fish

で設定ファイルに以下追記をします。

set -x GOPATH $HOME/go
set -x PATH $PATH $GOPATH/bin

これでGOPATHの設定は完了です。

Hello Golang!!

ではいよいよ「Hello Golang!!」を表示させていきます。

ディレクトリ構成

最終的に以下構成になります。

go/
┣ bin/
   ┣ hellogolang 
┣ pkg/
┣ src/
   ┣ hellogolang/
              ┣ main.go 

以下ファイルのそれぞれの役割に関して記載します。
bin: 実行ファイルが格納されるディレクトリ
pkg: ビルドしたパッケージオブジェクトが格納されるディレクトリ
src: パッケージごとのソースが格納されるディレクトリ

main.goを作成する

go/src/hellogolang/main.goを作成したら、以下内容を記載する。

go/src/hellogolang/main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello Golang!!")
}

実行ファイルを作成する

go installで実行ファイルを作成することができます。

$ go install hellogolang

実行をすると、bin/hellogolangが作成されます。

Hello Golang!!を表示する

go/ディレクトリで以下コマンドを実行します。

$ bin/hellogolang
Hello Golang!!

Hello Golang!!が表示されました。

まとめ

Goは環境構築が簡単で勉強を始めやすくていいですね!!
あと、個人的にはfishはデフォルトでいい感じだし、コマンド履歴から自動で補完候補を表示させてくれたりと便利なので、ぜひ使ってみてください!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSが無くなってもエンジニアを続けるためのSwiftUI+MacCatalyst入門

メリークリスマス(イヴ)!!??

この記事は、第二のドワンゴ Advent Calendar 2019 の24日目の記事です。
そして、今日は12/24、クリスマスイヴです。みなさん、いかがお過ごしでしょうか。

この記事で伝えたい事

この記事は、以下についてまとめています。

  • SwiftUI を使って、動画プレーヤー上にコメントを表示、アニメーションさせる方法
  • Mac Catalyst で、 iOS/iPadOSアプリをMacアプリとして動作させる方法(ここはちょっとだけです?)

対象読者は、iOS開発の経験があり、SwiftUIをこれから触っていこうとしている人です。

自己紹介

お仕事では、iOSアプリ開発を担当しています。
現在の私の役割としては、プロジェクトの進行管理や、調整ごとがメインなため、どうしても自分で手を動かすことが減ってしまっています。
しかし、その状況に甘える事なく、継続して新しい技術に触れ、技術的な視野・知識を広げていくことがエンジニアとして必要不可欠だと感じています。
というわけで、今回、エンジニアとしてのお勉強の一環として、最近学習したSwiftUIの基本事項をまとめるという形で、アドベントカレンダーに参加させていただこうと思います。よろしくお願いします ??‍♂️

ちなみに…
自己紹介ついでに、これまで社内LTで発表したものやAppStoreに公開したものについて触れておくと、
顔認識を使って降り注ぐ自分の顔を避けながら進む、ほうれいせん予防の弾幕ゲームアプリ とか、きれいなジャイアンからヒントを得たARKitを使ってイケメンになれるアプリ とか、とてもここでは説明できないようなちょっとおかしなアプリばかりです…
が、社内のエンジニアの皆さんはそんなふざけたテーマでも暖かく受け止めてくれていましたw
…… 大丈夫です。今回は真面目なテーマで書きますので ?

こんな感じでした。
F8E20E62-7FB4-4B5F-8DD2-B2468F2A3F62.jpeg683AA08B-9546-4284-B3FC-11A66AA2C644.jpeg

2019年のiOS開発トピックス

さて、iOSの今年のトピックスといえば、なんといっても、
WWDC2019で Mac CatalystSwiftUI が発表されたことです。

Mac Catalyst は、iPad AppをMac Appでも動かせるようにするという画期的な取り組みです。これに伴い、UIKitやAppKitなどのフレームワークが拡張されました。
SwiftUI は、これまでのStoryBoardやXIBを使わずにswiftコードだけでUIが作れるという仕組みです。iOSやMacなどのUIを一括で作成する際にも SwiftUI が使われます。

Mac Catalyst の理由について、Appleは、iOS/iPadOSのAppStoreには多くのアプリが存在しており、それらの多くのアプリがMacでも使えるようになることで、Macユーザがより便利になり、また、デベロッパにも新たな機会を提供できるとしています。1
この取り組みがうまくいけば、 閑散としている MacAppStoreは活性化して、私たちスマホエンジニアは、デスクトップアプリ開発へと、活躍の幅を広げていけることでしょう。そう!これを自在に使いこなすようになれれば、もしiOSが無くなっても、Macアプリ開発者としてやっていける(はず)です!

ということで、今回は、この SwiftUI の基本事項と Mac Catalyst での開発の導入までをまとめていきたいと思います。

動作環境

以下の環境で動作確認しました。

  • macOS Catalina(10.15.2 Beta)
  • xcode11.3 beta

※ ベータ版である理由は特にありません。ベータ版じゃなくても動作するはずです。

開発の準備

SwiftUIで開発を進めるために、まずは、xcodeの新規プロジェクトを作成します。
話を簡単にするために今回は、iOS > SinglePageApplicationを選択します。

ツリーの中から、ContentView.swift を選択します。このファイルがSwiftUIのエントリポイントになります。

Sampleアプリのお題

Sampleプログラムを組みながら、SwiftUIの基礎を把握していきたいと思います。
ドワンゴといえば、niconico 2 です。niconicoは映像の上にコメントが流れることが特徴的な動画/生放送サービスなので、サンプルプログラムもそれにちなんだものにしたいと思います。

今回は、以下のような動画の上にSwiftUIでコメントに見立てたテキストを並べて動かすというものを作ってみたいと思います。

GIFイメージ.gif

映像は、サンタが機械(MACHINE)に溶け込み、光速に飛び込んでいく様子を撮影したものです。
重ねて表示されるテキストは、お寿司です?
ハマチがちょっと痛んでしまっています。

SwiftUIの基礎を把握する

UI部分のサンプルコードは次の通りです。

ContentView.swift
import SwiftUI

struct ContentView: View {

    var body: some View {
        PlayerVCView()
        .overlay(GECommentView())
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

まず、SwiftUIの要点について、サンプルコードにも触れつつ、順に説明していきます。

SwiftUIではView構造は構造体(struct)で表現する

上記コードのメインとなるのは、ここです。

strict ContentView: View {
    //...
    var body: some View {
        // ここにViewの構造を定義する
        PlayerVCView()
        .overlay(GECommentView())
    }
    //...
}

ひと目見てわかるとおり、SwiftUIではViewの構造は、構造体(struct)で表現します。

そして、viewは定義するだけでよく、 addSubview() や removeFromSuperview() などの呼び出しは不要です。viewを組みたい構造に合わせて、structを入れ子の構造にしてあげるだけでレイアウトが簡単に組み上がってしまいます。とってもシンプル!

View#overlay() は、view同士を重ね合わせるメソッドです。
たったこれだけで、プレーヤーの上にコメントを載せることができてしまいます。

SwiftUIは、Viewから座標やsizeの取得ができない

なんと、SwiftUIでは、Viewのstructから直接、xy座標やsize(width,height)を取得することができません。
極力、座標指定せずにVStackなどのContainerやalignment、paddingなどを駆使して、座標やサイズ指定せずにレイアウトを組めるのが理想的な形だと思いますが、ときには、座標やサイズが必要なケースもあります。
そういうときのために用意されているのが、GeometryReaderです。(これについては、後述します。)

また、UIViewのように、viewのxy座標とsizeを指定して、配置するということもできません。唯一指定できるのは、offset(x,y) ですが、これは、そのviewの中心の座標をx:0,y:0 として指定します。
それと、viewを配置する際のデフォルトは、親Viewの中心(center)に合わせて配置されるようです。

宣言的なコーディング

SwiftUIでは、各viewのふるまいや見た目などについて、宣言的なコーディングを行います。前述の .overlay().animation() など、.hoge() の形式で記述していきます。最初はとっつきにくいかもしれませんが、慣れてくるときっと読みやすいコードが書けるようになるんじゃないかなと思います。

特徴的なview構造のルール

前述のコードの場合、登場するviewとその親子関係は次のようになります。

親;ルートのview(土台になっているview)
    子:ContentView(bodyのviewと同じsize)
        孫:PlayerVCView(プレーヤーのview)        
            ひ孫:GECommentView(コメントのview)

そして、SwiftUIでは、Viewの位置やsizeは、親ではなく子のViewが決めることになっているようです。

では、ここから PlayerVCViewGCCommentView の中を見ていきたいと思います。

UIViewControllerをSwiftUIから呼び出す方法

一般的な SwiftUI のチュートリアルでは、もっと後のほうに出てくる内容ですが、UIKitを触った事がある人だったら、これを先に見たほうが、 SwiftUI の理解が早まるかなと思うので、先に説明してしまいます。

PlayerVCView (自作struct)は動画を再生してくれるプレーヤーのviewです。iOSで動画再生する場合、 AVKitAVPlayerViewController を使うのが簡単です。シークバーなどのコントローラもよしなに提供してくれます。
もうちょっと複雑な制御をしたい場合、プレーヤーを作り込みたい場合は、 AVPlayer を利用します。

今回は、サンプルプログラムということで、AVPlayerViewController を使いたいと思います。コードは以下のとおりです。

PlayerVCView.swift
import SwiftUI
import UIKit
import AVKit

struct PlayerVCView: UIViewControllerRepresentable {
    typealias UIViewControllerType = AVPlayerViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<PlayerVCView>) -> AVPlayerViewController {
        let vc = AVPlayerViewController()

        let asset = NSDataAsset(name:"movie")
        let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("movie.mp4")
        try! asset!.data.write(to: url)
        let item = AVPlayerItem(url: url)
        vc.player = AVPlayer(playerItem: item)

        vc.player!.play() // auto play
        return vc
    }

    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<PlayerVCView>) {
        // NOP
    }
}

ViewControllerを SwiftUI から利用する場合、 UIViewControllerRepresentable protocol を実装します。
実装が必要なのは、typealias1つと、次の2つのメソッドです。

  • typealias UIViewControllerType
    • 対象のViewControllerクラスの型を指定します。今回の例では、 AVPlayerViewController になります。
  • makeUIViewController(context: Self.Context) -> Self.UIViewControllerType
    • ViewControllerでいうところの viewDidLoad にあたるメソッドです。ViewControllerの初期化などはここで行います。
  • updateUIViewController(Self.UIViewControllerType, context: Self.Context)
    • viewDidAppear 的なもの? ViewControllerの設定変更などの処理はここで行う。ちなみに、特別な処理がない場合は、空っぽでも問題ありません。

あとは、前述のように、このstructのイニシャライザを、表示したいところで呼び出すだけです。

    PlayerVCView()

UIViewをSwiftUIから呼び出す方法

UIViewを呼び出す方法も紹介しておきましょう。
AVPlayerを利用するケースだと、以下のようなコードになります。

PlayerView.swift
import SwiftUI
import UIKit
import AVKit

struct PlayerView: UIViewRepresentable {
    typealias UIViewType = PlayerUIView

    func makeUIView(context: UIViewRepresentableContext<PlayerView>) -> PlayerView.UIViewType {
        let player = AVPlayer(url: URL(string: "<<動画のURL>>")!)
        player.play()
        let playerView = PlayerUIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), player: player)
        return playerView
    }

    func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext<PlayerView>) {
        // NOP
    }
}

class PlayerUIView: UIView {
    private let playerLayer = AVPlayerLayer()

    init(frame: CGRect, player:AVPlayer) {
        super.init(frame: frame)
        self.playerLayer.player = player
        layer.addSublayer(playerLayer)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
}

AVPlayerはUIViewを直接持っていないので、別途UIViewのクラスを作って、AVPlayerのlayerをaddSublayerし、そのveiwを返すようにします。

UIViewRepresentable protocol を利用しますが、 UIViewControllerRepresentable とほとんどやることは変わりません。

レイアウトを決める

ここからは、 GECommentView の中を解説していきます。
プレーヤー上にコメントを表示し、アニメーションさせるviewです。
実装は次のとおりです。

GECommentView.swift
import SwiftUI

struct GECommentView: View {
    @State private var moveIt = false
    @State private var commentStrs = [
        "マグロ",
        "サバ",
        "ハマチ",
        "たまご"
    ]

    var animation: Animation {
        return Animation.default
    }

    var body: some View {

        let animation = Animation.easeInOut(duration: 1.0)

        return VStack {
            CommentLabelView(text: commentStrs[0], offset: moveIt ? 120 : -120, backgroundColor: .red) { view in
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    self.moveIt.toggle()
                }
            }
            .animation(animation)

            CommentLabelView(text: commentStrs[1], offset: moveIt ? 120 : -120, backgroundColor: .blue) { view in

                       }
                .animation(animation.delay(0.1))

            CommentLabelView(text: commentStrs[2], offset: moveIt ? 120 : -120, backgroundColor: .green) { view in

                       }
            .animation(animation.delay(0.2))

            CommentLabelView(text: commentStrs[3], offset: moveIt ? 120 : -120, backgroundColor: .yellow) { view in

                       }
                .animation(animation.delay(0.3))

        }
        .onTapGesture { self.moveIt.toggle() }
    }
}

struct CommentLabelView: View {
    let text: String
    var offset: CGFloat
    let backgroundColor: Color
    var completion: (CommentLabelView) -> Void

    var body: some View {

        Text(text)
            .font(.headline)
            .padding(5)
            .background(BackgroundLabelView(cornerRedius: 5, color:backgroundColor))
            .foregroundColor(Color.black)
            .modifier(SlidingEffect(offset: offset, completion: { effect in
                self.completion(self)
            }))
    }
}

struct SlidingEffect: GeometryEffect {
    var offset: CGFloat
    let goalOffset: CGFloat
    var completion: (SlidingEffect) -> Void

    init(offset: CGFloat, completion: @escaping (SlidingEffect) -> Void) {
        self.offset = offset
        self.goalOffset = self.offset
        self.completion = completion
    }

    var animatableData: CGFloat {
        get { return CGFloat(offset) }
        set {
            offset = newValue
            if offset == self.goalOffset {
                self.completion(self)
            }
        }
    }

    func effectValue(size: CGSize) -> ProjectionTransform {
        return ProjectionTransform(CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: offset, ty: 0))
    }
}

struct BackgroundLabelView: View {
    var cornerRedius: CGFloat
    var color: Color

    var body: some View {
        GeometryReader { geometry in
            VStack(spacing:0) {
                RoundedRectangle(cornerRadius: self.cornerRedius).foregroundColor(self.color)
                    .frame(width: geometry.size.width, height: geometry.size.height / 2)
                RoundedRectangle(cornerRadius: self.cornerRedius).foregroundColor(Color.white)
                    .frame(width: geometry.size.width, height: geometry.size.height / 2)
            }
        }
    }
}

struct GECommentView_Previews: PreviewProvider {
    static var previews: some View {
        GECommentView()
    }
}

まず注目してほしいのが、bodyの中の、大枠の構造です。

    var body: some View {
      //...
      return VStack {
        //...
      }
      .onTapGesture { self.moveIt.toggle() }
    }

VStack というのは、V = vertical なstack、つまり垂直に要素を並べるためのstructです。このようなstructはいくつか種類があり、

  • HStack … 水平に要素を並べられる
  • ZStack … overlayで要素を並べられる
  • ScrollView … スクロールさせられる
  • Spacer … スペースを作り出す

などがあります。3

ジェスチャーを定義する

これも簡単です。
先ほどあった以下の部分は、

      .onTapGesture { self.moveIt.toggle() }

該当のviewをタップした際に、カッコ内の処理をさせるという事を示しています。
この例では、viewをタップする度に、moveItプロパティのbool値をtrue/falseとトグルで切り替えます。

アニメーションを定義する

アニメーションもジェスチャーのように簡単に定義できます。
先ほどのコードの以下の部分、

    let animation = Animation.easeInOut(duration: 1.0)

    CommentLabelView(...) { view in
    }
    .animation(animation.delay(0.1))

    CommentLabelView(...) { view in
    }
    .animation(animation.delay(0.2))

このように書くだけで、イージングでアニメーションできます。 4
よく見ると、下のCommentLabelViewには、delay をかけています。
こうする事で、同時にアニメーションを効かせたときに、ちょっとずつアニメーションをずらしていくことが可能です。

他にも、linear や、spring など、様々なアニメーションが用意されています。5

viewの状態を管理する

viewの状態管理には、いくつか種類がありますが、今回は、StateBinding について紹介します。

State - viewの内部でのみ状態を持たせたい場合

viewの内部でのみ状態を持ちたい場合は、 @State プロパティラッパーを使います。

    struct GECommentView: View {
      @State private var moveIt = false
      @State private var commentStrs = {
        //...
      }

    //...
    var body: some View {
      //...
        CommentLabelView(text: commentStrs[0], offset: moveIt ? 120 : -120, backgroundColor: .red) { view in
          //...
        }
    }

途中、省略しまくりなコードで若干わかりにくいですが、注目すべき箇所は、body内で moveIt がCommentLabelViewを生成する際の条件判定に利用されているということです。
このように、@State なプロパティをbody内で参照している場合、そのプロパティ値を変更するたびに、body内のviewが再計算されるという仕組みになっています。
これによって、Viewの状態に合わせてアニメーションさせたり表示を変更することが可能になります。

たとえば、以下のように書くと、

    var body: some View {
      if moveIt {
        HogeView()
      }
    }

moveItがtrueのときだけHogeViewを表示し、falseのときは非表示(viewが存在しない状態)という動きを作ることができます。

Binding - viewの外から状態を指定したい場合

viewの外から状態を指定したい場合は、 @Binding を使います。
今回のサンプルでは、Bindingを使っていないので書き方だけ紹介します。

    struct HogeView: View {
      @Binding var moveIt: Bool = false
      //...

    }

このように定義し、

    HogeView(moveIt: true)

外から状態を渡します。

GeometryReaderで座標やsizeを取得する

最初のほうで少し触れましたが、viewから座標やsizeを取得する方法がありません。
そういうときは、GeometryReaderを利用します。

    struct BackgroundLabelView: View {
      //...
      var body: some View {
        GeometryReader { geometry in
          VStack(spacing:0) {
            RoundedRectangle(cornerRedius: self.cornerRedius).foregroundColor(self.color).frame(width: geometry.size.width, height: geometry.size.height /2)
            RoundedRectangle(cornerRedius: self.cornerRedius).foregroundColor(Color.white).frame(width: geometry.size.width, height: geometry.size.height /2)
          }
      }
    }

このように、GeometryReader のクロージャの引数の geometry (GeometryProxy型) を使うと、sizeやwidth、heightなどが取得できます。
この場合、BackgroundLabelViewを貼り付けたCommentLabelViewのTextのサイズをとることができます。

GeometryEffectで複雑なアニメーションを実現する

シンプルなアニメーションの場合は、.animation()withAnimation { } で十分ですが、複雑なアニメーションを行う場合は、それだけでは難しいです。
そういうときは、GeometryEffect を使うとよいです。

struct CommentLabelView: View {
    //...

    var body: some View {

        Text(text)
            //...
            .modifier(SlidingEffect(offset: offset, completion: { effect in
                self.completion(self)
            }))
    }
}

struct SlidingEffect: GeometryEffect {
    var offset: CGFloat
    let goalOffset: CGFloat
    var completion: (SlidingEffect) -> Void

    init(offset: CGFloat, completion: @escaping (SlidingEffect) -> Void) {
        self.offset = offset
        self.goalOffset = self.offset
        self.completion = completion
    }

    var animatableData: CGFloat {
        get { return CGFloat(offset) }
        set {
            offset = newValue
            if offset == self.goalOffset {
                self.completion(self)
            }
        }
    }

    func effectValue(size: CGSize) -> ProjectionTransform {
        return ProjectionTransform(CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: offset, ty: 0))
    }
}

CGAffineTransform を使って、色々とviewの形状を変更することができそうですが、今回はシンプルにviewを左右に移動させることに利用しています。

サンプルコードで工夫したところは、アニメーションによってviewが左右の端まで来たことを親view(この場合、GECommentView)に通知するために、completionのクロージャを利用しているところです。

Macアプリとして実行する

ここまでSwiftUIの基礎を駆け足で紹介してきました。
最後にMacCatalystを利用して、macアプリとして動かしてみましょう。

実行は非常に簡単です。

プロジェクトファイル>General>Deployment Info>Device>「Mac」のチェックをつける

これだけです。 6 7

maccheckbox_screenshot

実行すると、このような表示になります。

iphone_screenshotmac_screenshot

おわりに

やや駆け足でしたが、SwiftUIの使い方の概要を紹介しました。
(Mac用に最適化する部分については全く触れられなかったので、もっと勉強していきたいと思います。)

viewを組み立てるときの概念がこれまでのUIKitとは異なっていて、最初はとっつきにいくい印象を受けますが、慣れればもっと簡単に、複雑なレイアウトをシンプルなコードで実現できるのではないかと思います。
viewまわりのコーディングをGUIを使わずに開発できるようになることは、今後のアプリ開発スタイルにも様々な影響を与えるのではないかなと思います。

今後は、そのあたりも模索してみたいと思っています。形になったらまた記事にしたいと思います。最後まで読んでいただき、ありがとうございました!

明日は、アドベントカレンダーも最終日です。
しかし… おかわり版である 第二のドワンゴ Advent Calendar 2019 の最終枠はまだ空いている模様。果たしていったい誰が書くのか!? いや、書かないのか!?乞うご期待です!

それではみなさま、素敵なクリスマスをお過ごしください?

お役立ちリンク

SwiftUI Tutorials
まずは一次情報を。チュートリアルがしっかり用意されているので学習しやすいです。
APIドキュメントはまだ説明が足りていない箇所も見受けられます。

https://developer.apple.com/documentation/swiftui
https://developer.apple.com/tutorials/swiftui/

Mac Catalyst
こちらも一次情報。
しっかりドキュメントがまとまっています(まだあまり読めていませんが…)

https://developer.apple.com/design/human-interface-guidelines/ios/overview/mac-catalyst/
https://developer.apple.com/documentation/uikit/mac_catalyst

The SwiftUI Lab
SwiftUIの細かいところまで具体的なサンプルコードで解説されていてすごく参考になります。

https://swiftui-lab.com
https://swiftui-lab.com/swiftui-animations-part2

Personal-Factory
WWDCのセッションや、SwiftUIの基礎について、詳しくわかりやすく書かれています。
最初に一読したほうがその後の学習が楽になると思います。

https://blog.personal-factory.com/2019/08/20/building-custom-views-with-swiftui/
https://blog.personal-factory.com/2019/12/08/how-to-know-coorginate-space-by-geometryreader/

AVPlayerをSwiftUIで利用する方法
AVPlayerをSwiftUIで利用する際の実装方法の参考になります。
https://medium.com/@chris.mash/avplayer-swiftui-b87af6d0553

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む