[iOS][Gemfile] bundle instsall すると Gemfile.lock に差分が出てしまう時の対処法

1.はじめに

複数人で開発をしていると、依存ライブラリのバージョンを縛る場合が多いと思います。
例えば Podfile.lock や Cartfile.resolved などでバージョンを縛り、
それに沿って環境構築をすることになります。

Gemfile.lock も同様なのですが、bundle install で環境構築をすると、
自分だけなぜか Gemfile.lock ファイルが更新されてしまう場合がありました。

今回はそれの解決方法についてまとめます。

2.前提条件

Mac: macOS Big Sur 11.5.1
Bundler: とあるバージョン

3.原因

Bundler のバージョンが他の人と異なっている場合、上記のようなことが起こり得ます。
(他の原因もあるかも知れませんが 🤔)

4.Bundler とは

Bundler provides a consistent environment for Ruby projects by tracking and installing the exact gems and versions that are needed.

https://bundler.io/

訳: Bundlerは、必要なgem やバージョンを正確に追跡してインストールすることで、
Rubyプロジェクトに一貫した環境を提供します。

4-1.Gem とは

The RubyGems software allows you to easily download, install, and use ruby software packages on your system. The software package is called a “gem” which contains a packaged Ruby application or library.

https://guides.rubygems.org/

訳: RubyGems は、Rubyのソフトウェアパッケージを簡単に
ダウンロード、インストールして、システム上で使用することができるソフトウェアです。
ソフトウェアパッケージは「gem」と呼ばれ、
Rubyのアプリケーションやライブラリがパッケージ化されています。


つまり Bundler とは Ruby 用ライブラリ(つまり gem) の
パッケージやそのバージョンを指定・特定してインストールすることができる gem

ということになります。

パッケージやそのバージョンの指定は Gemfile に記述します。
そして bundle installbundle update をすると、
gem ライブラリの依存関係が Gemfile.lock に出力されます。

5.解決方法

タイトルの件について、例えば以下のような方法があるかなと思います。

  1. bundle install 時のバージョンを指定する
  2. Bundler のバージョンを合わせる
  3. Gem のバージョンを合わせる

上に行くほど暫定的な対応になります。

逆に言うと下に行くほど根本的な対応になりますが、
その分影響範囲が広くなります。

5-1.bundle install 時のバージョンを一時的に合わせる

これは個人の環境のみで完結できます。

他の人の Bundler のバージョンを聞きます。

bundler -v

で、他の人の Bundler のバージョンがが例えば 2.0.2 なら、
以下のようなコマンドを打てばよいです。

bundle _2.0.2_ install

バージョン番号の前後にアンスコを付ける形です。

2.0.2 が自分のマシンに入っていない場合は、
まず以下のようにインストールする必要があります。

gem install bundler -v 2.0.2

このようにすると既存の Gemfile.lock を変更することなく環境構築ができるかと思います。

ただし毎回バージョン指定して bundle install する必要がありそうです。
やや面倒くさいですね 🤧

5-2.Bundler のバージョンを合わせる

みんなで Bundler のバージョンを合わせます。

他の人のマシンに Bundler の特定のバージョンが入っていない場合は、
上記と同様にインストールしてもらう必要があります。

gem install bundler -v 2.0.2

この手段の場合、他の人も bundle install をすると
Gemfile.lock に差分が出る状態となる場合があります。

ですので今後はその新しい Gemfile.lock を共通で使っていくことになります。

5-2-1.他の人は差分が出ない場合

自分のBundler のバージョンが他の人の Bundler よりも進んでいる可能性があります。
その場合は自分のBundler のバージョンを下げます。

以下のようにして自分のバージョンのBundler を消しましょう。

gem uninstall bundler -v 2.1.4

もし以下のようなワーニングが表示されて消せない場合。

Gem bundler-2.1.4 cannot be uninstalled because it is a default gem

自分がGem のデフォルトバージョンのBundler を使用していて、
それは消せない、と怒られてしまっています。

そのような場合は、後述のようにみんなでGem のバージョンを合わせるか、
Gem のデフォルトバージョンのBundler を削除するか、
の選択肢があります。

Gem のデフォルトバージョンのBundler を削除する方法は以下に詳しいです。
Qiita: docker内でGem(特にbundler)のdefault設定を解除したい!

が、この方法はあまりおすすめできないようです。
他のGem ライブラリで、
デフォルトバージョンを指定したBundler を利用している可能性があるためです。

rbenv を使っている場合、デフォルトはこの辺りに定義されています。
(パス中のバージョンは適宜読み替えてください)

/Users/<username>/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/specifications/default/

5-2-2.なぜ他の人は差分が出ないのか

自分のBundler のバージョンが他の人より進んでいて 2.1.4 だった場合。
他の人が bundle install をすると、2.1.4 未満となる訳で、
少なくとも以下のBundler のバージョン記述に差分が出て欲しいものです。

BUNDLED WITH
   2.1.4

が、ここの箇所には差分は出ません。

これはBundler の仕様として、
BUNDLED WITH よりも古いバージョンの bundler を使っている人は、
bundle install しても BUNDLED WITHの記述を更新しない、ということのようです。

この辺りのことは以下に詳しいです。
Qiita BUNDLED WITH で Gemfile.lock が更新されてしまう件

5-3.Gem のバージョンを合わせる

