今回は気に入って眺めていっている技術ブログ「その Swift コード、こう書き換えてみないか」の中から 名前空間
と enum
まわりのお話を眺める回になりそうです。かつてときおり話題にのぼる、個人的に興味深い話ですけれど、今のみんなの視点的にこの周りはどういう見解になっているのかとても気になるところですので、ぜひぜひ参加していろいろ聞かせてくださいね。よろしくお願いします。
そしてそんな本日は、愛知県名古屋市 からの配信になります。席にはまだ余裕があるので、お近くで今からでも間に合う人がいらしたらお越しくださいね。
——————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #264
00:00 開始 00:28 名前空間としての列挙型 01:21 列挙子を含まない列挙型 02:07 不必要なインスタンス生成を防ぐ 03:53 名前空間として列挙型を使う 07:12 型を名前空間として使う 11:23 名前空間の入れ子 12:46 名前空間に責務が伴う 13:58 名前空間としてだけの列挙型の是非 14:56 定数をグルーピングして揃えるときに利用するのもあり 15:59 グルーピングされた帯域変数 17:11 名前空間を活かしたコード補完 19:11 名前空間としての利用の是非は分かれるところ 19:58 言語仕様としても列挙子のない列挙型を容認 21:19 Never 型 24:23 Swift がそういうのなら、そうなのだろう 25:38 インスタンスが絶対に存在しないことを約束できる 29:40 シングルトンを使うべきか、静的メンバーを使うべきか 33:56 ライフサイクルの観点で見たシングルトン 35:55 クロージングと次回の展望 ———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #264
では、始めていきますね。今日も引き続き、田中陽佳さんのブログ「Swiftコードを書き換えてみないか」という提案のブログを取り上げます。これが個人的には非常に気に入っていて、せっかくだからじっくりと見ていきたいと思います。今日はまだ半分くらいしか見ていないので、この続きですね。
今回取り上げるのは「名前空間に良い名前を使う」というテーマです。これを見て最初はびっくりするかもしれませんが、私も最初は拒否感が強かったです。しかし、段々と解釈が変わってきて、「まあまあ、そう言われればそうかな」という認識に変わってきました。
田中陽佳さんの提案の一つに KCS VM
というアプリの話があります。これは以前話題になったものです。私もこのアプローチには大賛成ですね。インターフェースのメソッドを持たないサービスクラスには、インナークラスに変えるという方法が取れるんですね。
例えば、メモリー関係では、プライベートイニットを使わないことで、意味のないインターフェースを避けることができるんです。その点では、再度の修正が不要になります。また、著名なエンジニアもこの点についてコメントしているので、あとでその意見も紹介します。
まずは、「名前空間とは何か」を補足しておきましょう。Swiftが登場したときに、その特徴の一つに名前空間があると発表されました。オブジェクティブCには名前空間がなく、具体例としては CFString
や CFStringEncoding
など、全ての名前を頭からつける必要がありました。それに対して、C++のような言語ではネームスペースキーワードがありました。
Swiftでは特定の名前空間を使うためのキーワードがない代わりに、型そのものを名前空間として利用します。例えば、TwitterのAPIを扱うときには Twitter
という構造体を作り、その中に Message
や UserID
などのプロパティを持たせることができます。そして、この名前空間に所属することで、Twitterに特化した機能を持つようになります。
具体的には、Twitterの構造体に独自のイメージ型を追加し、UIイメージなどの外部の型を使うことも可能です。しかし、名前空間の中にあることを意識し、その範囲内で一貫性を持たせることが大切です。
以上が名前空間に関する基本的な説明です。このように名前空間を活用することで、コードの一貫性や保守性が向上するわけです。 このように、プログラムの中で名前空間を活用することで、ヘッダーのイメージを明確化することができます。例えば、headImage
と書くと、Twitterで使用されるイメージが名前空間の中で明確でも、それがUIイメージであるとしか分からないということがあります。ですが、Twitter用の画像であることは伝えられません。
名前空間を使うことで、このような曖昧さを解消できます。名前空間は意味を持っていて、その意味がそのまま名前空間として受け取られるため、漠然とした名前空間を作るわけにはいかないのです。特にTwitterのようなケースでは、その必要性が顕著です。
一方、C++の場合、ネームスペースを利用することで、比較的簡単に名前空間を作成することが可能です。名前さえ付ければ良いため、責務や役割をあまり深く考えずに名前空間を作ることができます。必要なら、ネームスペースの中にさらにネームスペースを入れることも可能です。C++では、このような柔軟な名前空間の使い方が特徴です。
Swiftの場合は、型を使用し名前空間を作成する必要があります。例えば、struct
やextension
を使って名前空間を作る方法があります。この方法では型を利用することでセキュリティや安定性が確保されます。列挙型(enum
)も名前空間として使用できますが、ケースを置かなければインスタンス化できないため、セキュリティを保つことができます。
ただし、列挙型は本来ケースを定義するためのものであり、それを名前空間として使うことには異論もあります。列挙型は、型として特定の値を取るという性格を持っているため、その本来の使い方から外れることになります。Swiftでは、列挙型を名前空間として利用することが推奨されることは少ないです。
コーディングの中で、グローバルに定義されるスタティックなメンバを名前空間によって整理することが賢明です。これにより、データが明確に分類され、アクセスも容易になります。また、名前空間があることで、データの意味や役割が明確にされるため、プログラムの可読性が向上します。ストラクトやクラスを使って名前空間を作成する場合も、これを通じてデータを整理することができます。
このような方法を活用することで、プログラムの保守性や拡張性が向上します。Swiftで名前空間を上手く利用することで、効果的なコード記述が可能になります。 例えば、ストラクト Color
とかですね。これで static なプロパティ red
や white
を定義するようなものです。UIキットや SwiftUI でも一般的で、こうすることによって Color.white
という書き方ができるわけです。さらに、例えば関数で setBackgroundColor
のようなものがあった場合、背景色
に Color.white
を設定することができます。名前空間を使用することのメリットは、これによってコードが整理され、明確に意図が伝わるようになります。
構造体を使用する場合でも同様で、物理的なデータを持つ必要がない場面で static なのは非常に有効です。ただ、名前空間を作る際には、enum を使おうという提案もあります。タイトルのところでキャッチーな感じになっていますが、要は定数がない enum を純粋な名前空間として使うというアイディアです。
このような提案は、名前空間として構造体を使わないほうが良いというわけではありませんが、enum を使うことによってより簡潔に目的を達成できるかもしれないという視点です。enum に static let
を置くだけなら特に問題はありませんが、定数がないとどうなのかという点が引っかかる部分です。
現代的なプログラミングの目的は、意味のある名前と構造を大切にすることです。その中で、enum に定数がないのは意図的な仕様であり、それが問題になる場面もあります。コンパイラーの役割は、プログラムが一貫性を持って動くことを確認することです。
それからもう一つ、Swift には Never
型が登場しました。この Never
は fatalError
のような場合によく使われ、戻り値としてボイドを返さない、つまり関数が呼び出されてから戻ってこないことを示します。
具体例として、enum Never
ですが、これにはケースがありません。そのため、インスタンス化することが不可能で、コンパイラーはその行以降の処理を続行できないと認識します。これにより、処理を継続させない仕組みが Swift に導入され、プログラムの構成がより論理的に組み立てられるようになります。
この Never
型の提案は、新しい言語仕様を追加することなくケースのない勉強型を使うことで実現できるという点で非常に合理的です。 なので、それを聞いていくと、Swiftが言っていることは一見すると「そうでしょねー」としか言えなくなってくるんです。しかし、実際にSwiftが何でそう言ってくるのかというのは、結構プロデュース的にかなり狙われている点が多いんです。理解不能でもSwiftが言っているんだったら正しいんだろうな、という風に理解すると意外とそこから学べるものがいっぱいありますので、おすすめです。
特に他の言語をやっていてSwiftに入ってきたとき、「なんでSwiftがこんなことをやっているんだろう?こっちの言語の方がずっといいのに」と思ったときには、ちょっと認めてあげるのが大事です。そういった面でも、さっき話したように名前空間を型に取得し、型を名前空間として使わなければいけないという謎の仕様なんですけど、見ていくとその責務が明確になって名前空間の内容化が進み、素晴らしいんですよ。
話がどんどん進んでいきますが、ストラクトで使わなきゃいけないってありますね。ストラクトだとインスタンス化ができてしまうんです。「ああ、それは避けたいな」と思うかもしれません。最低でもプライベートイニシャライザーにした方がいいでしょう。例えば、static let instance = Singleton()
とすると、インスタンスが作られなくなりますけれども、インスタンスは存在します。それで、関数でfatalError
みたいなのを作ったときに、こうやっても返ってくることがあるよね。
そのため、すべてを列挙型(enum)にしてしまうのも手です。列挙型にすることでインスタンスを作らせないようにするのです。ここでまたシングルトンの話に戻りますが、シングルトンにおいてもpublic static let instance = Singleton()
のようにします。そうすると、イニシャライザーを外からアクセス不可にすることができて、インスタンスを一つに限定する約束ができます。
具体的には、例えばstatic let instance
に対して、let instance = Singleton()
のようにして、唯一のインスタンスを作成します。要するに、スタティックメンバーを使って唯一のインスタンスを保持します。
こういった話を聞いていると、「シングルトンを使うべきかスタティックメンバーを使うべきか」というのは面白い話題ですよね。皆がシングルトンを使うことが多いですが、唯一のインスタンスが必要なんだったら別にインスタンスを作らなくても良いという考え方もあります。
個人的にはスタティックメンバーを使う方が好みです。シングルトンだとインスタンスで普通にプロパティを操作していきますが、スタティックメンバーだとよりシンプルに感じます。この辺りも検討する価値があります。 列挙型(enum)や静的メンバーを使う場合、列挙型のほうが好まれることについて話しました。ファクトリーやシングルトンの方式がない場合、現在開発中のデータがない時にどうするかという問題があります。列挙型に静的メンバーを適用すべきかを検討しましたが、最終的には設計の中で唯一のインスタンスを使用するという形で対応するべきです。
シングルトンは、インスタンス化しなければならないものであり、その唯一性を保証するための仕組みです。このような場合、静的メンバーを使うことで強力にグローバルにアクセスできるというメリットがありますが、グローバル変数と比較すると違いが不明瞭になることがあります。Objective-Cの時代にはインスタンスのライフサイクルに重点が置かれていたため、シングルトンのデザインパターンが使われていました。
Swiftではシングルトンの実現方法としてインスタンスのライフサイクルを考慮する必要があります。静的パラメータはアプリケーションが終了するまで保持されますが、インスタンスとしてはその限りではありません。NSApplicationのインスタンス化等、静的メモリに置いておくべきかどうかの判断も必要です。
シングルトンと静的メンバーの違いについての理解が深まりましたが、それらの使い道については慎重に検討する必要があります。次回はこれらの概念を踏まえた具体的な実装について話し合う予定です。質疑応答の時間は限られているため、詳細は次回に持ち越しますが、興味深いテーマがたくさんあるので次回も楽しみにしています。
時間になりましたので、今日はこれで終了します。お疲れ様でした、ありがとうございました。