https://youtu.be/aUWj-e6_gO8
今回は The Basics の 定数と変数
について、その宣言方法をさらに詳しく眺めていきます。Swift 言語に親しむほどにいつもだいたい同じスタイルでの記載になりがちな変数宣言ですけれど、幾らか柔軟な書き方があって、人によっても癖が違ったりするかもしれないところでもあるので、そんな辺りに着目しながらみんなと見ていけたらいいなと思ってます。どうぞよろしくお願いしますね。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #91
00:00 開始 00:35 JavaScriptCore 01:09 JavaScriptCore とは 02:12 Playground で JSExport を使いたいとき 10:09 JavaScriptCore は便利 12:15 定数と変数 12:33 今回の展望 13:39 単一行でまとめて宣言 15:01 宣言を単一行でまとめることはある? 15:46 単一行でまとめた宣言を改行する? 17:56 r, g, b で扱う場面 22:06 タプルを使った単一行の宣言 22:44 書式選びの価値観 24:59 リテラルの書式の選び方 25:50 C 言語の小数点数表記 26:35 登場場面による小数点数リテラル表記 29:24 C 言語での小数点数表記を確かめてみる 31:25 引数リストを改行する 34:05 複数ステートメントを単一業でまとめる方法 38:25 単一行での変数宣言で型注釈や型推論を使う ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #91
はい、じゃあ始めていきます。まず、今日は定数と変数の続きのところですが、前回の補足を少し話しておきたいと思います。補足とは言っても余談のような内容なので、人によってはあまり重要でないかもしれませんが、お伝えしておくといいかなと思います。
前回、JavaScriptCoreがうまく動かなかった原因がわかったので、軽く紹介しておこうと思います。これには、JavaScriptCoreの紹介も含まれますので、あまり馴染みのない方も、どんなものか知るきっかけにしてもらえたらいいかなと思います。
まず、Macには標準ライブラリがありますが、正確にはMac OS SDKにはJavaScriptCoreというフレームワークが含まれており、JavaScript仮想マシンを簡単に立ち上げられる仕組みが用意されています。ちなみに、これはMac OSだけでなく、iOS、WatchOS、Apple TVでも使えると思います。要はSafariのJavaScriptエンジンをユーザーが自由に使えるので、JavaScript仮想マシン、つまりJavaScript実行エンジンを独自のアプリに組み込むのが非常に容易になっています。
これが前回うまく動かなかった部分ですが、TwistのクラスをJavaScript側にエクスポートして使うという機能がプレイグラウンドでうまく動かなかったという話をしました。原因がわかったのでインターフェースを規定してJavaScript側にエクスポートする方法を紹介します。
プレイグラウンドのトップレベルでインターフェースの規定をしようとすると、うまくいかないことがわかりました。ソースコードのファイルを作って、そこでインターフェースを規定しないといけないという制約がありました。具体的には、JavaScriptCoreでTwistのインスタンスをエクスポートする場合に、JSExportプロトコルに準拠したプロトコルを作る必要があります。
例えば、@objc protocol CustomProtocol: JSExport { func action() -> Int }
というプロトコルを作成し、その中にファンクションを定義します。ここでは、例えばreturn 7
のような実装をします。そして、このプロトコルをSwiftのクラスに実装します。このクラスはNSオブジェクトを継承している必要があります。
@objc
public protocol CustomProtocol: JSExport {
func action() -> Int
}
@objc
public class CustomClass: NSObject, CustomProtocol {
public func action() -> Int {
return 7
}
}
このようにして、JavaScript側にエクスポートされるインターフェースを規定します。クラスは必須で、Objective-Cランタイムを活用するためNSオブジェクトを継承し、プロトコルがObjective-Cである必要があります。
このやり方で、プレイグラウンドでもJavaScriptCoreをうまく活用できるようになりますので、実際のアプリを作るときにはこのような制約を気にせずコードを書くことができます。
ということで、前回のJavaScriptCoreがうまく動かなかった原因とその解決方法を紹介しました。それでは、本題に戻って定数と変数の勉強を続けていきましょう。 多分こうでしょう、それでこれでレッドバリューとして、スイフトバリューとしてインターフェースを作ります。イニシャライズとして、例えば init
こうね、こういうふうに進めていきます。パブリックイニット public init
オーバーライドとかオーバーライドだから、別に書かなくていいですね。それでイニシャライズします。
Swiftのインスタンスができたら、これでJavaScriptのコンテキストを作成して、これだけでこの4行目だけで仮想マシンが準備できます。それでコンテキストに対して、あとは前回書いたのと同じように、setObject
でキーサブスクリプトでこのSwiftのインスタンスを渡してあげます。このときにJavaScript側では何という名前の変数名で受け取るか、たとえば swiftValue
としてあげます。それでNSStringで渡さないといけないという微妙な設計になっているので、NSStringで渡してこうしてあげます。すると evaluateScript
で、ここでJavaScriptとして swiftValue
のアクションが呼べて、これで結果が返ってきます。ほら、7って出てきますでしょう。こういうふうにちゃんと動作します。ただし、これはあくまでもプレイグラウンドで試す場合です。
これが多分、この型の定義自体をファイルに、要はモジュールとして作成しておかないで、プレイグラウンドのトップに持ってくると、これで7が表示されなくなります。あれ? ここでも表示されるんですね。じゃあ JSExport
だけでいいのかもしれません。これをたとえばプレイグラウンドのトップに持ってくると、さっきの7が動かなくなります。プレイグラウンドが動かなくなった理由は何だろう。まあいいや、そのうち動くでしょう。プレイグラウンドは動くでしょう。
なるほど、そうすると20秒目がちゃんと動かないことを確認しないといけません。そうすると、という言葉はちょっと語弊があるけれど、仮に20秒目が前回みたいに undefined
になったとすると、プロトコルの規定だけは別モジュール、要はソースの方に書いてあげないといけないっていう感じのようです。ちょっとしたどうでもいい補足でしたが、こういったところが前回ふっかかっていたので、一応共有しておきますね。
このJavaScriptCore、この勉強会でも何回か取り上げていますが、とても面白いです。SwiftのインスタンスをそのままJavaScriptに渡して、実行できます。これがとても応用性が高くて、たとえば JSExport
を継承したプロトコル、アズフレームや何でもよいですが、JSExport
をしてあげて、ここでフレームを CGRect
とか NSRect
とか何でもよいです。こういうふうにしてあげて、NSRect
とかでコンパイルして、インポートが足りないのでエラーが出るかと思いますが、CoreGraphics
が入っているからか…。まあいいや。これでエクステンションとして UIView
とかにこういうのを当ててあげて、それでJavaScriptに UIView
のインスタンスをそのまま渡してあげると、JavaScript側で自由にフレームをいじれるようになります。こういったこともできるので、応用するととても面白いです。ネイティブなUIとかをJavaScript側で編集するカスタムスクリプト環境を提供できたりします。とても魅力的な機能です。自分の中で。
では、話を戻していきましょう。改めて、定数と変数です。前回、名前の付け方や関連付け方、let
と var
で定義する方法などをお話しましたが、今日はもう少し変数や定数の宣言について、どのように書いていけるのかを見ていきたいと思います。 この辺りって、慣れてしまえばさらさらっと普通に書けるようになるところだと思うんですが、人によっては癖があったり、「こう書くものだ」と思い込んで、それ以上の発想をする機会がないこともあるかと思います。せっかくなので、もし自分がよくする書き方などがあれば教えてもらえると、「なるほど、そういう書き方もあるんだ」と視野が広がるかなと期待しています。
まず、変数や定数をまとめて宣言することができるというお話がありました。具体的に Playgrounds で書くなら、例えば r
と g
と b
という三つの変数・定数を定義しようという時に、r = 0.0
、g = 1.0
、b = 0.5
みたいな書き方をしますが、これをまとめて定義できるという話です。以下のようにまとめて書くこともできます。
let (r, g, b) = (0.0, 1.0, 0.5)
こういったことができます。普通にする書き方かどうかですが、私は昔使ってみたことはあります。Lintでこれを禁止するルールがあったような気もします。例えば、autocollect
でバラバラにされることがあるので、好みとも言えます。autocollect
すると、let g = 1.0
みたいに行ごとに修正されることもあります。
レビューする時に指摘はしないかもしれませんが、「他の部分と合わせましょう」と言うかもしれません。RGBが一つのまとまりとして扱われる場合、グループとしてまとまりが見えるので、意味的にはそのまま受け取れるかもしれません。他に例えば size
とか他の属性が付いてくると、RGBが一つのまとまりで、size
が一つのまとまりで、という感じになることもあります。
他にも、例えば CGFloat
データを扱う際に、画像のピクセルデータを個別のRGBデータに書き込むときには、それぞれのRGB変数に対して個別に書き込む必要があります。Core Graphicsのメソッドで、ピクセルからRGBデータを取得する場合、まずRGBごとの変数を0で作っておいて、その変数にそれぞれのデータを取り出して入れる形です。
関数名は忘れましたが、以前はそういった方法を使っていました。CGColor
を使う場合も同様に、個別のRGBにアクセスするための関数があります。例えば、UIカラーにも同様に getRGB
メソッドがあります。 UIカラーでレッド、グリーン、ブルーを取得するには、フロートのポインターを渡す方法があります。この方法ですが、変数を使用してRGBを扱わなければならないので、ストラクトでは対応できません。そのため、シンプルな方法を選ぶことが有益です。バラバラに定義する方法もありますが、今回はこのような書き方に注目してみましょう。
これが適切かどうかという問題について話を戻します。確かに一つのまとまりとして感じられる書き方ですが、見慣れるまで時間がかかるかもしれません。ただ、慣れると利便性が高いですね。また、別の書き方も可能です。
たとえば、6から8行目のコードが混在しているとします。これがLintersに引っかかるかどうか、疑問に思うかもしれませんね。Lintersにはフォーマッター的な要素とリント的な要素がありますが、基本的には全体のルールに合わせて同じフォーマットを使用することが重要です。フォーマッター寄りのルールですね。
さて、具体的な書き方に話を戻します。6行目と7行目のコードは次のような書き方です:
var doubleValue = 0.0
var anotherDouble: Double = 0.0
6行目のような書き方は、型推論を大事にする場合に使います。一方で、7行目のように型を明示する場合もあります。プロパティの定義などでは、7行目の書き方をよく使います。
また、数値リテラルを関数に渡す際にどのように書くかについても議論がありました。14行目と15行目のコードは次のようなケースです:
let value = 14.0
let anotherValue: Double = 14
14行目の書き方は自然ですが、人によっては15行目のように書くこともあります。非プログラミング言語から学んだ人は、型推論のない書き方の方が自然に感じるかもしれません。
フロート型の値を省略できるかについても話題になりました。たとえば、「0.0」と「.0」のように書き方に違いがありますが、最終的には統一された書き方を採用することが重要です。
このように、コードの書き方については一貫性を持つことが重要です。特に、プログラム全体の可読性や保守性を考えると、フォーマッターによる一致したルールを守ることが大切ですね。 確かにね、この勉強会でも「アノテーション」の話を結構していましたね。その時、やっぱり最初のうちはこの書き方が通じなかったですもんね。今は自然に受け取ってもらえてる気がしますが、これはキャストなのか型変換なのかとかね、そういった話に発展していきました。単純な型のアノテーション的なリテラルだと、そういった風に理解されませんので、やっぱりあんまり書かない方がいいのかもしれないですね。
一応、変数の宣言として6行目も7行目も8行目も基本的に書き方としてはアリなはずなんですよね。あと「10S」とか書きたくなる、そんな気がしますね、確かに。うん、なるほど。「10S」と書いてObjective-Cにしてみるとどうなのか検討しましたが、適切に探した結果どうしても見つからなくて、「ダブルバリューイコール10S」でビルドしたらビルドが失敗しました。ここはインターフェイスなんですけど、インプリメンテーションがどこか行っちゃった。でも、なんとか書けそうな気がするので、最終的にコンパイルが通った感じですね。「フロートじゃなかったかな?」「W」に変換されたかも。どうしても気になりますけど、今回は「W」は通ったので、まあいいかと。
そういえば、この1行目から3行目の書き方についてですが、個人的には「いいな」とか「微妙だな」とか意見が分かれるかもしれないですね。関数名にパラメーターを渡す時も、長いときは今でも改行することがあります。どうでしょうかね。Objective-C の頃って関数名が長くて、改行したことありませんか?改行のタイミングとしては、NSから括弧の後に改行するとかもありましたね。そうすると、型名や関数名が長いときに右にどんどん広がるので、それを整えるのに改行を使うのが一般的でしたね。
開発チームでは、改行するなら同じルールで改行しましょう、1行で揃えた方が良いですよ、といったルールを決めることが多いですよね。私もこの書き方、つまり丸括弧の後に改行するっていうのは好みなんですけど、見やすいですね。これに関しては慣れの問題もあるかもしれませんが、見やすいと思います。僕もこれに慣れているので、見やすいと感じることがあります。見た目の問題ではなく、信頼性の問題かもしれないですね。グルーピングで考えると確かに有用だなと。この書き方も、ついつい無意識にしないことが多いですが、意識して取り入れることで意外と見やすくなることもあり得ます。
他にも、例えば具体例は出せませんが、変数の定義とは全然違う書き方でも、見直してみると悪くないなと思えることがあります。たまには色々な書き方を見て学ぶのも良いと思いますよ。
そして、もう一つの書き方として、Swiftで複数行を一つにまとめる方法がありますね。例えば、次のような書き方です:
r3 = 0.0; x1 = 1.0
今のところ、自分はこの書き方はあまり好みではないですが、こういう書き方もできますね。Swiftでセミコロンを使っている人は少ないかもしれないです。 自分はセミコロンは使わないですね。ただ、セミコロンも歴史とした言語の構文ですからね。一行でどうしても書きたいときに使うことはあるかもしれませんが、やらないほうが良いと思います。やらないほうが良い理由は何ですかね?言語の構造的な理由もありますが、他の人がコードを書くときにギョッとするような記述は避けるべきだと思います。一行にするメリットがそれほど大きくないので、それならば誰が見ても分かる二行にするほうを優先しますね。
個人的にセミコロンを使うシーンとして、コンピュテッドプロパティのセッターを書くときに、アサートをセットする際に使うことがあります。例えば、セットのほうでミューテーブルな値をアサートしてからセットする場合ですね。ただ、それも必要なければ二行にしたほうが良いと思いますね。
また、デバッグログを出すためにセミコロンを使うこともありますね。例えば、デバッグログを出して、その後にもう一つのログを出すときなどです。これは主に補助的な要素として使う場合が多いですね。
コメントでいただいた意見としては、デバッガーでコマンドを打つときにセミコロンを使うことがあるというものがあります。確かに、LLDBが停止しているときにワンライナーで何かを書かなければいけない場面では便利かもしれません。
他にも、Swiftのレッド文(let
)についても様々な書き方ができます。例えば、let g: Int, r: Double
のように型を別々に定義することができたりします。このように、型推論を使った複雑な書き方ができるのも特徴です。
しかし、これがどれだけ有用かどうかは場面や個人のスタイルによるところがありますね。とりあえず、こうした書き方ができるという情報は収穫として押さえておきましょう。
さて、これで時間になってしまいました。特に他に言いたいことがある人はいますか?30秒で終わりそうなことを30分も喋るのは貴重な体験だと思います。こういったことを長く考える機会はあまりないので、いい機会です。
この話を生かすかどうかは聞いてくれている人次第です。次回も引き続き、コンスタントとバリアブルスの話を続けていこうと思います。次回は月曜日がお休みなので、次は水曜日にお会いしましょう。それではお疲れ様でした。ありがとうございました。