みんなで Gem のバージョンを合わせる方法です。
これでデフォルトのBundler のバージョンも一致すると思います。

gem update --system

この辺りは以下の記事に詳しいです。
Qiita: 【Rails】bundlerのdefaultを変更する方法。 Warning: the running version of Bundler (x.x.x) is older than the version that created the lockfileの対処法。

6.最後に

複数人での開発をしていると共通の環境構築をするのは難しいですね 😣

以上です。
お疲れ様でした!

[iOS][Bundler] bundle install で Errno::EACCES: Permission denied @ dir_s_mkdir … というエラーが出た時の対処法

1.はじめに

bundle install とコマンドを打つと以下のようなエラーが出る時があります。

Errno::EACCES: Permission denied @ dir_s_mkdir - /Users/<username>/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/extensions/x86_64-darwin-20/2.7.0/json-2.6.1
An error occurred while installing json (2.6.1), and Bundler cannot continue.
Make sure that `gem install json -v '2.6.1' --source 'https://rubygems.org/'` succeeds before bundling.

それの解消法を記載します。

2.前提条件

Mac: macOS Big Sur 11.5.1
Bundler: 2.0.2

3.原因

ディレクトリが root ユーザになっていることが原因の可能性があります。

僕の場合は
/Users/<username>/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/extensions/x86_64-darwin-20/
以下が root ユーザでした。

$ ls -al /Users/<username>/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/extensions/2.7.0/extensions/x86_64-darwin-20/
drwxr-xr-x  3 root     staff   96  9 22 17:19 .

そのため、コマンド実行時に権限がないと怒られていたということです。

4.解決方法

以下のコマンドでユーザを再帰的に変更すれば解決できるかと思います!

chown -R <username> /Users/<username>/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/extensions/x86_64-darwin-20

<username> の部分は自身のユーザ名で置き換えてください。

5.最後に

少しでも助けになれば幸いです。

以上です。
お疲れ様でした!

[Android] プロジェクト作成からViewBinding を使って画像表示するまでをまとめる

1.はじめに

本ポストでは以下の内容に馴染むことを目的としています。

  1. 画像をプロジェクトに組み込む(単一ファイルをdrawable に入れる)
  2. ViewBinding を導入する
  3. ViewBinding を使って動的にViewを操作する

参考

  1. Android Developers: Resource Manager でアプリの UI リソースを管理する
  2. Android Developers: ビューバインディング

2.前提条件

Mac: macOS Big Sur 11.5.1
Android Studio: Arctic Fox | 2020.3.1 Patch 3
Gradle: 7.0.2

3.プロジェクトを作る

まずは Empty Activity からプロジェクトを作成します。

Empty Activity を選択してプロジェクト作成

4.画像をプロジェクトに組み込む

最低限の手順ということで、何も考えずに一つの画像を組み込みます。

画像の組み込みは Resource Manager にて行います。
以下のようにAndroid Studio の左端から Resource Manager を選択します。

Resource Manager

左端にない場合には、以下のようにツールバーから
View -> Tool Windows -> Resource Manager で辿れます。

Resource Manager

Resource Manager を開いたら + ボタンを押し、Import Drawables を選択します。

Import Drawables を選択

インポートする画像を選択し、Open を押します。
すると以下のような画面が表示されるので、ファイル名を入力して右下の Next を押します。
今回は適当に sample_image と指定しています。

ファイル名を入力してNextを押す

確認画面が表示されるので Import ボタンを押します。
すると画像が追加されました!

Resource Manager に画像が追加されている

Project で確認すると、drawable 配下に追加されているのが分かります。

drawable 配下に追加されている

これで Android Studio 内で画像リソースを扱えるようになりました。

5.ViewBinding を導入する

build.gradle (Module) を開きます。

build.gradle (Module) を開く

android ブロック の中に、以下のように buildFeatures を記述します。

plugins {
    ...
}

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    ...
}

その後、Android Studio の右上にある「象さんアイコン」を押して、
プロジェクトをシンクします。

象さんアイコンを押す

これで ViewBinding が利用可能になりました!🙌

6.ViewBinding を使って動的にViewを操作する

いよいよ ViewBinding を使っていきます。
本ポストでは上記でインポートした画像を ViewBindiing で動的にセットしていきます。

6-1.空の ImageView を画面に配置

その前に下準備として、空の ImageView を画面に配置します!
activity_main.xml を開いて、以下のようにレイアウトを記述します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>

デザイン的には以下のようになります。

レイアウト

「imageView」というIDを指定した画像を画面中央に配置しています。
ViewBinding から操作する際はこのIDを元に画像を操作することになります。

表示されているアバター画像は tools 属性指定のため、開発時にのみ表示される画像です。
参照: Android Developers: ツール属性のリファレンス

これで準備が完了しました!
あとはコードで動的に画像のリソースをセットします。

6-2.ViewBinding を使う

MainActivity.kt を開き、MainActivityClass を以下のように記述します。

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)

        binding.imageView.setImageResource(R.drawable.sample_image)

        val view = binding.root
        setContentView(view)
    }
}

ActivityonCreate() メソッドに処理を記述します。

以下の記述で、ActivityMainBinding のインスタンスを生成しています。

binding = ActivityMainBinding.inflate(layoutInflater)

スタティックな inflate() メソッドで生成されるんですね。
ちなみに ActivityMainBinding クラス自体は自動で生成されています。

