https://youtu.be/KEbV4OcN2aU
今回も The Basics
の 論理値
について見ていきます。基本的機能はおおよそ見終えた感じですけれど、そういえば Bool
って「 true
や false
を入れるもの」というわかりやすさがあって、それ以上はあまり意識して眺めていなかった気がするので、せっかくなのでじっくりと Bool
の定義あたりを確認していこうと思います。よろしくお願いしますね。
もしそれが早く見終わってしまったら、次の項の タプル
についても見ていくことになるかもしれないです。
————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #129
00:00 開始 00:17 今回の展望と論理値の印象 01:30 Flutter に窺う const の役割 03:45 定数のおさらい 05:31 論理値についての前回のおさらい 06:15 論理値の利用場面のおさらい 06:47 余談 : Mmm と Eww 08:06 論理型における型安全 09:19 Bool 型 09:45 Bool 型に備わっている機能 11:06 インポートされた論理値の扱い 13:17 ブリッジとは別の、元環境相当の論理型 14:39 Bool の具体値 15:23 C 言語由来の bool 型 17:02 環境ごとに論理型が違う理由は? 18:42 ObjCBool と Bool のブリッジ 20:41 Objective-C の BOOL が使われる場面 22:43 Bool から ObjCBool への変換方法(訂正) 23:20 Bool は Sendable 23:49 既定イニシャライザーの存在意義は? 25:22 Bool の既定イニシャライザーは使用禁止 27:39 Bool.random と RandomNumberGenerator 29:11 inout と既定値とオーバーロード 31:08 真偽を反転させる 31:42 論理積と論理和 32:11 論理演算子と autoclosure 32:53 オートクロージャーの利用場面 37:20 真偽の状態を反転する 37:39 なぜ toggled が存在しないのか 38:25 Bool に備えられている既定の実装 39:03 文字列から論理値への変換 39:40 クロージング —————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #129
はい、では始めていきますね。今日も引き続き、真理値、ブーリアンについてお話ししようと思います。ブーリアンがテーマで、前回だいたいざっくりと見たところもあるんですが、普通だったら次に進んでもいいところです。でも、ブーリアン、要は true
や false
を条件式で使うということくらいしか知らないので、せっかくだからもう少し深掘りしてみたいと思います。勉強会なので、ゆっくり進めてみましょう。
そういえば、前回の話の中で真理値の演算、特に三項演算と論理演算という言葉を紹介しました。それぞれの違いについても後でお話しすると面白いかなと思います。
ブールに入る前に、ちょっと前にあった「フラッター入門会」という勉強会についても触れておきます。その会には途中から参加したのですが、面白い話が出ていました。今回の勉強会でも取り上げましたが、最近のスタティックストリングについて、将来的には @const
というコンパイルタイムに計算される図形が出てくるという話がありました。
ダート言語についても、そのときに初めて知ったのですが、ダートには const
というキーワードがあり、それがコンパイルタイムに値を確定するキーワードです。これが導入されることで、単純にコンパイルタイムに計算をしてランタイムのパフォーマンスを向上させるだけではなく、UIの再評価を必要としないため、UI評価のパフォーマンス向上にもつながるそうです。
本当に興味深いですね。ランタイム上のパフォーマンス向上というだけでなく、UI再評価の処理を最適化するという考え方もあります。ただ、疑問に思ったのは、コンパイル時に確定してランタイム時に変わる可能性があるものを定数(リードオンリー変数)で良いのではないかという点です。
コンパイル時に確定し、ランタイム時に状態変化を伴わないインスタンスを使いたいなら、 const
が役立つことが分かりますね。特にインスタンスの状態変化がない場合には非常に効果的です。例えば、リードオンリーの変数では中身が変わらない保証がないため、こういった特性を持つ const
キーワードは非常に有用だと感じました。 とりあえず他の言語の話を聞いたことで、Swiftが目指そうとしているところを既に他の言語ではやっているんだなと感じました。他の言語、特に最近の言語に触れることは、メインで使っている言語の知見を広げる手助けになるかもしれません。
さて、本題に戻ってBoolean型についてお話ししましょう。Booleanについて何度でも見ていきましょうか。前回のお話では、Bool
は型推論ができて、デフォルトで代入が可能ということでしたね。この辺りはすでに確認済みです。
Bool
に関しては主に型推論と、他の大抵の定数や変数を操作する際に使う型ということが書かれています。また、条件式ではBool
以外受け付けなかったという話もしました。それに関連して、他の言語、例えばC言語では、条件式での判定が柔軟で、値が真(True
に相当)か偽(False
に相当)かを判定してくれますが、Swiftでは厳密な判定をするという話もしました。これはうっかりミスを防ぐためのものですね。
ここから少し具体的に、Boolean型について見ていきましょう。具体的に、Playgroundで皆さんのBool
型に対する理解を深めるための話題を考えてみます。例えば、Bool
型には多くのメソッドが搭載されています。実際にはそんなに多くはなくても、いくつか重要なメソッドがありますので、それを見ていきましょう。
Bool.random()
このメソッドはランダムにTrue
かFalse
の値を生成します。他にも、Bool
型は単純な構造体ですが、さまざまなメソッドを持っていますので、それを調べてみると興味深いかもしれません。まず、Bool
型の定義やメソッドを見てみましょう。 まずは、Boolean型について説明がありました。Boolean型の変数を表現しているのですが、このあたりは基本的な内容ですね。具体的には、外部から書き込まれたBoolean型用の補足があったので、その部分を見ておきます。
SwiftのBool
型とObjective-CのBool
型について説明があります。Objective-CにおけるBoolean型は、自動的にSwiftのBool
型にブリッジされます。実際、これはその通りです。
Bool
型は関数やメソッド、プロパティに使われます。CやObjective-Cからインポートされたインターフェースを扱うときに、自動的にブリッジされてSwiftネイティブのBool
型として扱えるようになります。この過程について解説があり、ちょっと考えてみましょう。
例えば、Bool
を引数に取るメソッドを思い出すと、ファイルマネージャーなどを使う場合に出てきます。sendParentDirectoryPath
というAPIを呼ぶときに、その戻り値を見てpath
を取ったり、タイプオブアクセスをやったりする際にBool
型が使われます。Objective-CにおけるBoolean型は、0がfalse
で、それ以外はtrue
になります。
SwiftのBool
型では、Objective-CでのTrue/Falseの扱い方が異なりますが、ブリッジされることでSwiftネイティブのBoolean型に統一されます。それでも、Objective-CのBoolean型は存在し続けます。例えば、イニシャライザがBool
型を取る場合などですね。
さらに、Bool
型のサイズについても触れられています。Objective-CのBOOL
は1バイトですが、SwiftのBool
も基本的には1バイトとして実装されています。 Swiftのブール型について話を進めましょう。継ぎ接ぎで分かりにくくなっていた部分を整理しますね。
Swiftでのブール型は Bool
と呼ばれます。Bool
型の値は true
または false
のいずれかです。C言語やObjective-Cにも bool
型がありますが、Swiftの Bool
型とは異なります。それぞれの言語でのブール型のメモリレイアウトは以下のように異なります。
例えば、Swiftで Bool
型を UInt8
型に変換すると、true
は 1
、false
は 0
になります。以下のようにキャストが行われます。
let trueValue: Bool = true
let falseValue: Bool = false
let trueUInt: UInt8 = unsafeBitCast(trueValue, to: UInt8.self)
let falseUInt: UInt8 = unsafeBitCast(falseValue, to: UInt8.self)
print(trueUInt) // 出力: 1
print(falseUInt) // 出力: 0
Objective-Cにおける BOOL
型も同様に true
は 1
、false
は 0
として扱うことが一般的です。メモリレイアウトとしては、どちらも1バイトです。おそらくC言語の bool
型も同様に1バイトで、true
は 1
、false
は 0
です。
しかし、言語によってブール型の値が異なる場合があります。例えば、Visual Basicでは true
を -1
として扱うことがあります。このような違いがあるため、異なる言語間でブール型を扱う際には注意が必要です。
Objective-CとSwiftの相互運用性についても触れておくと、Objective-Cの BOOL
型とSwiftの Bool
型の間にはある程度ブリッジがありますが、完全に同じではありません。たとえば、Objective-Cで定義された BOOL
型の値をSwiftで操作する際には、一部のケースでキャストが必要になることがあります。具体的には、Objective-CのAPIをSwiftで呼び出す際、ブール型のキャストを追加して明示的に変換する必要があることがあります。
例として、Objective-CとSwift間のブリッジを考えてみます。Objective-CのBOOL値をSwiftのBool型にキャストするには次のようになります。
let objCBool: ObjCBool = true // Objective-CのBOOL
let swiftBool = Bool(objCBool) // SwiftのBoolに変換
このように、SwiftとObjective-C間では暗黙的なブール値の変換が行われますが、場合によっては明示的なキャストが必要となることを覚えておいてください。この違いのため、場合によっては正しい型変換を行うための特別な処理が求められます。
ブール型についての理解が深まったでしょうか。他にも気になるポイントがあれば教えてくださいね。 これね、あんまりやらないことかもしれませんが、APIでObjective-C
のBool
が出てくるのは稀ですので、いざというときだけでしょうね。ちなみにObjective-C
のブールが必要な場合ですが、例えばファイルマネージャーのfileExists(atPath:isDirectory:)
メソッドがあります。このメソッドは指定したパスが存在するかをチェックし、それが存在していてディレクトリであった場合には、ポインターで渡した変数にその情報が返されます。
このとき、ブリッジされていないObjective-C
のBOOL
のポインターを渡す必要があります。ですので、例えば以下のように定義します。
var isDirectory: ObjCBool = false
そしてその後に、
FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
このように書かないといけません。これがBool
を扱うためのコードですが、非常に微妙なコードになります。例として、あるディレクトリが存在する場合の処理を書くと、次のようになります。
if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) {
if isDirectory.boolValue {
// ディレクトリだったときの処理
}
}
このように、必要なときにはObjective-C
のBOOL
が出てくることがあります。普通にtrue
やfalse
を返すBool
はSendable
ですので、特に気にせずに並列処理で渡せます。それはスレッドセーフです。デフォルトイニシャライザを持っていて、false
を内包するBool
型をインスタンス化できます。
デフォルトイニシャライザがなぜ搭載されているかというと、特に理由がないように思えますが、これはインタープレラビリティを意識したものかもしれません。デフォルトイニシャライザを使用すると0
になります。同様に、モチレットもデフォルトイニシャライザが搭載されていて、空のモチレットになります。
この辺りについては、特にプロトコルで制約されているわけでもなく、内包されている値がデフォルトで0
か空であるかどうかは、設計次第ということなのでしょう。それでも、Bool
型にもデフォルトイニシャライザが存在しています。
デフォルトイニシャライザがあることで、学ぶべきことが増えてしまいますが、例えばstatic var true
やstatic var false
のリテラルがあるので、デフォルトイニシャライザがなくともやりくりは可能です。個人的にはそちらのほうが好ましいように感じます。 デフォルトイニシャライザーが用意されているので便利に使えるという話がありました。しかし「デフォルトイニシャライザーを直接呼ばないでくれ」という意図がどういうことなのかについて疑問が提起されました。プロトコルで適切に味付けしないと、デフォルトイニシャライザーを直接使うことが難しい場面があるという文脈です。
たとえば、Bool
やSomeStringConvertible
のようなプロトコルを使用することで、そのまま使えるようにはなるものの、プロトコルで特別な処理が定義されていない場合、デフォルトイニシャライザーの直接使用は避けるべきかもしれない、とのことです。これは怖がる必要はなく、「使わないほうがいいんだ」との受け止めがありました。また、誰かが説明してくれたらと思います。
次に、プール型を取るイニシャライザーの自然さについて話されました。これだけで十分なフィードバックが得られるとのことです。そしてランダムな数値を取得する方法についても触れられました。たとえば、RandomNumberGenerator
のインスタンスを使うと、そのジェネレーターに基づいたランダム数が得られるということです。このRandomNumberGenerator
をどうやって作るのか、そしてnext
メソッドで乱数を返す方法について説明がありました。
inout
パラメータやデフォルトパラメータに関する議論もありました。APIデザインガイドライン
に沿ってこれを試みることについての話題で、たとえば関数func something(value: inout Int = 0)
のような実装を行うことが非現実的であると認識しました。inout
パラメータにはデフォルト値を渡せないため、コンパイルエラーになるということです。これは安全性のために重要な仕様であり、無視することなくエラーとして処理するのが賢明だという認識です。
オーバーロードに関する話もありました。「なるほど」と納得して、次に進みました。
さらに演算子に関する話題が続きました。たとえば、プレフィクス演算子としての!
演算子がありますが、この演算子は真偽値を反転させる機能を持っています。また、その他の論理演算子として&&
や||
が定義されています。
最後に、オートクロージャーの機能について触れられました。オートクロージャーはパラメータを取らずにクロージャーを返す算出方法を受け取るインターフェースであり、これにより関数呼び出しの文脈でシンプルに使用できるという機能が説明されました。 関数で何かを実行するときに、オートクロージャーのような機能がないと問題が生じます。具体的には、条件によって異なる処理をする場合、例えば true
の場合には特定のクロージャーを実行し、そうでない場合には別のクロージャーを実行するといった場面です。
例えば、関数内で print(a)
を実行し、a
が true
だった場合に限り、b
も print
するような処理を考えてみましょう。このとき b
が関数で渡されるため、関数の実行時に b
を評価せずにクロージャーとして保持しておきます。条件が満たされたときに初めて b
のクロージャーを評価して実行します。
このように、クロージャーを用いることで、処理を延期し、必要なときにのみ評価することができます。これにより、パフォーマンスが向上します。オートクロージャーを用いると、手動でクロージャーを作成する手間を省き、簡潔なコードを書くことが可能になります。
次に、toggle
メソッドについて説明します。toggle
はミュータブルな関数で、変数が true
なら false
に、false
なら true
に切り替えます。このメソッドはSwiftの論理否定演算子(!
)と同等の機能を提供します。
続いて、エクステンションについてです。エクステンションを使うことで、既存の型に新しいメソッドやプロパティを追加できます。また、文字列からブール型への変換などもサポートされています。これは非常に便利で、例えば "true" という文字列が与えられた場合に true
のブール値に変換することができます。
これで一通りの説明は終わりました。今回の勉強会の内容を活かして、Swiftを使った開発に役立ててください。それでは、今日の勉強会を終わりにします。お疲れ様でした。ありがとうございました。