https://youtu.be/aF6KSg0w6Qs
今回は The Basics の 定数と変数
から、その宣言方法について眺めていきます。前回に話した価値観的なところも含めて改めて記憶と照らし合わせていく復習的な感じの回になりそうですけれど、どうぞよろしくお願いしますね。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #90
00:00 開始 00:58 前回のおさらい 05:30 定数宣言と変数宣言 08:24 宣言の読み方 11:11 宣言する必要のない定数 11:45 クロージャーの引数リスト 12:55 エラー処理の error 定数 16:07 for の変数は "省略可能" とは言えなそう 17:00 wllSet と didSet 20:15 Objective-C 名の明記 25:32 JavaScript での利用を意識した名称 32:39 プロトコルでの Objective-C 名の要求 35:51 Objective-C クラスの接頭辞 37:30 自動で宣言される定数のおさらい 39:07 定数と変数の使い分け方 41:25 タプルの要素とキーパス 50:27 次回の展望 ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #90
では始めていきましょう。今日は定数と変数についてお話しします。これは前回の続きとなりますが、まずは概要を簡単におさらいしながら進めていきます。
前回の内容を復習しながら見ていきますと、名前と特定の値を関連づけるものについて話しました。昔の「変数とは代入するもの」という観念とは少し違った話です。定数と変数の扱われ方が少しずつ変わってきているようです。特に定数については、使い回しをしないことで具体的な名前を決められるようになったという話をしました。
これに関連して、定数とは値をセットしたら変更できないものであり、その値そのものを表現します。変数は値をセットした後に書き換えが可能であるため、時間に応じて値が変わってくる可能性があります。つまり、変数は状態を表現するものといえます。この話を踏まえると、定数は時間から解放されたスナップショットのような存在になり得るのです。
次にスライドに沿って具体的な話を進めていきますが、まず定数や変数は使用前に宣言が必要であることを確認します。Swiftでは、定数は let
で宣言し、変数は var
で宣言します。var
は変数(variable)の略であると考えられます。let
の由来については詳細は分かりませんが、そのものの命令形かもしれません。
他のプログラミング言語の例を挙げると、いきなり変数を使用できる言語もあります。JavaScriptやPerlは昔はそうでしたし、今もそうです。例えば、JavaScriptでは var
宣言なしに変数を使うという文化もありました(ただし、現在は推奨されません)。一方、C言語やPascalのような言語では変数の宣言が必須でした。
Swiftは宣言が必要な言語に分類されます。C言語、C++、Java、Objective-Cなども宣言が必要な言語ですが、Swiftもその一つです。 例として、以下のコードを見てみましょう。マキシマムナンバーオブログインアテンプスを10に設定する形のコードになっています。
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempts = 0
まず、let maximumNumberOfLoginAttempts = 10
では、新たに maximumNumberOfLoginAttempts
という名前の定数を宣言し、それに値10を割り当てています。一方、var currentLoginAttempts = 0
は、新たに currentLoginAttempts
という名前の変数を宣言し、それに初期値ゼロを割り当てています。
ここで異なるのは「値10を与える」と「初期値ゼロを与える」という部分です。定数 let
は値が書き換えられないため、初期値として10を割り当て、その値は変化しません。だから「値を与える」という表現が適切です。それに対して、var
を使った変数は与えた値が後に変わる可能性があるため、それはあくまでも「初期値ゼロを与える」ということになります。将来的には値が変わる可能性があるということを意識した読み方です。
実際に変数を使うときには、値が変化する可能性があるという前提のもとで var
を使います。これらはプログラムを書く上で基本的重要な点です。
また、Swift ではいくつか宣言しなくても使える変数があります。たとえばクロージャー内で使う $0
や、catch
ブロック内の error
が該当します。以下の例を見てみましょう。
let closure: (Int) -> Int = { $0 + $0 }
この場合、$0
はクロージャーが受け取る最初の引数を意味します。そして、エラーハンドリングの例では次のように書きます。
do {
try someFunction()
} catch {
print("Error: \\(error)")
}
ここで error
も特に宣言せずに使えます。このように、Swift には便利な省略記法が用意されており、コードを簡潔に書くことが可能です。
キャッチブロック内でエラー変数を使わない場合でも、特にエラーにはなりません。次のコードも同様に有効です。
do {
try someFunction()
} catch {
// エラーを無視する
}
こうして、Swift では柔軟かつ便利な構文が多くありますが、その一つ一つを使う際には、プログラムの意図をしっかり理解して記述することが重要です。 これ便利ですが、時々知らない人もいますよね。確かに先ほど言った通り「何とでもなるから」というところもあるし、このあたりはちゃんと言語仕様を気にしていないと気づかないところでもあります。まあ、知ってても知らなくてもどっちでもいい気がしますが、個人的には結構好みな仕様です。
他に何かありますか?
あと、際どいのが1つと完全に省略できるのが1つあります。2つ一緒にしてもいいかなと思いますが、いろいろありますね。まず、これは省略可能と言えるのかどうか際どいところですね。まあ、let
が省略できるだけといえばそれまでですが、これを略すとは言わないか、これくらいにならないと略したことにはならないかな。でも、このformでlet
を書かなくていいっていうのはすごく違和感を感じるんですけれど、まあ、当たり前だからですかね。煩わしくないから慣れちゃえば楽かな。そうですね、まあいいか。
で、あとは変数があって、それにdidSet
があったとき、このときにdidSet
には現在の値(value
)のほかに、以前に入っていたoldValue
という変数が使えるんですよね。これも宣言しなくて使える変数です。これもさっきのエラーとは似ていないか、似ていないですね。ここで例えばプレフィクスとかを付けてあげると、oldValue
という変数がなくなって代わりにプレフィクスになりますよ、といったふうに宣言することもできますし、宣言しないでoldValue
を使うこともできます。こういう特徴を持っていたりします。
また、同様にwillSet
があり、このwillSet
には以前の値、以前というか書き換える前の値がvalue
ですね。willSet
なので、それに対してこれから書き換えるよっていう値がnewValue
に入っている。これも宣言しなくても使えるやつですね。で、これも全く同様で、括弧で名前付けてあげると、これでnewValue
が変数から存在しなくなって代わりにnext
っていうのが使えるようになると。こういった自動的に使える変数があって、このリネームできるというのが面白いですよね。
自分もこれ知ってから気に入って使っているんですけれど、名前付けないとwillSet
とかでnewValue
になるじゃないですか。違う、oldValue
になるじゃないですかね。oldValue
になる。このときはいいんですよ、value
に対してoldValue
すごくよく分かるんですけれど、これがname
だったりしたときにwillSet
で以前の名前を取りたいっていうときにoldValue
分かることは分かりますが、なんか気持ち悪い感じが個人的にはするんですよね。でも、このリネームできることを知ってから、こうやってoldName
とかやると違和感が払拭されるわけですよ。old
じゃなくてもうちょっとprevious
とか何でもいいんですけれど、こういうふうに名前付けていくことができると、とても可読性が上がってくる。そんな感じで個人的にとても好みな仕様の一つですね。
自動的に宣言される変数という話とはまた違うんですけれど、もう一つ紹介したいのが、オブジェクティブCが好きな人はご存じだと思いますけれど、クラスってそれで例えばオブジェクトみたいな、もうちょっとちゃんとした名前にしようかな。そうだな、CustomViewController
みたいなね、そういったのを作ったときに、今回はNSObject
だけにしちゃいますね。で、こういうクラスを作ったときに、オブジェクティブCブリッジを通すときにこうやって@objc
を付けてオブジェクティブC互換にして、(ViewController
じゃなくていいですね、どうでもいいやっていうふうに)こうやってオブジェクティブCインタープレイラビリティを通してオブジェクティブC側でも使えるようにしましょう、みたいにしたとき。
このCustomViewController
っていうクラスは当然のようにオブジェクティブCでもSwiftでも使えるわけです。 ただ、Objective-Cのコードが完全にバック、要は裏方で動いているという状況なら、これで何の支障もないんですけれども、もしObjective-Cでもコードを書いていてSwiftでもコードを書いていて、両方を相互にコーディングしていく場合、Objective-Cの命名規則にも従いたいんですよね。
例えば、Objective-Cの命名規則は一般的に3文字のプレフィックスをつけることが推奨されています。自分はついつい2文字でやっているんですが、確か3文字でしたよね。3文字プレフィックスだったか2文字プレフィックスだったか、よく知っている方がいれば教えてください。
命名規則については、Cocoaの命名規則を見れば確認できると思います。とりあえず2文字のプレフィックスをつけるとします。朝文字で例を挙げると、こうやって付けるとするじゃないですか。こうすることでObjective-CのほうではObjective-Cらしい名前で使えるようになるんですけれども、Swiftのほうでは古い感じ、ダサい感じが出てしまいますね。そこで、かっこいい名前に収めたいときには、Swift名とObjective-C名を別々に設定することができる機能もあります。
この機能を利用すれば、Swiftでは CustomViewController
というSwiftらしい名前を使い、Objective-CではObjective-Cらしいクラス名を使うことができます。これは現代でもObjective-Cを書いていて、Swiftとブリッジしている人には非常におすすめの機能です。
同じように、最近Objective-Cを書かなくなって忘れてしまったのですが、例えば次のような関数を作ったとします。
- (void)actionWithValue:(int)value;
この場合も確かに @objc
で名前をつけられたと思います。
@objc func actionWith(value: Int) {
// 実行コード
}
しかし、この名前が意図した名前と違うメソッドの署名になってしまったときには、こういった方法で修正することができます。この機能が役立つ場面は、基本的にはObjective-Cのインターオペラビリティで自然な名前をつけるときです。
また、JavaScriptCoreを使ってJavaScript仮想マシンとAPIをやり取りするような場合も役立ちます。JavaScript側ではラベルなしの関数名で、Objective-C的な名前ではなく、例えば actionWithValue
のような書き方ではなく action
そのままの名前を使用することがあります。
このときに、JavaScriptCoreで @objc
を使って、Swiftの要求をする際の方法になります。JavaScriptCoreではObjective-Cの名前でメソッドを探しますので、そのときにObjective-Cのプロトコルを使って実装をすることになります。
具体的には、次のように記述します。
@protocol MyProtocol <NSObject>
@optional
- (void)action:(nullable id)value;
@end
このようにすると、JavaScriptからObjective-Cのメソッドを呼び出すことができ、Swift側でも自然に呼び出せるようになります。エクステンションなどを使って、適切なメソッド名を設定することができます。 はい、文章を整えてみました。以下のような内容になります。
インターフェイスにアクションを搭載してあげて、これでプリントとかして、ここまででコンパイルは通っているのかな。大丈夫かな。実行されているかどうかわからないな。
まず JSContext
ですが、これは NSObject
を継承しているので、このインスタンスを生成します。そしてコンテキストに対して objectForKeyedSubscript
だけ、これを取得するやつですね。setObject:forKeyedSubscript:
で x
を渡して、例えば x
という名前でコンテキストに対して evaluateScript
を実行します。例えば x
のアクションで10を設定して、これを表示しようかなと思っています。これで動くのかな。
ここが NSString
を渡しているので、このアクションを実行してみて、コンソールに何が出るか確認してみますね。あれ、undefined
が表示されました。何かアクションについて間違っているようです。アクションが適切に実行されていないみたいですね。
最近、JavaScriptCore をいじってなかったから操作にミスがあるかもしれません。ちょっとインターフェイスを省略するとどうなるでしょうか。何も出ないですね。何かエラーが出ました。アクションバリューにコンフリクトがあるみたいです。このエラーは何でしょうか。どうやら同じ名前のアクションがぶつかっていたようです。修正してもう一度試してみます。エラーが undefined
として出ているので、何か見落としているようです。
このカスタムビューコントローラーは NSObject
から継承していて、@objc
属性が付いていますが、動作していないようです。とりあえず先ほどから確認しているコンフリクトの部分を見直します。@objc
でアクションを求めて実装していますが、エラーが出てしまっています。
JavaScriptCore
を使う際、プロトコルに @objc
でアクションを求めるようにしていて、これを実装しないとエラーになるようです。ここが JavaScriptCore
についてです。とりあえず、プロトコルを正しく実装しないとエラーが出るみたいですね。
アクションウィズバリューの部分で、アクションが求められていますが、ここがアンダースコアになります。この修正を加えると、どうなるか見てみましょう。@objc
がついているけれど、違うタイプでエラーが出ていますね。何が間違っているのか、プロトコルの実装部分でしょうか。
プロトコルが正しく効いているようですが、実装のところで @objc
のメソッドが提供されていないと言われています。大変脱線してしまいましたが、もう少し見直してみます。
コメントも少し拾っておきましょう。2文字は OS 側で使うため、ユーザーは3文字にするべきだと聞いたことがあります。オブジェクティブCの命名規則についてですね。システムライブラリが2文字で、個人は3文字を使用するという暗黙のルールがあったようです。そういったルールが暗黙的に存在していましたが、実際には従わなかった人も多かったようです。
また、@objc
インターオペラビリティでは withValue
などが自動で追加されることを教えてもらいました。ちょうどいろんなエラーを見ていたところですね。
以上、文を整え、コメントを拾う形で修正しました。何か追加の指示があれば教えてください。 他に宣言しなくても使える変数って何かありますかね。思いつくのが少ないのですが、確か何かがあったような気もします。とりあえず、思い当たるのはなさそうなので、まとめると、クロージャーの暗黙パラメーターや、didSet
やwillSet
の中のnewValue
やoldValue
があります。この類であと1つくらいあったような気がするのですが、気のせいかもしれません。
そして、教えてもらったもので言うと、do-catch
ブロックのcatch
部分のエラー変数ですね。それくらいだったかと思います。出てきた例はそのくらいで、若干暗黙的に使える変数はありますが、基本的な原則としては使用前に宣言して使うという枠組みに従います。
はい、これで大丈夫でしょうか。いい感じの時間になってきたので、進めていきましょうか。次は定数と変数の使い分け方についてです。ここでは教科書的な紹介になりますが、最大ログイン試行回数
は変更されないため定数で宣言します。それに対して、現在のログイン試行回数
はログイン失敗するたびに増加させる必要があるため変数で宣言します。こういった変わらないものと変わるものの見分け方で、let
とvar
を使い分けます。これは普通の考え方ですね。
また、今日の勉強会の最初に話した、最大試行回数
は定数であるという点は変わりません。それに対して、ログイン試行回数
は現在の状態を表現するため変数であるという考え方もできます。定数というのはスナップショットのようなものと考えるほかに、マジックナンバー的な純粋な定数としても捉えることができます。場合によっては状態のスナップショットとしての役割を持つこともありますので、捉え方は違ってきますね。それは自分の価値観によるものなので、参考程度に受け取っていただければ良いです。
ここで話す内容はこれくらいですね。あれ、このスライドを話していたのかな?前のスライドに戻ってみましょう。いや、例題としてのコードが同じだけですね。コメントで教えていただいた、省略されている変数の話ですが、そういえばタプルの.0
や.1
もそうですね。確かにこれは省略されている変数として取れますね。
これを思い出しましたが、面白いですね。今までの暗黙の定数的なものは明示的に宣言されたら使えなくなるのに、タプルは.0や.1がそのまま使えるというのは面白い点です。好んで使う人はいないと思いますが、これが便利だと思うことは何でしょうか。単一的なタプルとして扱ったときに統一的な書き方ができる点では良いかもしれません。
それで、TPathを使ったときに汎用的に書くためとかに使いませんでしたか?TPathですか、文字列からその関数や値にアクセスしたいときにこういうのが残されていると便利と思うことがありましたね。ただ、確かにこの.0
でアクセスできるのは面白そうです。 これを型とかよく分からなくて、すごい抽象的に書いているライブラリがこういうのをチェックしつつ書いてて、なるほどなって思った記憶がありますが、ちょっと具体例が全然思い出せないですね。
確かに、タプルって例えば関数にタプルを入れるときって、タプルの表明というか変数名がつけてもつけなくても、つけたときにあってもなくても確か対応できるんじゃなかったっけ?
そうそう、名前なしのタプルはできますね。名前なしと名前ありを振り返ることができますね。
そうですね、多分それの整合性とかを取るために。
なるほど、名前なしのtop0
、top1
でも使うようにしてるんですかね。もしかすると。
なるほどね、確かにそういう可能性もありますね。
なるほどなるほど、確かに何かがあるかもしれないですね。こんな感じで、2行目も3行目もどっちもいけるってことは、どっちもいけてるっていう何となくの統一感がありますね。
あとキーパスで、ちょっとこの0
のキーパスが興味深いな。型を問わないキーパスって何型でしたっけ?
えーと、そんなのなかったかな。Any
キーパスはどういう型なんだ?
えーと、Any
キーパスはジェネリクスじゃない?
あんまりキーパスを派手に使ったことないからわかんないな。
Marshal
キーパスって確か型情報を取っちゃうのか。なるほど。
じゃあこのキーパスを取らなくてもタプルならできるのか。
えーと、let path
として・・・
あ、書いてあったわ。えーとこれでタプルのヒントを取る。タプルのキーパス。キーパス、だから円マークの0
をキーパス取れたんじゃない。で、あ、ダメか。
あ、大丈夫よね、きっとね。これで当たり前か。x
に対して、プレイグラウンド動いてなさそうだな。に対してパスを渡す。y
に対してパスを渡す。そうすると動くかな。x
からもy
からも、まあ普通に取れますよね。
えーと、でちょっと動かないからXcode落としますが、やろうとしてるのは、0
では取れてa
では取れない、をやろうとしてますが、それを確認するところですけど、これで動くのかな。
うーんと、動いた、取れたね。でこれで、ラベル付きのものにして、ここでa
がもちろん取れるよね。で、このときのキーパスは・・・あ、取れちゃった。どういうことだこれは。これは取れちゃっていいのか。あ、x
、x
ないよね。
んー、じゃあタイプエイリアスとは言わないですけど、ラベルのエイリアスみたいなものですかねこれ。だからx
として、y
として。でもちろん12行目はこれでa
なんてないから、これだとエラーですよね。でこれでx
にして、実行すると、ん?あ、こっちはエラーなんだ。面白いですね。
微妙に把握してるのね。ラベル名がないものに対しては自由にいけるんだけど、ラベル名がついちゃってるといけないんだ。x
にすると突然、ん?あ、違う?えーと、ここも合わせないといけないの?そうなの。そうなんだ。なるほどね。んー、大変ね。
だからさっき教えてもらったこの、このあくまでもこの変換なのね。ラベルなしとラベルありの変換、またはラベルありにラベルなしの変換。でもラベルが付いて違うと代入できないよっていう、タプルのねおなじみな動き。
あ、あれ入っちゃった?えーとこれはダメよね。これはダメよね。なんでこれ入るの。あ、そうなんだ、どういうことこれ。あれ、嘘だよね。これ知らなかった。どういうことだ。ちょっとタプルを勉強し直さないと。まあいいや、ちょっと面白いことがわかったところで、時間も過ぎているのでこれぐらいにしますかね。
何か言い残したことありますか。大丈夫ですかね。ちょっと面白いこと見つかりましたね。
はい、じゃあね。良さそうなので今日はこれぐらいにしておきましょうね。また次回どうしようかな。タプルの話はなんか進展があったらするとして、そうじゃなければ引き続き定数と変数のお話をしていこうかなと思います。
では、これで今日の勉強会終わりにしますね。お疲れ様でした。ありがとうございました。