そして以下の記述で、ActivityMainBinding を介して
「imageView」というIDを持つ ImageView に、画像をセットしています。

binding.imageView.setImageResource(R.drawable.sample_image)

inflate() メソッドがどういうメソッドなのか最初はイメージが湧きづらいかもしれませんが、
バインディングクラスのインスタンスを生成するメソッドということで、
一つ覚えておくべきポイントかなと思います。

生成したバインディングクラスを介してビューを操作していくという形ですね!

ちなみに以下のコードでバインディングクラスの root を参照していますが、
これはビュー内の要素ではなくビューのルートを取得できるプロパティになっています。

val view = binding.root

今回の例でいうと、activity_main.xml に記述した
androidx.constraintlayout.widget.ConstraintLayout を取得しています。


最後にアプリを実行しておきます。

7.アプリを実行する

エミュレータのキャプチャ

狙い通りコードでセットした画像が表示されました!🙌

8.最後に

本文中にも書きましたが、
inflate() メソッドでバイディングクラスのインスタンスを生成し
そのクラスを介してビューを操作する、
と覚えておけば ViewBinding の利用方法をイメージしやすいのかなと思います。

以上です。
お疲れ様でした!

[Android] ビルド時に Use ‘–warning-mode all’ … というワーニングが出た場合の確認方法

1.はじめに

Android Studio でアプリをビルドする際、以下のようなワーニングが出ました。

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings

Gradle で非推奨の機能を使っているので、
個々の内容を見るときは --warning-mode all を利用してください、
と言われています。

本ポストではこれの設定方法を書きます。
5秒で設定できます。

2.前提条件

Mac: macOS Big Sur 11.5.1
Android Studio: Arctic Fox | 2020.3.1 Patch 3
Gradle: 7.0.2

3.設定する

gradle.properties を開きます。

gradle.properties を開く

そのファイルに以下を追記します。

org.gradle.warning.mode=all

その後、Android Studio の右上にある「象さんアイコン」を押して、
プロジェクトをシンクします。

象さんアイコンを押す

そうすると、次のビルドログの冒頭でより詳細なワーニングを確認できます!


ちなみに設定できる値は all, fail, summary, none の4つがあります。
参照: Gradleの公式ドキュメント

4.最後に

Gradle は iOS で言うところのプロジェクトファイルのようなものですね 🤔

以上です。
お疲れ様でした!

[iOS] Xcode が開かない、もしくは開いてもずっとプロジェクトのロードが終わらない場合の対処法

前提条件

Mac: macOS Catalina 10.15.6
Xcode: Version 12.4 (12D4e)

これで直る場合があります

Xcode を終了した後、以下のディレクトリを削除すると直る場合があります。

~/Library/Saved Application State/com.apple.dt.Xcode.savedState/

参考) Stack Overflow のこの回答

最後に

これで直れば幸いです!

[iOS] ‘weak’ cannot be applied to a property declaration in a protocol というエラーがあった場合の対応

はじめに

結論から言うと、Protocol での記述は weak は特に気にしなくてよさそうです。
Protocol では実クラスのプロパティが weakstrong かには関心がありません。


Swift において、weak var のプロパティを持つプロトコルを定義したくなった場合、
本ポストのタイトルのようなエラーが出ると思います。

例えば delegate を持つ Protocol を定義したい場合があると思います。

protocol BProtocol {
    weak var delegate: AProtocol? { get set }
}

実クラスでは delegate は weak var で宣言しないと循環参照となってしまいます。

ですが、Protocol での記述は weak は気にしなくてよさそうです。

簡単なプログラムで検証します。

前提条件

Mac: macOS Catalina 10.15.6
Xcode: Version 12.4 (12D4e)

検証

先に言ったように、Protocol での記述は weak は特に気にしなくてよさそうです。

以下のようなサンプルプログラムで検証します。

protocol AProtocol: AnyObject {
    func doSometing()
}

final class A: NSObject, AProtocol {
    var b: B?
    func raiseB() -> B? {
        b = B()
        b?.delegate = self
        
        return b
    }
    func doSometing() {}
}

protocol BProtocol {
    var delegate: AProtocol? { get set }
}

final class B: BProtocol {
    weak var delegate: AProtocol? // <- weak を外すと循環参照になります
    
    func printDelegate() {
        print("delegate: \(delegate)")
    }
}

ここで注目したいのが class B が持っている delegate です。

delegate プロパティはAProtocol に準拠したクラスの変数です。

BProtocol では delegate プロパティを weak 宣言していません(できません)。
が、実クラスのBでは weak 宣言しています。

最も単純なコードだと、A のインスタンスがnil になった時点で、
Bが保持している delegate プロパティも weak であれば、 nil になるはるはずです。

以下のようなコードを書いたとします。
「コードX」とします。

// コードX

var a: A? = A()
let b = a?.raiseB()
print("最初のprint")
b?.printDelegate()
        
a = nil
print("二番目のprint")
b?.printDelegate()

a = nil を実行された時点で、b が保持している delegate プロパティも nil になるはずです。

実際にそのようになります。

最初のprint
delegate: Optional(<HogeApp.A: 0x6000010f4320>)
二番目のprint
delegate: nil

一方、実クラスのB の delegate から weak を外してみます。
BProtocol では delegate プロパティを weak 宣言していません(できません)。

