最初に、前回の配列宣言時の書き方の追加説明。
↓これね
var labels = [UILabel]()
配列の宣言は「色々と脱線」で [ ] (ブラケットペア)で囲んだ中に、複数の要素(あの時は文字列だったけど、今回はUILabelってことになる)を , (カンマ)で区切って並べることで作るって言ったので、上の記述を
[UILabel] ← UILabelクラスオブジェクトが1つ入った配列
[UILabel]() ← で、その後ろに()ってなにこれ?
って思った人もいるんじゃないかと思われ。
クラスオブジェクト?な人は「色々と脱線」を読みましょう。
で、実はこれもSwiftの略記法の一つで、正式なUILabelの配列宣言は
var labels = Array<UILabel>()
と書くんですよ。
これで、UILabel型の要素を持つ空の配列の作成になるんですよ。varなので変更可能ね。
Array
で、ここで現れたArray構造体こそが配列の正体なんですが、こいつは自分が取り扱う要素の型に合わせて自身の型が変化するという特性を持ってて、(Array+取り扱う要素の型)で一つの型となります。
int型のArrayとか、UILabel型のArrayとか、取り扱う要素によって色々な型のArrayができるわけですよ。
こういった構造体をジェネリック構造体というんですが、その特性ゆえに、作成時には型指定(今回のArrayなら配列要素の型)が必須で、ジェネリックじゃない構造体のように
Array()
なんて風には作れません。それで、後ろに続く < > (アングルブラケットペア)で囲んだ文字列で要素の型を表記することになってます。
例えば、Intの配列は
Array<Int>()
で、作成となるわけです。でもって、その略記法が
[Int]()
であり、もし要素の型がUILabelなら
[UILabel]()
となるわけですね。
ちなみに () を外した部分が、ジェネリックであるArrayの型で、引数定義なんかで型を書く時は、Array<Int>とか、[Int]といった風に書きます。
なので、ここで出てきたUILabelは、配列の要素としてUILabelクラスオブジェクトを登録してるんじゃなく、要素の型を示してるだけです。
ついでにいうと「色々と脱線」で説明した配列宣言の書き方
let script = [ "雨の日はだるい…\n今日は学校休んじゃおっかな〜", "誰だ" ]
は
var script = Array<String>()
script.append("雨の日はだるい…\n今日は学校休んじゃおっかな〜")
script.append("誰だ")
と書いたのとほぼ同じ(letでは要素の追加ができないのでvarになる点を除いて)意味になる。
[ ] (ブラケットペア)で囲んだ中に , (カンマ)で区切って要素を並べることで、配列宣言、要素追加までを一気にやっちゃうわけですな。
この時、要素の型は並べた要素から勝手に判断します。
そいうわけで
let script = ["太郎",1,2.0]
なーんて書くとコンパイラから「ごめん、何いってるか解らない」って注意されたりもします。文字列の配列なのか、Intの配列なのか、Doubleの配列なのかはっきりしろよと…
こんな時は
let script:[Any] = ["太郎",1,2.0]
と書いて、問題を解決する。
Anyというのは、型のワイルドカード。「何かの型」という反則的な型です。これで「欲しいのは色々な型の要素が入る配列じゃあ」という意味になる。
まあこれも略記で正式には
let script:Array<Any> = ["太郎",1,2.0]
ですな。これで
var script = Array<Any>()
script.append("太郎")
script.append(1)
script.append(2.0)
と書いたことになる。
こんな感じで便利なAnyなんだけど、可能なら避けましょう。
これ指定すると、配列の要素は文字列(String型)だったり、Int型、Double型だったりと一定の型じゃなくなるので、数値を取り出したつもりが、文字列でしたとか、混乱が目に見えてるのでな。
でも使わざるを得ない時もあるんですよ。
その時は配列の要素の型は全てAnyなので、そのままでは配列から取り出してもAny型としてしか使えません。
例えば先例のscriptの場合、インディックス0の要素はString型なんだけど
let text = script[0]
としてもtextはAny型のままです。
このままUILabelのtextプロパティに設定しようとしても型が合わないって怒られます。
そのためAny型からString型に変換する必要があるんだけど、「ifの話」で紹介したInt型からDouble型への変換みたいなことはできません。
Double(sign)
てやつね。こんな感じで
let text = String(script[0])
とやっても怒られます。あれは渡された値を使った、新しい変数、定数の作成という意味なので、String側にAny型の値から、新しいStringを作る機能がない限り使えないんですよ。で、実際、そんなものは無いと。
こういった場合、script[0]の型を、可能ならString型として扱ってくださいという意味で as? を使い
let text = script[0] as? String
と書いたりします。こうするとtextには、script[0]がString型として扱えるなら、その値が、そうでないなら無効な値という意味のnilが設定されます。
script[0]なら
let text = script[0] as? String ← textには"太郎"が設定される
script[1]なら
let text = script[1] as? String ← textにはnilが設定される
なので、script[1]の場合はIntを使って
let value = script[1] as? Int ← valueには1が設定される
ですね。あくまで型をどう扱うかの指定なんで as? Double なんてすると、valueにはnilが入ります。
let value = script[1] as? Double ← valueにはnilが設定される
script[1]の値をDouble型として使いたければ、一旦Intとして取り出して、その上でDouble()を使って変換します。
let value = script[1] as? Int
let dvalue = Double(value!) ← !をつける点に注目
Double()を使って変換する時、valueの後ろに ! (エクスクラメーション)を付けているのは、valueがInt型ではなくnilの指定が可能なInt?型だからです。
こういった型名の後ろに?がついた型をオプショナル型っていいます。各型をnil設定可能にしたものです。Int型の定数・変数にはnilは設定できません。
そのためnilの可能性のあるas? IntではInt型ではなくInt?型となります。
オプショナル型
で、このオプショナル型の変数・定数は、そのままでは使えず、使う時は「この変数・定数はnilでは無い事を保証します」という意思表示が必要なんですな。
それが後ろの!の意味。
これをオプショナル型をアンラップするって言います。
もし間違ってnilが設定されている変数や定数をアンラップすると、アプリは実行時エラーとなって終了するんで気をつけましょう。今回ならscript[0]を指定した時そうなります。
let value = script[0] as? Int ← nilが設定される
let dvalue = Double(value!) ← 実行時エラーとなってアプリ死亡
結構デンジャラスです。
そういったミスを防ぐためには事前にnilかどうかのチェックをするといいでしょう。
let value = script[0] as? Int
if value != nil {
let dvalue = Double(value!)
・・・ ←dvalueを使った処理
}
でもって、こういうオプショナル型をアンラップする局面は結構あるんで、もうちょと便利な記述法が用意されてます。
if let value = script[0] as? Int {
let dvalue = Double(value) ← valueはInt型なので ! はいらない
・・・ ←dvalueを使った処理
}
この場合、script[0] のas? Intの結果がnilでない場合だけ { } (ブレースペア)内が実行されることになり、valueもオプショナル型のInt?型ではなくInt型になります。
つまりscript[0]の時は何もおこらず、script[1]の時はdvalueについての処理が実行されるわけです。
Appleのサンプル見ると結構、この記述法に出くわすので覚えておきましょう。
あと
guard let value = script[0] as? Int else {
return
}
let dvalue = Double(value) ← valueはInt型なので ! はいらない
・・・ ←dvalueを使った処理
なんて記述もあります。
guard文
この場合、value = script[0] as? Int がnilなら else { } 内が実行される。
こいつはnilなら処理を切り上げたい場合に使われることが多いので、大概 else { } の中はreturnとなります。こっちも結構使われる。
色々なオプショナル型をif letでアンラップしていくと
if let a = … {
・・・ ← aを使った処理
if let b = … {
・・・ ← a,bを使った処理
if let c = … {
・・・ ← a,b,cを使った処理
}
}
}
てな感じになって、インデントが深くなって嫌だっていう要望から用意されました。
まあ , let で区切って
if let a = …, let b = …, let c = … {
・・・ ← a,b,cを使った処理
}
なんて書き方もあるんで、一気にアンラップできるなら、そこまで深くはならないけどね。
ということで配列の宣言の補足説明終わり。
あと、もう一つ補足説明。
addObserverのselector:引数に指定するメソッドは
func メソッド名(_ notification:Notification) {・・・}
にしないといけないって言いましたが、ありゃ不正確です。
引数なしのメソッドも指定できます。
func メソッド名() {・・・}
「引数なし、もしくは引数にNotification型を1つ受け取る」この、どちらかを指定できます。
調べ方は何度も言うけど「または私は如何にして心配するのを止めて…」で紹介したクイックヘルプね。
引数で受け取るNotificationには、指定したイベントが発生した際の詳細な情報が入ってるんで、そういった情報が欲しければ、引数ありのメソッドを使えばいい。
ちなみに親切本では、categoryDidChangeメソッド内で受け取ったNotificationを使って
if let category = notification.userInfo?[UIContentSizeCategoryNewValueKey] as? String { println(" ユーザーが選んだ分類は \(category) です。") }
とやってます。
何これ?ってなると思うんで、順を追って説明。
まずNotificationオブジェクトのuserInfoプロパティが何かから。
こいつはクイックヘルプで調べると
[AnyHashable : Any]?
となってます。?はオプショナル型って意味なのは先に説明した通り。じゃあ
[AnyHashable : Any]
は何か?
さっきの説明聞いた人は、配列?あれ、でも:(コロン)を挟んで2つ型を書いてる、何これ?
になると思うんだよね。で、これは連想配列と呼ばれるもので、要素へのアクセスに [ ] を使うのは配列と同じなんですが、要素を指定するのにインディックスではなく、任意の値(普通文字列を使う)を使うようになってます。任意のキーワードで要素を指定できるので連想配列。
配列:
連想配列:
連想型配列(Dictionary)
Appleではこいつを辞書と呼んでいて、Dictionaryというジェネリックな構造体で定義してます。ジェネリックというように要素指定子の型, 要素の型とも指定することになってて、正式な型表記は
Dictionary<要素指定子の型, 要素の型>
となります。で、これの省略形が
[要素指定子の型 : 要素の型]
となる。 , じゃなく : を挟む点に注意ね。
多分、宣言時の略系で書くときに、要素の区切りの , と紛らわしくなるから : を使ったのに合わせたんじゃないかと思ってる。辞書宣言の略系は
let script:[String:Any] = ["name":"太郎", "level":1, "life":2.0]
なんて風に書きます。scriptの型をわざわざ :[String:Any] と記述してる理由は先のArrayと同じ理由。
配列と違って0から順に入るわけじゃないので、こんな感じで要素指定子と要素のペアで記述する必要がある。要素指定子の"name"、"level"、"life"はプログラマが自由に決めていい。
letが使えない点を除いて、実質、次の記述の略系です。
var script = Dictionary<String,Any>() script["name"] = "太郎" script["level"] = 1 script["life"] = 2.0
というわけで、今回のuserInfoプロパティの
[AnyHashable : Any]
なら
Dictionary<AnyHashable , Any>
てことです。
Any型はさっき説明した通り。
AnyHashableは何かと言うと、特定の計算で整数にできる型ならなんでもいいという型。
この、要素指定子に特定の計算で整数にできる型、というのはDictionaryの条件でもあります。もうちょっと突っ込んだ言い方をすると「Hashableプロトコルを採用している型」です。そこらへんは各自で調査。
で、String型はその条件に適応するように定義されてるので、Dictionaryの要素指定子の型として指定できるわけで、先の例では
[String:Any]
としました。
とにかく、userInfoプロパティでは、[AnyHashable : Any]に?がついてるので、userInfoから要素を取り出す時はアンラップの必要があるんですよ。
でもって取り出す要素がみつからない場合はnilとなるし、要素が見つかっても、Anyなので、それが求める型として扱えなければやっぱりnilになる。そこらへんを素直に
if let userInfo = notification.userInfo {
if let category = userInfo[要素指定子] as? String {
・・・ ← categoryを使った処理
}
}
と書いてもいいけど、一気に
if let category = notification.userInfo?[要素指定子] as? String {
・・・ ← categoryを使った処理
}
と一気に書き下す書き方もできるんですな。
左から順にアンラップや型変換が行われ、成り立たなかった時点でfalseとなって { } 内は実行されません。アンラップチェーンといいます。
これが親切本のサンプルのcategoryDidChangeメソッドでやってることです。
何が起こるか興味がある人は、前回のサンプルで同じように書いて実験してみましょう。
てことで、ようやく本題のAuto Layoutに入ろうと思ったけど疲れたので次回!
追記:
iBook見ててApple発行のこんなの発見しました。
おおう。ちゃんと日本語です。無料です。Apple日本法人、頑張ってます。