https://youtu.be/bQNUDJ5IjjY
本日は、前回にも見てきた The Basics
の 定数と変数の出力
を踏まえてのインスタンスを文字列化する方法を具体的に確認していく続きです。前回は CustomStringConvertible
の文字列化について見ていったので、今回は残りの CustomDebugStringConvertible
の文字列化周りを眺めてみますね。それが終わったら先へ進むか、それか print
関数のソースコードを改めて眺めてみるか、成り行きで進めてみようと思ってます。どうぞよろしくお願いします。
——————————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #105
00:00 開始 02:30 インスタンスを文字列値として取得 04:41 書式化文字列 06:01 CVarArg には独自準拠できない様子 11:03 デバッグ文字列をインスタンスとして取得 13:12 普段 CustomDebugStringConvertible は使う? 15:04 debugPrint に対する印象 15:59 GitHub を使った利用頻度の調査 16:47 CustomDebugStringConvertible との使いわけ 18:01 debugPrint の出番は少なそう 19:50 dump 関数 21:59 dump の出力は debugDescription ? 23:41 debugPrint の出力はリリースビルドに含まれる? 25:24 dump の出力はリリースビルドに含まれる? 26:48 デバッグ用の文字列表現を変数で扱いたいとき 28:40 変換イニシャライザーによるテキストの切り替え 31:57 CustomDebugStringConvertible に準拠していない値の扱い 33:05 適用範囲の広いコードにするために 34:26 オプショナル型のテキスト出力 39:40 クロージング ———————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #105
はい、じゃあ始めていきますね。今日もまた引き続き定数と変数の出力について見ていくことになります。基本的なことは前回、前々回までで話し終えていますが、今回はその周辺的なところで補足したい部分が残っているので、それをカバーしていこうかなと思います。まずは早速プレイグラウンドに入りましょう。ただ、その前に初めて参加されている方もいらっしゃると思うので、軽くおさらいしておきます。
Swiftでは、どんなインスタンスでもテキスト出力が可能です。そして、そのテキスト出力の方法は言語標準で定義されており、どのようにテキストに変換するかが組み込まれています。また、これを独自にカスタマイズすることもできます。
出力の方法として、自分でカスタムする際には例えば CustomStringConvertible
というプロトコルに準拠させることで、そのインスタンスの内容を適切にテキストで表現することができます。例えば、Int
型の値 555
を文字列で持っていたとしても、インスタンスが適切な値として文字列を表示する際に、裏で何かが表示されることなくテキスト出力できます。
テキスト出力は、一般的には print
を使う方法が一般的です。具体的には、文字列として表示できる場合、インスタンスを文字列として取得できます。これには主に以下の4つの方法があります。
- String Interpolation を使う方法
CustomStringConvertible
に準拠したインスタンスが持つdescription
プロパティを使う方法String
のイニシャライザーに用意されているdescribing
ラベル付きのイニシャライザーを使う方法- 独自に
String
に型変換用のイニシャライザーを定義する方法
これが前回お話しした内容です。もし、この他にもインスタンスを文字列化する適切な方法を思い付いた方がいれば、ぜひ教えてください。
話しながら思い付いたのですが、sprintf
みたいなレガシーな方法もありますね。これも紹介しておく価値があるかもしれません。完全なレガシーではなくても、String
クラスの format
イニシャライザーを使う方法があります。ただし、これを使用するには Foundation
フレームワークをインポートする必要がありますね。
import Foundation
let value = 555
let formattedString = String(format: "%@", value)
しかし、これを使うためには引数が CVarArg
プロトコルに準拠している必要があります。この CVarArg
プロトコルは、C言語系の引数に互換性があることを表現するものです。Swiftでもこれを使えるように、互換性のある型として定義されています。
ただ、CVarArg
自体は特に何も要求するインターフェースがないですが、C言語やC++の可変長引数を受け取るインターフェースを実装するためには、おなじみの va_list
という型とともに使います。この互換性をSwiftで実現させるために、何か特別な条件が必要というわけではないですが、メモリ管理などの手間も発生することがあります。
ということで、この辺りの話も含めて進めていきます。質問があれば、なにか解決したいことがあればぜひ声をかけてください。 とりあえずこれでできるかどうか試してみようかと思いましたが、まだいろいろありそうですね。ビットパターンやオプショナル、ポインターについてです。特にオプショナルのときにはビットパターンがどう影響するのか、多少気になるところです。ビットパターンの辻褄合わせ用にマークをしておくという感じなのかもしれないですね。完全には理解していない部分もありますが。
何にしても、ある程度準拠させなければいけませんね。何か求められているような気がしましたが、特に指定されたわけではない感じですね。それでいて何かを求めてきました。バーグエンコーディング音頭の配列についても、ちょっと思いついたのですが、実装するにはいろいろと要求されるようです。この辺りに慣れ親しんでいる人がいるでしょうかね、Swiftでね。ビバーグエンコーディングをあまり知らないので、想像つきません。大したことを書いていないので、この辺りを載せるには若干苦労しそうです。
適当な値を入れるとどうなるのでしょうか。例えば、ゼロを入れてみましょう。大体この Int
の配列は何を取っているのでしょうか。nil
とか出てしまった。そうか、%s
とかにするともうちょっと違うかもしれませんね。nil
になってしまいました。1を入れるとどうなるのか試してみましょう。エラーが出ました。そうですか、いろいろやっていますね。これについて調べてみても面白そうですが、ひとまず変数名だけちょっと調べてみましょう。
標準のドキュメントに出てきたものもありました。普通に実装ができた感じです。2番目に出てきたものを見てみます。違いがあるようですが、一応何かの実装だと、Objective-CブリッジやNSオブジェクトも基本的に搭載しているのではないかと思います。いろいろと調査する必要がありそうですが、今はちょっと面倒なので、このあたりで止めておきます。昔はもう少し簡単だったと思うのですけどね。無条件にできていた気がしますが。
37行目をネイティブなSwiftで使うためにはもう少し知識が必要だと感じました。なので、37行目はお預けということで。ただ、ちゃんとすればできるので候補として挙げておきます。他に何かありますかね。
ここまで喋ってきて、自分が思い浮かぶものは4つないし5つかなと思います。個人的なおすすめは36行目です。せっかくなので、カスタムストリングコンバーティブルを使うのがおすすめかなという話を前回もしましたね。
Swiftにはもう一つ文字列変換の方法があります。カスタムデバッグストリングコンバーティブル(CustomDebugStringConvertible
)です。インテージャーにも CustomDebugStringConvertible
を実装して debugDescription
を使う方法です。こうやって例えばデバッグ的な情報を debugDescription
に書いてあげて、プリントのときには特に問題なく表示されるわけですが、これはデバッグ用のプリントだった場合にデバッグ用の詳細な情報が表示されるというものです。これも前回あたりにお話しした内容ですね。
今日はこれを文字列化して使う状況を紹介しようと思います。まずは CustomDebugStringConvertible
がどんなふうに動くかを確認しましょう。先にコードを書いてみましょう。デバッグ用の表示は debugPrint
を使うことで、出力が CustomDebugStringConvertible
で書いた表現になるわけです。
余談ですが、CustomDebugStringConvertible
は普段使いますか?開発をしていて、自分の場合は個人的に稀に使う程度です。今回の勉強会で初めて見たという人もいるかもしれませんね。普段から使慣れているかどうか、コメントや声などで教えてもらえると嬉しいです。 とりあえず動きました。このように差が出る、と言うと大げさですが、実際に差が出ます。そして、このようにデバッグプリントが得られるわけです。しかし、41行目から44行目までを見ても、特にデバッグ系の情報は含まれていません。ただし、内容に関連するテキストを使いたい場面、例えばエラーメッセージを出す際に詳細を記録したい場合などには、デバッグプリントが便利だったりします。
それをもう少しエラーハンドリングのエラー内容と組み合わせて使いたい場面では、文字列化が必要になってくるわけです。ここでコメントを拾ってみますが、やはりデバッグプリントをほとんど使わないという印象ですよね。私自身も確かに、デバッグプリント関数そのものはまず使わないですね。非常に稀に、ふと思い出した時に CustomDebugStringConvertible
を使いたくなることがあります。ただ、普段は気を配っていなければ普通に print
文で済ませてしまいます。
それにしても、 CustomDebugStringConvertible
を初めて知るという方も多くいらっしゃいますね。リンクを送ってくださった方もいますが、検索してくれたのですね。これによって、その一般的な使用頻度がわかります。ここで紹介されたリンクを開いてみるとゼロ件だったり、弊社のサイトが見れない場合もあるようです。面白いですね。
デバッグプリントの話をすると、意外に頻度が多い印象です。 CustomDebugStringConvertible
は、少なくともインスタンスそのものをしっかり表現しないといけない場面で使われるべきです。このような表現を無理に出すのはやり過ぎかもしれません。そのため、 CustomDebugStringConvertible
の使い分けをしっかり意識することが大事です。
確かにカスタムデバッグストリングコンバーティブルを上手に使うのは、大人な使い方と言えるかもしれません。それを意識して遊んでみると面白いでしょう。ただ、普段の中でカスタムデバッグストリングコンバーティブルを使う機会は少ないと思います。
たとえば、デバッグプリントとカスタムストリングコンバーティブルの両方を備える方はあまりいないかもしれません。この2つを両方備えることは少ないと思います。それにより、プリント関数が何を出力するか、デバッグプリントが何を出力するかを見極める必要があります。前回も少し触れましたが、デバッグプリント用のものになると description
がなくなります。このように、どの手段が適切かを意識すれば、多くの場合でプリントで事足りるわけです。
コメントでいただいた意見にも同感です。プリント分で細かい情報が得られなかったときには、デバッグプリントを使用するよりも先に dump
関数を使うと細かい情報が得られる、という方法もあります。 今はどういう感じになるのかな、こういうふうに得られて、しかもこういうふうに余計な情報が得られないんだ。でも下を見ると細かく出ています。この今選択したところがダンプで出てきたやつです。内部のプロパティとかも持っています。要はリフレクション的な表現が出てきて、これが便利なんですね。
そもそもデバッグプリントを想定するためだけに CustomDebugStringConvertible
を使って時間を費やすよりも、何も書かなくても dump
を呼んであげればいいです。プリントデバッグが目的なら dump
だけで充分ですね。カスタムの文字列表現が出てきて若干見にくいところはありますが、詳細な情報が得られるのでほとんどの場合、大体これで済ませちゃうんです。
ただし、型が大きすぎると膨大な情報が出てきたり、コンテナ型だったりすると、そのときに CustomDebugStringConvertible
を持ち出したりはしますけど、プリントデバッグだけであれば特に必要ないでしょう。
ちょっとコメントの話を、自分も興味があるので改めて整理してみると、dump
で description
と debugDescription
のどちらが出てくるんだろうということで、この2つが有効だったときの dump
の出力を見てみました。すると、すごいですね、面白いですね。プレイグラウンドの出力はどうでもいいですが、まず debugDescription
が出ているということは、dump
自体は debugDescription
を優先しているということですね。
もう一つのコメントは、「デバッグプリントはリリースビルドで出力されないのではないか」という点です。確かに、考えたことなかったんですが、あり得ますね。例えば、Swiftコードを書いて、以下のようにしてみます。
print("普通のプリント")
debugPrint("デバッグプリント")
Twistコンパイラーで普通にコンパイルすると、まず両方のメッセージが出力されます。ここでオプティマイズ(最適化)を有効にしてみますが、最適化しても両方出ますね。確か、-O
や -Osize
が最適化オプションで、-Onone
が最適化しないオプションですね。
また、Unchecked
オプションを使ってみても、どれも出力されます。つまり、デバッグプリントも dump
もリリースビルドではダダ漏れですね。確かに、リリースビルドでデバッグ情報が出なくても良いかもしれませんが、実際にはリリースされたアプリでもコンソール出力でデバッグしたい場合があるかもしれません。
そのため、デバッグストリング(Debug String)を文字列として扱いたいときには、CustomStringConvertible
および CustomDebugStringConvertible
に準拠している場合に、どう取り扱うかを考える必要があります。ストリングインターポレーション(String Interpolation)ではできないことがあるので、その際には別の方法を検討する必要があります。
これで、デバッグ周りの理解が少し深まってきた感じがしますね。 とりあえず、ここはどうにもならないという時は、「これでいいのかデバッグディスクリプションを使えば済むのかな」と思うことがあります。もしカスタムデバッグストリングコンバーティブルに準拠していなかったとすると、基本的なストリングインターポレーションで対処できます。ただ、逆の場合はどうにもならないかもしれません。
ストリングインターポレーションを使用する際には、カスタムデバッグストリングコンバーティブルに準拠している場合に限り、デバッグディスクリプションを使うと良いという話です。これが学問的に妥当かどうかは別として、もう一つの方法として、ストリングのイニシャライザーに reflecting
を使う方法があります。これは値を渡してあげる方法です。
ここまで、とりあえず実行してみると、デバッグディスクリプションが取れているのがわかると思います。
45行目について説明しますと、これは独自に定義した変換イニシャライザーです。同じように独自に定義したい場合、インテジャのイニットにラベルがないとどうにもならないので、ここでラベルを付けるわけです。「デバッグ」とか付けて value
を「デバッグバリューインテジャ」にする感じです。その上で、self = value
として、結局デバッグディスクリプションを呼ぶように実装してあげて、String(describing: value)
として出力する方法もあります。これもしっかり機能します。
48行目はちゃんとツインになっていて、ラベルがないイニシャライザーを搭載する場合には、Type Preserving Type Conversion(値を保全する型変換)の役割を持つイニシャライザーになり、ラベルを搭載した場合は、 Narrow Type Conversion(具体的な型変換)になるという、APIデザインガイドラインに従った規定があります。このため、これらのイニシャライザーはツインとして整えられます。
初めはそのものを変換するのだから、「ローバリュー」とか書くよりも「ディスクリプション」とした方がツインになった感があります。純粋な変換イニシャライザーであり、デバッグ情報を含めた文字列を表すためのプロパティとなります。「デバッグ」というよりも「デバッギング」といった感じが適切かもしれませんが、とにかく正しいラベルをつけることが重要です。
標準で reflecting
が備わっているため、それを使うのが良い場合もありますが、全く同じとは言えません。デバッグディスクリプションがあればそれを優先して使い、なければ通常のテキスト表現(カスタムストリングコンバーティブル)を使うという動きになります。
そのため、カスタムデバッグストリングコンバーティブルが優先され、それがなければカスタムストリングコンバーティブル、それもなければ言語組み込みのテキスト表現という動きになります。
デバッグ用の文字列が欲しい場合は、53行目のようにするのが良いですし、表現用のインスタンス文字列が欲しい場合は47行目のようにします。この選び方が最適です。
デバッグ用の文字列を得たい場合にはカスタムデバッグストリングコンバーティブルに準拠しているかどうかが重要です。この依存関係があるため、デバッグ用の文字列が必要であれば53行目の方法、表現用のインスタンス文字列が必要であれば47行目の方法を選ぶのが良いですね。 型がどうしても限定的になってしまいます。この今コメントを解除した3つを優先的に使うと、型が固定ならどうでもいい話なんですが、ジェネリクスなどを使い始めたときに表現の幅が狭まってしまいます。やはり、優先的にはこれがお勧めだなという感じです。
さて、大体話したいことは話し終えたんですけれども、もう一つ面白いデバッグストリングが選ばれる場面があるので、紹介しておきたいと思います。変数としてオプショナルの、そうですね、ここはストリングでいいでしょう。インテジャー型のオプショナル型があったとします。例えば30を入れたときのテキスト出力が面白いんですよ。
ローバリューが30ですよって出てくるのです。面白くないですか?これは、「555」って出ないんですよね。注意するっていうのは大げさかもしれないですが、プレイグラウンドだからこういった表現が出るわけです。プリントするようなときも、また若干表現が違うかもしれませんが、結構ちゃんと出てくれます。
例えば、オプショナル型のテキスト表現のときには「オプショナルのローバリューは何ですよ」って表示されるんです。ここで要はインテジャー型のデバッグディスクリプションが使われているんですよね。これはデバッグプリントしても同じなんです。ダンプしても詳細な情報が出るので便利です。
ダンプだと列挙子が何なのか確認しやすくて、とても楽ですよね。デバッグプリントだけだと列挙子が何なのか、何がSome
なのかが分からないですが、ダンプを使うとちゃんと出てくるので便利です。オプショナルをプリントするとローバリューがバレバレで出てくるので、注意が必要です。
例えば、デバッグコントローラーや認証情報を扱う場合、デバッグプリントをすると内部に持っているトークンなどが表示されるようになっていて、エラーメッセージとしてプリント文で出してしまうと中身のトークンなどが見えてしまうことがあります。これを防ぐためには、ダンプを使うのが良いかもしれません。
また、これがディスクライブイングでも同様に注意が必要です。ストリングインターポレーションを使っていると警告が出るかもしれませんね。カスタムデバッグストリングは注意が必要で、ダンプで済ますと安心だと思います。
カスタムデバッグストリングコンバーティブルが動いているのか、オプショナルでカスタムストリングコンバーティブルに準拠させることができるのかなど検証したい部分もありましたが、それは次回か別の機会にしましょう。
今日の勉強会はこの辺で終わりにします。お疲れ様でした、ありがとうございました。