final class B: BProtocol {
    var delegate: AProtocol?
    ... 略 ...
}

これで「コードX」を実行します。

すると以下のようになります。

最初のprint
delegate: Optional(<HogeApp.A: 0x6000018b4530>)
二番目のprint
delegate: Optional(<HogeApp.A: 0x6000018b4530>)

つまり、Protocol では実クラスが weakstrong かには関心がない
ということになります。

当該のプロポーザル

Swift evolution に以下のプロポーザルがあります。
0186-remove-ownership-keyword-support-in-protocols.md

Protocol は weak だろうが strong だろうが宣言できるが、
実クラスではそれに従う必要はないため、混乱を招いてしまう。
そのため、そもそもProtocol ではそれらの宣言をできないようにするべきだ、と書かれています。

最後に

というわけで Protocol は所有権に関心がないということが分かりました。

以上です。
お疲れ様でした!

[デザイン] 欧文書体(フォント)についてざっくり整理してみた

1.書体(Typeface)とフォント(Font)の違い

まずこれの違いについて確認します。

ざっくりと言えば、

  • 書体(Typeface): 文字の形の世界観を表す
  • フォント(Font): その文字たちを実際に表示・印字する際のより細かな規則を表す

ということかなと思います。

具体的に言うと、Helvetica や Times New Roman が書体です。
Helvetica Bold 50px や Times New Roman Regular 10px など、
大きさや線の太さなど表示・印字するときに必要な情報まで含めたのがフォントとなります。

他のブログを見ると
現在だとほぼ同義のように使われているため、(書体の専門家でなければ)それぞれ言い換えても全然伝わるでしょう
と言う感じのようですね。

1-1.フォントの語源

上記のように、現在だと「書体」との境界線が曖昧になりつつありますが、
昔ははっきりと使い分けがなされていたようです。

フォントとはその昔「活字印刷する際に利用する活字たちの一揃い」の事を指していたようです。

印刷するときには活字たちを一文字ずつ手作業で並べる必要があったと思いますが、
その際文字の大きさが違っていると、正しいものに入れ替えをする必要があり労力がかかりますね。

印刷する時にフォントを同じものにするには今よりはるかに注意が必要だったと思うので、
きちんと使い分けられていたのだと思います。

2.欧文書体の大まかな分類

分類方法は特に定まったものがなさそうなため、以降の分類は一例として捉えてもらえれば。

欧文書体はまず大きく分けて以下の4つに分けることができそうです。

  1. セリフ体 (ローマン体)
  2. サンセリフ体
  3. スクリプト体
  4. 装飾体

参考: Fonts.com: Type Classifications

3.セリフ体とサンセリフ体の違い

セリフ体とサンセリフ体の違い
  • セリフがあるのがセリフ体
  • セリフがないのがサンセリフ体

セリフ(Serif)とは、文字の端にあるひげのような装飾です。

サンセリフ(Sans-Serif)のサン(Sans)はフランス語で「無い」という意味になります。
ですのでサンセリフはセリフがない書体という意味になります。


では以下、先述の4つの分類について更に細かく分類します。

4.セリフ体(ローマン体)

セリフ体

上述の通り、文字の端にひげのような装飾のある書体です。
和文書体で言うと明朝体に近いです。

4つに分類する場合、例えば以下のような分類ができます。

  1. オールドローマン
    1. 主な特徴: 古めかしい印象。
      ひげが太い。oが傾いている、eの横棒が斜めになりがち など。
      更に2つに細分化する場合は以下のようにもできる。
      1. ヴェネチアン・ローマン
        1. 主な特徴: e の横棒が斜め、oが傾いている、ひげが太い(Bracket Serif) など
        2. (Adobe) Jenson
        3. Centaur
        4. 他にも TrajanITC Berkeley OldstyleBriosoHightowerSchneidlerLutetiaSpira など
      2. オールド・ローマン
        1. 主な特徴: e の横棒が真横、oが少し傾いている、ひげが太い(Bracket Serif) など
        2. (Adobe) Garamond
        3. Caslon
        4. 他にも PalatinoBemboSabonVan DijckGalliardGranjonGoudy Old StyleMinion など
    2. 英語版のWiki: オールド・ローマンの一覧
  2. トランジショナル・ローマン
    1. 主な特徴: オールド・ローマンと以下のモダン・ローマンの中間。
      変遷中という意味でトランジショナル。トラジショナル(伝統的)という単語ではない。
      e の横棒が真横、oが直立、ひげがやや太い(Bracket Serif) など。
    2. Baskerville
    3. Times New Roman
    4. 他にも CenturyFreight TextFournierBookmanPerpetuaGeorgiaPlantin など
    5. 英語版のWiki: トランジショナル・ローマンの一覧
  3. モダン・ローマン
    1. 主な特徴: 都会的で洗練された印象。
      e の横棒が真横、oが直立、ひげがとても細い(Hair Serif) など
    2. Didot
    3. Bodoni
    4. 他にも Walbaum など
    5. 英語版のWiki: モダン・ローマンの一覧
  4. スラブ・セリフ
    1. 主な特徴: エジプシャンとも呼ばれる。
      モダン・ローマンとは逆で、ひげがとても太い(Slab Serif)。
    2. Rockwell
    3. Egyptienne
    4. 他にも ClarendonTrilbyCourierPrestige EliteIonicMemphisBetonSerifa など
    5. 英語版のWiki: スラブ・セリフの一覧

