https://youtu.be/xYuNf5PRkkw
本日からは The Basics
の 定数と変数の出力
について眺めていきます。いわゆる print
的な話で、Swift に親しんでいる人にとってはお馴染みなところではありますけれど、うっかりすると見落としているところもあったりするかもしれない楽しいところでもあるので、そんな辺りをじっくりと見ていけたらいいなと思っています。どうぞよろしくお願いしますね。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #101
00:00 開始 00:58 定数と変数の出力 01:49 print 02:43 複数の値を出力可能 03:39 標準出力 07:26 テキストの出力先 09:26 ブレークポイント 11:34 共有ブレークポイント 13:09 ブレークポイントの高度な機能 17:22 例外ブレークポイント 19:17 従来の print 関数の扱い 21:03 Swift の表現力を活かした print 関数 24:22 可変長引数 24:44 Swift の print 関数に持つ印象 26:00 NSLog 29:35 末尾出力のカスタマイズ性 33:50 可変長引数と引数ラベルの組み合わせ 35:41 出力先を変更するには? 36:18 次回の展望 ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #101
では、始めていきますね。この勉強会では、Appleの公式のドキュメント「The Swift Programming Language」に沿って進めていく予定です。今日は、「The Basics」というランゲージガイドの章の中から、定数と変数の出力について詳しく見ていきます。話の流れ次第で途中で脱線することもあるかもしれませんが、基本的にはこのテーマに沿って進める予定です。
では、早速見ていこうと思いますが、Swiftの変数や定数の表示、出力には、他の言語と比べて特徴的な機能があります。Swiftに馴染んでいる人にとっては当たり前かもしれませんが、「The Swift Programming Language」に書かれている内容を改めて確認していきましょう。
まず、定数や変数の現在の値を print
関数で出力可能です。これは多くのプログラミング言語に共通する基本的な機能です。しかし、この説明だけではSwiftの特徴を十分に伝えきれていないかもしれません。Swiftの print
関数のユニークな点に注目してみましょう。
print
関数は、一つまたは複数の値を適切な出力装置に表現するためのグローバル関数です。この「一つまたは複数」という部分が重要です。多くの従来のプログラミング言語では、print
関数は一度に一つの値しか出力できず、複数の値を表示するためには一つの値にまとめる必要がありました。しかし、Swiftでは標準で複数の値を表示することができます。
具体的に言うと、print
関数は次のように使用します:
print("Hello, World!")
print("The value of x is", x)
これにより、文字列リテラルと変数の値を一度に出力できます。
また、出力装置としては標準出力が一般的で、その他に標準エラー出力やファイル出力などもあります。これらはプログラミングにおける基本的な概念として多くの人が知っているでしょう。たとえば、C言語では標準出力 (STDOUT
)、標準エラー出力 (STDERR
) があります。一部の古い書籍や資料では「おまじない」として紹介されることもありますが、これらは重要な概念です。
さらに、C++などでは標準入出力のためにiostream
ライブラリが使用されます。ここでは、名前空間 std
内で扱われることが一般的です:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
このように、言語ごとに標準入出力の取り扱いが異なる場合がありますが、Swiftにおいては print
関数が非常に強力であり、複数の値を簡単に表示することができます。
最初に触れるのは、標準的なプリント関数の使い方です。「Hello, World!」を出力する最も基本的な例から複数の値を同時に出力する例まで、さまざまなシナリオを考察しながら進めていきます。これにより、Swiftのユニークな特性を理解しやすくなるでしょう。
以上が今日の勉強会の冒頭部分です。次に、定数と変数の具体的な使用法についてもう少し深掘りしていきましょう。 標準入出力の他に、ファイルをオープンしてファイルハンドルに対して何かしらの書き込みを行う場合があります。ファイル名を指定してファイルに対して出力することができます。ここで言いたいのは、適切な出力装置に表示する方法が複数あるということです。
実際に print
文で試してみましょう。現在、プレイグラウンドで動かしていますが、ここで出力をかけるとプレイグラウンドのコンソールで出力されます。ターミナルで動かせば、ターミナルの標準出力に出力されます。このプログラムをアプリに変えて実行すると、アプリ用のコンソールに出力されることになります。標準出力先がコンソールのシステムログなどに出てくる場合もあります。これは、コンソールに出ていなくても「コンソール」と呼んでしまうので、少し語弊があるかもしれませんが、コンソールにしっかり出力されています。
プログラマー初心者によくありますが、デバッグの際に NSLog
や print
で情報を詳細に出力することが多いと思います。例えば、リリースビルドであってもユーザー名やパスワードを出力することがあり、その情報がコンソールに表示されてしまうことがあります。こうしたうっかりミスは重大な問題を引き起こす可能性があります。
ですから、大事な情報や機密な情報はプリントデバッグではなく、ブレイクポイントを使用することをお勧めします。リリースビルドでは出力されないので、安全です。プレイグラウンドではブレイクポイントが活用しづらい場合もありますが、プロジェクトでブレイクポイントを設定しておけば、リリースビルドには影響しません。
ブレイクポイントは一般的にはあまり活用されていないという意見もありますが、手軽にデバッグ情報を出力できる利点があります。ブレイクポイントで条件を設定し、必要なログメッセージを書くことができます。ただし、うっかりデリートキーで消してしまいやすい点は注意が必要です。頑張って設定した出力が一瞬で消える可能性があるので、注意が必要です。
ブレイクポイントによるデバッグはコードのリビルドが不要で、そのままテストできるという利点があります。一時的なデバッグには非常に有用です。共有したくないブレイクポイントもあると思いますが、個人的には自前でロッカーメソッドを作って #if DEBUG
条件で出力する方法もよく使いますが、ブレイクポイントの利点も理解しています。
ワークスペースでシェアする方法もありますが、コミットに含めると何となく気づくことができるので、うっかり消してしまったとしても大きな問題にはなりません。 そうですね、これがシェアドになったということです。シェアドなブレイクポイントと通常のブレイクポイントの違いについて説明します。シェアドのブレイクポイントは、みんなで共有するのに対し、シェアじゃない方は個人専用のブレイクポイントです。他にもブレイクポイントには色々な使い道があります。
ブレイクポイントと言うと止まってしまうイメージを持つ人もいるかもしれませんが、「Automatically Continue〜」にチェックを入れると、処理が止まらずに突っ切っていくことができます。これを使うとプリントデバッグ的な作業が容易になります。また、アクションを複数登録できるので、表示しつつ何かを実行させることもできます。
例えば、AppleScriptを動かすことができますが、それを極端に使う人は少ないかもしれません。他にもGPUワークロードをキャプチャーすることができたり、シェルを動かしたり、サウンドを鳴らしたりすることも可能です。突然特定のパスを通ったときにサウンドを設定しておくと、音で気づくことができます。
私自身マルチスレッドプログラミングで非常にシビアなタイミングでエラーが発生したときに、このブレイクポイントが役立ちました。止めてしまうとタイミングが合わないので、突っ切る設定にします。ログには残るので良いですが、例えば10分に1回しか発生しないような場合、ずっと監視しているのは現実的ではありません。そのため、音を鳴らす設定にしておくと便利です。
ですので、ブレイクポイントに馴染みがない方や、ブレイクポイントで何ができるかわからない方は、一度ブレイクポイントを右クリックして「Edit Breakpoint」を選び、色々と試してみると良いでしょう。特にブレイクポイントナビゲーターのプラスアイコンから追加できる「Exception Breakpoint」は便利です。特定の例外が発生したときに止めることができますので、複雑なマルチスレッドプログラミングを行う際にも役立ちます。
さらに、シンボリックブレイクポイントを使うと、特定の関数に突入するときに止めることができます。例えば viewDidLoad
のような自分が作成していない関数でも止めることができます。不思議なAPIが呼ばれてしまったときなどに、糸口をつかむために役立ちます。
ブレイクポイントの機能を知らない方や、止まるだけと思っている方は、ブレイクポイントの右クリックメニューやナビゲーターのプラスボタンを使って色々と試してみると良いでしょう。
デバッグの方法を学ぶのは難しいですが、大事です。デバッグの勉強会を開くのも面白いかもしれません。以前の話に戻りますが、NSLogで重要な情報が出ないように注意しましょう。適切な出力装置に情報が出力されていることを確認し、プリント文を適切に使うよう気を付けましょう。 ここからは「プリント分」として具体的に見ていきましょう。前にお話しした内容を振り返りながら解説しますね。
一般的にprint
文を想像する際、この1行目を見るだけで十分ではないかと思います。具体的には、バリュー(値)が何かに格納されており、その値を出力するためのものです。たとえば、変数が複数ある場合、それらを表示したいときにはちょっと工夫が必要です。Swiftらしいコードで書く場合、次のようにストリングインターポレーションを使います。
例として、`print("aはこの値、bはこの値")`
といった形で1つの文字列として整形し出力します。しかし、4行目の記号が多すぎると感じるかもしれません。分かりやすくするために次のように1つにまとめました。
print("a = \\(a), b = \\(b)")
これは、昔のprint
関数を使っていた人にとっては馴染みのある方法かもしれません。もちろん、この方法でも間違いありません。しかし、現在のプログラミング言語、特にSwiftではAPIの表現力がかなり広がっています。そのため、複数の値をprint
文で直接書くことができます。試しに書いてみますね。
例えば以下のようにします。
print(a, b, c)
このように複数の値をカンマ区切りで並べるだけで、各値がさらっと表示されます。デバッグ中に変数の内容を確認する際には、インターポレーションを使わなくても簡単に出力が可能です。例えばAとBの値を出力したい場合、次のように書けば良いですね。
print("Aは", a, "Bは", b)
とはいえ、例えば10行目のように、空白区切りだと視覚的に見分けがつかないこともありますよね。その場合、区切り記号を変えることもできます。次のようにします。
print(a, b, c, separator: ", ")
これにより、カンマ区切りで各値が表示され、視覚的に見分けやすくなります。このように書けるのは、Swiftのラベル付き引数が許されているおかげです。ラベル付き引数が導入されていない言語では、こう書いても意味が分からなくなる可能性があります。
また、このような柔軟なprint
関数の定義を理解すると、その多機能性に驚くことでしょう。これが分かりやすく、非常に便利です。以前のプログラミング言語、例えばG言語や、print
関数についても様々ありましたね。
たとえば、NSLogやその他の言語の祭り解除(デバッグ用出力)など、プリント関連の違いもありました。プリントFのようなフォーマット指定もありますよね。これについては別のセッションで詳しく見ていきましょう。 そうですね、print
関数を使うときには、C
言語の場合と似たような感じで出力内容をフォーマットできます。NSLog
はオブジェクティブCの場合のログ出力の方法で、テキスト部分にフォーマット指定子(例えば、%s
や%d
など)を含めて、その後に変数を渡すというスタイルですね。たとえば、以下のように書くことが多いです。
NSLog(@"Text with string: %s and integer: %d", stringValue, intValue);
print
関数に関しては、Swiftでも同様にフォーマット指定子を使って書くことができます。ただし、Swiftでは特徴的なストリング補完(文字列補間)が利用でき、よりわかりやすい表現が可能です。
例えば:
let stringValue = "Hello"
let intValue = 42
print("Text with string: \\(stringValue) and integer: \\(intValue)")
print
関数では、デフォルトでは新しい行で印刷を終了しますが、terminator
パラメータを使用してこれを変更することができます。例えば、以下のコードでは改行せずに連続して印刷します。
print("Hello, ", terminator: "")
print("world!")
これは、C
言語やオブジェクティブCの経験がある人には馴染み深いかもしれませんが、Swiftでも似たような柔軟性を持っている点が面白いところです。また、separator
を使うことで、出力時の要素間の区切り文字を指定することもできます。
例えば:
let items = ["apple", "banana", "cherry"]
print(items.joined(separator: ", "))
こうすることで、items
の各要素をカンマとスペースで区切って表示できます。
Swiftのストリング補完機能やprint
関数の柔軟な仕様は、開発者にとって非常に便利で、分かりやすいコードを書きやすくしていますね。こうした機能を活用することで、より読みやすく保守しやすいコードを書くことができるのが大きな利点です。 定義をちょっと見ておきます。これも面白いですよね。ちゃんとデフォルトバリューをうまく使って、printf
とかだと規定値で改行がどうやっても変更できないものに対して、こういうデフォルト値でうまくカスタマイズできるようになっているのは、素晴らしいと思います。特に print
関数や NSLog
関数など、いろいろと困るようなこと、たとえばセパレーターの設定などがガイドから定義できるのは、すごく素晴らしいですね。設計がちゃんとしているなと感じます。
拡張性を考えると、NSLog
の話を思い出しますが、NSLog
なんかは決め打ちで改行しちゃいますが、それでも何も問題にならないです。それがこうやって規定値によって制御されていることによって、普通に使う分には便利に使えますし、ちょっとここを変えたいという時に、独自の手法を使ったり、そこだけ printf
にする必要もなく、パラメータをちょこちょこといじるだけで済むというのは大事です。こういった発想で自分が何かAPIを用意する時にも提供するというのは、使いやすさの上でとても大事なことですね。
コメントにもいただいているプリント文の件についてですが、プリントしたい内容は Any
型として受け取るようになっています。ここも自分はすごく感動したところで、何個でも表示していける。また、ラベル引数によって分離することによって何個でも表示させられ、かつセパレーターやターミネータを自由にデフォルトから変えられるようなAPIになっているのは、スウィフトの表現力ならではですね。
さらに、コメントでいただいてた「for-in」のとき、for line in lines
で print(lineTitle, lineType, lineValue, separator:)
として表のように作っていくというイメージですかね。これ、Markdownで表を作るときに簡単に表を作れるので便利です。セパレーターがあるおかげで、行が変に分離しちゃうことも防げます。
プリント文で他にも便利なこととして、出力先が選べるのではなく、ストリームで出力することもできます。これについては、詳細を突き詰めると時間がないので、ここでは言及しませんが、プリント文をリダイレクトでファイルに書き出すこともできます。
最後に次回の話題にしようと思っていますが、プリント文で Any
を取るというところがとても大事なんですよね。普通は String
を取るところですが、スウィフトでは Any
を取るというところが重要です。これについては、今日は時間がなくなったので、また次回お話しできればと思います。
今日は時間になったので、勉強会を終わりにしたいと思います。お疲れ様でした。ありがとうございました。