1.ヴェネチアン・ローマン から3.モダン・ローマンに向かって年代が新しくなります。

セリフ体の詳細な解説は以下のサイトたちがとても参考になりそうです。

5.サンセリフ体

サンセリフ体

こちらは文字の端にひげがない書体となります。
和文書体で言うとゴシック体に近いです。

4つに分類する場合、例えば以下のような分類ができます。

  1. グロテスク
    1. 主な特徴: エレガントなセリフ体との対比からグロテスク。ゴシックとも呼ばれる。線の太さが不均一。
    2. Akzidenz Grotesque
    3. Venus
    4. その他にも Franklin GothicDINMmonotype GrotesqueNews Gothic など
    5. 英語版のWiki: グロテスクの一覧
  2. ネオグロテスク
    1. 主な特徴: 線の太さがより均一、没個性的、汎用的
    2. Helvetica
    3. Folio
    4. その他 UniverseApercuRobotoArialAcumin など
    5. 英語版のWiki: ネオグロテスクの一覧
  3. ジオメトリック
    1. 主な特徴: より幾何学的な形。そのため可読性はあまりよくない場合が多い
    2. Futura
    3. Kabel
    4. その他にも Bernhard GothicErbarAvenirGothamAvan Garde GothicBrandon Grotesque など
    5. 英語版のWiki: ジオメトリックの一覧
  4. ヒューマニスト
    1. 主な特徴: セリフ書体の影響が強く、人間的。
    2. Gill Sans
    3. Optima
    4. 他にも FrutigerJohnstonMyriadTahomaTrebuchet MSVerdanaOpen SansSkia など
  5. 英語版のWiki: ヒューマニストの一覧

サンセリフ体の詳細な解説は以下のサイトたちがとても参考になりそうです。

6.スクリプト体

スクリプト体

こちらは手書きに似せたフォントになります。
2つに分類する場合、例えば以下のような分類ができます。

  1. フォーマルスクリプト
    1. 主な特徴: エレガントで風格がある。羽根ペンのような筆跡となる。
    2. Bickham Script
    3. Shelley Script
    4. その他にも Snell ScriptKuenstler ScriptSnell Roundhand など
  2. カジュアルスクリプト
    1. 主な特徴: 親しみやすく格式ばらない形。ペンやブラシで素早く書いたような筆跡となる。
    2. Brush Script
    3. Kaufmann
    4. その他にも ZapfinoMistral など

読みづらくはあるので、地の文で使うことは少なそうで、
表彰状や招待状などエレガントで高級な世界観を表現するときによく使われるようです。

ちなみにとても荘厳な趣のあるブラックレターという書体も、スクリプト体の一部です。

ブラックレター

スクリプト体の詳細な解説は以下のサイトたちがとても参考になりそうです。

7.装飾体

装飾体

看板や見出しなどに使用される、印象的なフォント。
インライン、グランジ、ステンシルなど様々な形態が内包されています。

  1. Ostrich Sans Inline
  2. Astonished
  3. Stencil

などがあります。

多様なため一括りに語れませんが、
インパクトを与えたいキャッチコピーなどに使うのがよさそうです。

可読性はよくないと思うので、通常は本文に使うのは避けた方がいいかもしれません。

8.最後に

ざっくりとまとめてみました。

ロゴやWebのデザインに活かせればと思います!

[iOS] BDDフレームワークのQuick について概要をまとめる

環境

  • Xcode 12.4
  • Swift 5.3.2

Quick とは

Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpecSpecta, and Ginkgo.

https://github.com/Quick/Quick#readme
振る舞い駆動開発のためのSwift や Objective-C 用のテストフレームワークです。
振る舞い駆動開発とは何ぞや、ということについては上のリンクの他、
本ブログの以下ポストなどを参照していただければと思います。
Web-y.dev: 振る舞い駆動開発(Behavior-Driven Development)とは

インストール方法

公式ドキュメントがあります。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/InstallingQuick.md

SwiftPM でもインストール可能でしたが、動作が不安定だった
(失敗したテストを再度回しても何のテストも実行されないなど)ので、
個人的には、SwiftPM以外の方法でインストールするのがいいかなと思います。

使い方のイメージ

Web-y.dev: 一つ一つの振る舞いテストのお作法 に書いた通り、
一つ一つの振る舞いテストは以下の3ステップをひとまとまりとして記述していきます。

  1. Given: システムがこの状態の時(前提条件)
  2. When: この動作をすると
  3. Then: このような振る舞いをする

Quick はこの3ステップを、人が読みやすい形で記述することをサポートしてくれる
フレームワークとなっています。

Quick でテストを書くときのお作法として Arrange, Act, Assert の手順が挙げられていますが、
上記の Given, When, Then と同義かなと思います。
https://github.com/Quick/Quick/blob/main/Documentation/ja/ArrangeActAssert.md#effective-tests-using-xctest-arrange-act-and-assert

具体的な使い方

Git に公式ドキュメントがあります。
https://github.com/Quick/Quick/blob/main/Documentation/ja/QuickExamplesAndGroups.md

そちらを見てもらうのが早そうですが、イメージだけ書いておきます。

import Quick
import Nimble
@testable import HogeApp

class HogeAppSpec: QuickSpec {
    override func spec() {
        describe("メイン画面") {
            var vc: MainVC!
            beforeEach {
                vc = MainVC()
                vc.load()
            }
            it("メインコンテンツが表示される") {
                expect(vc.collectionView.cellForItem(at: indexPath) as? ContentCell)
                    .toEventuallyNot(beNil()) // ここは Nimble で書ける
            }
        }
        describe("アカウント画面") {
            beforeEach {
                // 下2つのcontext の事前準備の共通処理をここに書ける
            }
            context("ログインしている場合") {
                it("アカウント情報が表示される") {
                    
                }
            }
            context("ログインしていない場合") {
                it("ログイン画面が表示される") {
                    
                }
            }
        }
    }
}

spec() メソッドをオーバーライドし、その中でクロージャをネストさせてテストを書いていきます。

describe もしくは context 内のクロージャで、
Given: システムがこの状態の時(前提条件)」と「When: この動作をすると」まで記述します。
(describecontext に違いはなく、ただのエイリアスです)

it 内のクロージャで「Then: このような振る舞いをする」を記述します。

beforeEachafterEach で共通の事前準備、後始末を書けます。

実行すると

上記のテストコードを実行すると、以下の3つのテストケースが順番に実行されます。

  1. アカウント画面__ログインしていない場合__ログイン画面が表示される
  2. アカウント画面__ログインしている場合__アカウント画面が表示される
  3. メイン画面__メインコンテンツが表示される

テストファイルの上から実行されるのでは無く、テストケース名の辞書順に実行されます。

メリットとデメリット

僕の思うメリットとデメリットは以下です。

メリット

  1. describecontext で処理を共通化するため、ボイラーテンプレート的な事前準備などのコードを複数書かなくて済む
  2. describe, context, it の組み合わせにより仕様書として読みやすい形になる
  3. 構造化することで、特に条件で出し分ける箇所の仕様が読み取りやすくなる

デメリット

  1. うまく仕様を分解して構造化できなかった場合、テストコード自体がスパゲッティコードになる
  2. 行数が多くなると、いまどの describe 内のコードなんだっけ?と混乱しがち
  3. func にテストを書かないため、ファイル内のテストケース一覧が参照できない

注意しないとデメリットにあるようにけっこうスパゲッティコードになりがちかなと思います。

そのような場合、テストコードの設計見直しや、
describecontext やテストファイルの分割などを検討するとよいのかなと思います。

銀の弾丸ではないので、本格導入する前に一度試してメリデメを把握するのがよいかと思います!

便利な機能

特定のテストケースを無効にする

公式の説明はこちら。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/QuickExamplesAndGroups.md#temporarily-disabling-examples-or-groups

xdescribexcontextxit のように先頭に x を付けると、
そのクロージャ内のテストを無効にできます。

特定のテストケースのみ有効にする

公式の説明はこちら。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/QuickExamplesAndGroups.md#temporarily-running-a-subset-of-focused-examples

fdescribefcontextfit のように先頭に f を付けると、
そのクロージャ内のテストのみを有効にできます。

テストを共通化する

it の内容がボイラーテンプレート的に同じような内容になる場合、
その部分も共通化することができます。

公式の説明はこちら。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/SharedExamples.md

SharedExample という機構を使います。

以下のようなイメージで使います。

class HogeAppSpec: QuickSpec {
    override func spec() {
        describe("アカウント画面") {
            context("ログインしている場合") {
                itBehavesLike(HogeSharedExamplesConfiguration.accountViewExample) {[
                    HogeSharedExamplesConfiguration.faqKey: "ログインのFAQを表示"
                ]}
            }
            context("ログインしていない場合") {
                itBehavesLike(HogeSharedExamplesConfiguration.accountViewExample) {[
                    HogeSharedExamplesConfiguration.faqKey: "ログインのFAQを表示"
                ]}
            }
        }
    }
}

class HogeSharedExamplesConfiguration: QuickConfiguration {
    static let accountViewExample = "アカウント画面"
    
    static let faqKey = "faq"
    
    override class func configure(_ configuration: Configuration) {
        var faq: String!
        sharedExamples(accountViewExample) { (sharedExampleContext: @escaping SharedExampleContext) in
            it("情報が正しい") {
                faq = sharedExampleContext()[faqKey] as? String
                expect(faq).to(equal("ログインのFAQを表示"))
            }
        }
    }
}

QuickConfiguration を継承したクラスを作成し、共通する it の処理を書いていきます。

で、テストする側は it で呼び出すのでは無く、代わりに itBehavesLike を使います。
テスト対象の値などを itBehavesLike のクロージャ内に辞書形式で書き、SharedExample に渡します。

サンプルではString を渡していますが、それ以外の型ももちろん利用可能です。

SharedExample 側は sharedExampleContext()[キー名] のような形で値を取り出し、
あとはそれを元にテストしていきます。

ちなみに、SharedExample 内には、以下のように it 以外も記述可能です。

class HogeSharedExamplesConfiguration: QuickConfiguration {
        sharedExamples("hogeExample") { (sharedExampleContext: @escaping SharedExampleContext) in
            beforeEach {   
            }
            context("hogeContext") {
                it("hogeIt") {
                }
            }
        }
    }
}

けっこうコストがかかる書き方のため、必要に応じて使うとよさそうですね!

最後に

Quick についてつらつらっと書いてみました。

本文に記した通り、銀の弾丸ではないと思うので、本格導入の前に一度試すのがよいのかなと思います。

他にもこんな便利な機能があるよ、というのがあればコメントに書いていただければと思います!

以上です。
お疲れ様でした!

振る舞い駆動開発(Behavior-Driven Development)とは

1.はじめに

テストコード。

開発期限が迫っているため書く余裕がない、ということはママあると思いますが、
個人的には書けるなら書いた方がいいかなと思っています。

テストを書ける状態のプロダクトコードになっていないなら、まずはリファクタから頑張る。。!
Baby-Steps で KAIZEN していくしか。。!


本ポストではテストコードを書くメリデメを含め、TDDの一流派のBDDについて書いていきます。

2.テストコードを書くメリデメ

個人の見解ですが、以下のようなメリデメがあるのではと考えています。

  • メリット
    • テストで担保されている箇所に関しては恐れることなくロジック変更ができる
    • 仕様を把握しないとテストが書けないため、逆に言うとテストを書く段階で仕様の認識漏れが減る
    • テスタブルなコードになるため、コードの結合度や凝集度がより適切になり、コードの品質が高くなる
  • デメリット
    • プロダクトコードのみを書く場合と比べると、短期的な開発スピード(特にコードをまずテスタブルに改修する場合)はもちろん遅くなる
    • 学習コストがそれなりにある。チーム内でテストコードのお作法を合わせるのもコストがかかる
    • 記述するコードが増えるため、コードレビューの負担がその分増える。巨大なプルリクエストだとテストコードまで見る余裕がないことも。。

3.TDDについて思うこと

短期的には成果が見えづらいため、焼畑的な開発を続けざるを得ない状況下においては、
取り入れるのが非常に難しいかなと思っています。

ですが長期的に見ると、低品質のコード仕様の認識漏れバグ修正などでの手戻りが減り、
スパゲッティコード化による属人化開発速度の低下を防げるため、
プロダクト的にも、またもちろん精神衛生上にもとてもよいのではと思っています。

で、プロダクトコードを書いてからテストコードを書くのも速くていいですが、
可能であればテストコードを書いてからプロダクトコードを書いた方が品質が高くなるのでは
ないかと思っています。

プロダクトコードがある程度の品質で動いているなら
テストが書けない設計になっていても、多少サボってもいいと思ってしまうのが人間なのかなと🤔

もちろん、最初からテスタブルなコードを書けるなら順番はどちらでもいいと思います。
が、それなら最初からテストを書いた方が、リファクタによる手戻りの可能性は減りそうですね。

テストを書いてからプロダクトコードを書く開発手法をTest-Driven Development (TDD) と呼びます。

本ポストでは以下、TDDの一流派である Behavior-Driven Development (BDD) について
概要を書いていきます。

4.振る舞い駆動開発(BDD)とは

BDDはTDDの一流派ともいえますが、TDDに対し以下の実現のための原則や工夫が加えられています。

– テストを「振る舞い」(機能的な外部仕様)の記述に特化させる

– ユーザーの要求やアーキテクチャの設計仕様といった、より上位のインプットとTDDのテストにつながりを持たせる

https://www.atmarkit.co.jp/ait/articles/1403/05/news035_3.html

「コード自体の振る舞い」や「システム・アプリの振る舞い」に焦点を当てたテストを書いていくTDDの一流派です。

僕のイメージとしては、

  • テストファイルに仕様を書いていく
  • つまり、テストファイルを読めばコードやシステムの外部仕様が理解できる

ということを目指している開発手法、と捉えています。


目指しますが、個人的には、
ある程度以上複雑なアプリの場合、仕様を完璧に網羅するのは難しいと思うので、
コスパを鑑みた上である程度で妥協するのが現実的にはコスパがよいのではとも感じています。

特に動的なコンテンツ、インタラクティブなコンテンツは、
とかく考えうる仕様・条件・動きが膨大になりがちです。

妥協する場合、どの程度で妥協するかはチームやプロジェクト内で、
「価値のあるテストとは? 価値のないテストとは?」などについて話し合い、
最適な落とし所の認識を合わせる必要があるかと思います。

5.振る舞い駆動開発のお作法

そもそも、振る舞い駆動開発(というより派生元のテスト駆動開発)は以下の3ステップの繰り返しで進めていきます。

  1. Red
  2. Green
  3. Refactor

5-1.Red

Red では振る舞いテストを書きます。
システムがこの状態の時この動作をするとこう振る舞う、というテスト(つまり仕様)を書きます。

なので、ここではテストというよりは実質、仕様を書いていくという意識に近いかなと思います。

で、実装がまだないため、テスト実行すると必ずこけます
テストがこけているのでIDEでは赤ポチがついているはずです。
なのでRed。

5-2.Green

次にGreenです。
こけていたテスト(つまり仕様)を通すように実装をします。
実装が終わった後にテストを回すと、赤ポチが緑ポチになります。
なぜならテスト(つまり仕様)を満たすように実装したからです。

5-3.Refactor

最後に、必要であれば実装をリファクタリングします。

これでおそらく、ある仕様を満たす最小限の実装ができたかと思います。

あとはこれの繰り返しです!

え、複数の出し分けの仕様があるのにこのままだと一つの条件しか考慮できておらず、マジックナンバーがある?
次のRed, Green, Refactor できれいに出し分け/リファクタすればいいでしょう!


こんな感じで、仕様を満たす最低限のロジックのみを書き、
それをテンポよく繰り返し改修していく。
仕様の解像度を徐々に上げていくような進め方。

それがBDD(TDD) かなと僕は捉えています。

6.一つ一つの振る舞いテストのお作法

一つ一つの振る舞いテストは以下の3つの手順で記述できると言われています。
Agile Allianceのメンバー Martin Fowler さんのブログより。

  1. Given: システムがこの状態の時(前提条件)
  2. When: この動作をすると
  3. Then: このような振る舞いをする

これらを1セットとして、一つ一つの振る舞いに対してのテストを記述できるはずです。

7.最後に

今ではテストを書かないとなんだか気持ち悪いと思えるようになりましたが、
当初はもちろん戸惑いました。

もちろんTDD/BDD が絶対正しいという訳ではなく、
コードの品質と、ある期間内での開発スピード感、どちらを重視するかによって
最適解は変わってくると思います。

以上、BDDについてでした.。
お疲れ様でした!

[iOS][Xcode] error: Building for iOS Simulator, but the linked framework ‘Hoge.framework’ was built for iOS.というエラーが出たときの対処法

はじめに

Xcode でビルドしようとした際にタイトルのエラーに遭遇したので対処法を書いていきます。

テストターゲットにCarthage で framework を追加しており、アプリをビルドしようとした際に遭遇しました。

前提条件

Mac: macOS Catalina 10.15.6
Xcode: Version 12.4 (12D4e)
Carthage: 0.36.0 (後半で0.37.0 以降にアップデートします)

現象

ビルド時に以下のようなエラーが出ました。

テストビルドのエラー

テキストに起こすと以下。

error: Building for iOS Simulator, but the linked framework 'Quick.framework' was built for iOS. (in target 'SampleApp02Tests' from project 'SampleApp02')

SampleApp02 というアプリなんですが、それのTestターゲットに
Quick.framework を追加した状態でのエラーです。
(このエラーメッセージはCarthage のバージョンで少し異なってくるかもしれません)

iOSシミュレータをビルドしているが、
リンクされているのはiOS用(=シミュレータ用ではない)のframeworkです

と怒られています。

簡単な対処法(ただし根本的ではない)

Stackoverflowに回答があります

Build SettingsValidate Workspace の設定を一度 Yes にし、その後 No に変更すればOKです。
デフォルト表示は No ですが、実際の挙動はどうやら Yes (Error) となっているようです。

Yes のままでもエラーは回避できますが、ビルド時にワーニングが出ます。

ただ、この対処法は少なくとも「フレームワークの検証をしない」という状態になるため、
将来的にはビルドできなくなる可能性も出てくるのかなと思います🤔

より根本的な対処法

そもそもの原因

そもそも Validate Workspace が Yes (Error) でエラーになるのは、
フレームワークの検証で引っかかっているからのようです。

どういうことかと言うと。

Carthage 0.36.0 で作成された framework は、
その中に実機用やシミュレータ用の複数のバイナリが含まれています。
それらが1つのframework として生成されます。

これはuniversal framework もしくは、より直感的に fat framework と呼ばれています。

fat framework のイメージは以下が非常に分かりやすいと思います。
Qiita: XCFrameworksに対応したCarthageを使ってみた CarthageがIntel Mac上でどのように動いていたか

そして、Xcode12.3 からはデフォルト(つまりValidate Workspaceが Yes(Error)の状態)で
fat framework を弾くようになりました。

そのため、ビルドができなくなったようです。

ちなみにアプリのビルド時、Carthage の copy-frameworks スクリプトを実行していると思いますが、
そこでは fat framework を元に

  • 実機ビルドなら実機用のみの framework を出力する
  • シミュレータビルドならシミュレータ用のみの framework を出力する

という処理をしているようです。

そこで XCFramework

fat framework が弾かれるようになった。
ではどうするか。

fat framework をやめ、XCFramework を使います。

XCFramework とは何か。
イメージを掴むには、中身を見るのが手っ取り早そうです。

XCFramework の中身

このようにXCFramework の中には複数の framework が含まれています。
ios-arm64-armv7 がiOS用、ios-arm64_i386_x86_64-simulator がシミュレータ用のframework かな🤔

このXCFramework を、既存の framework の代わりにXcode に組み込むことで、
ビルド時にXcode が適切なアーキテクチャのframework を選択してリンクするようになります。

ということは先に述べた copy-frameworks スクリプトも不要になります🙌

XCFramework を使う

主流になりそうだし使った方がよさそう、ということでCarthage でXCFramework を作ります!

そのためには Carthage 0.37.0 以降が必要なので準備しましょう。

準備できたらあとは --use-xcframeworks オプションを付けて
carthage コマンドを打つだけです。

carthage update --use-xcframeworks --platform iOS

これ以降の導入手順は難しくないです。

DevelopersIO: [iOS] CarthageでもXCFrameworkが使いたい
Qiita: XCFrameworksに対応したCarthageを使ってみた CarthageでXCFrameworksをビルドする
などが参考になりそうです。

最後に

以上です。

可能ならワークアラウンド的な方法ではなく根本的な対応をすると、
今後困る可能性が減りそうですね🙌

お疲れ様でした!