この記事の要点
- Swift 2.2 は Swift 3 へ向けた中間リリースで、足りない機能を補い、既存の構文を洗練し、不要なものを取り除くという Swift 3 全体の方針に沿った変更が入っています。すべて Swift evolution process を経て採択されたものです。
- 新しく追加された主な機能は、コンパイル時の Swift バージョンチェック(
#if swift(...))、コンパイル時にチェックされるセレクタ(#selector)、引数ラベルとしてのキーワード利用の拡大、タプル同士の比較、そして#file/#lineなどへ刷新されたデバッグ用識別子です。 - 一方で、C スタイルの
forループ、++/--演算子、varパラメータ、タプル splat 構文といった機能が deprecated になりました。deprecated とは「非推奨で、将来(多くは Swift 3)で完全に削除される」という意味で、いまは警告が出て、将来エラーになります。多くの変更には Xcode の Fix-it が用意されています。
追加された機能
コンパイル時の Swift バージョンチェック
ライブラリ作者は、利用者が使う Swift のバージョンが異なると複数バージョンへの対応を迫られます。Swift 2.2 では、コンパイラが対応する Swift 言語バージョンに応じて読み込むコードブロックを切り替える、新しいコンパイラディレクティブが導入されました。
#if swift(>=3.0)
print("Running Swift 3.0 or later")
#else
print("Running Swift 2.2 or earlier")
#endif
これは Swift 2 で導入された #available とは異なります。#available が実行時チェックなのに対し、こちらはコンパイル時のチェックです。バージョン条件を満たさないコードは事実上「見えない」状態になるため、満たされない側のブロックには文法的に正しくないテキストが書かれていてもコンパイルが通ります。
なお、この記事の時点では実用上の制約がありました。Swift 2.1 のコンパイラは #if swift(>=2.2) 自体を理解できずエラーになるためです。Swift 3.0 以降のすべてのバージョンで有用な機能になります。詳細は SE-0020(Swiftバージョンによる条件付きコンパイル) を参照してください。
コンパイル時にチェックされるセレクタ
Swift 2.1 では、セレクタを文字列で指定できました。そのため addNewFireflyReference を addNewFireflyRefernce のようにタイプミスしてもコンパイルが通り、実行時にメソッドが見つからずクラッシュする、という落とし穴がありました。
Swift 2.2 ではセレクタへの文字列利用が deprecated になり、代わりに #selector 構文が導入されました。#selector は指定したメソッドが実際に存在するかをコンパイル時にチェックし、存在しなければビルドエラーになります。
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .Add, target: self,
action: #selector(addNewFireflyRefernce))
}
func addNewFireflyReference() {
gratuitousReferences.append("Curse your sudden but inevitable betrayal!")
}
上のコードはメソッド名が一致しないため、ビルド時に “Use of unresolved identifier ‘addNewFireflyRefernce’” というエラーになり、バグの原因を事前に取り除けます。詳細は SE-0022(Objective-Cセレクタの公開) を参照してください。
引数ラベルとしてのキーワード利用の拡大
Swift には class や func、let、public のように特別な意味を持ち、識別子として使えないキーワードが数多くあります。これまでもキーワードを引数ラベルに使うことはできましたが、バッククォートで囲む必要がありました。
func visitCity(name: String, `in` state: String) {
print("I'm going to visit \(name) in \(state)")
}
visitCity("Nashville", `in`: "Tennessee")
Swift 2.2 からは、inout / var / let を除くすべてのキーワードを、バッククォートなしでそのまま引数ラベルに使えるようになりました。
func visitCity(name: String, in state: String) {
print("I'm going to visit \(name) in \(state)")
}
visitCity("Nashville", in: "Tennessee")
詳細は SE-0001(引数ラベルとしてのキーワード) を参照してください。
タプル同士の比較
Swift 2.2 では、2 つのタプルが等しいかを比較できるようになりました。各要素を対応する位置どうしで比較し、すべて一致すれば true になります。
let singer = (first: "Taylor", last: "Swift")
let alien = (first: "Justin", last: "Bieber")
if singer == alien {
print("They match!")
} else {
print("No match.")
}
比較できるのは要素数が 6 個までのタプルです。注意点として、比較では要素名は無視されるため、要素名が異なっていても値が一致すれば等しいとみなされます。
let singer = (first: "Taylor", last: "Swift")
let bird = (name: "Taylor", breed: "Swift")
// 要素名は無視され、値が一致するので等しいとみなされる
if singer == bird {
print("Match")
} else {
print("No match.")
}
詳細は SE-0015(タプルの比較演算子) を参照してください。
刷新されたデバッグ用識別子
Swift コンパイラは、デバッグに役立ついくつかのシンボルを自動的に提供します。従来は __FILE__ や __LINE__ のように大文字とアンダースコアの命名でした。Swift 2.2 ではこれらが deprecated になり、#file / #line / #column / #function に置き換えられました。これは「# はコンパイラによる置換ロジックの呼び出しを意味する」という規則を導入するものです。
func visitCity(name: String, in state: String) {
// old - deprecated!
print("This is on line \(__LINE__) of \(__FUNCTION__)")
// new - shiny!
print("This is on line \(#line) of \(#function)")
}
Xcode の Fix-it で更新できます。詳細は SE-0028(デバッグ識別子の刷新) を参照してください。
deprecated になった機能
deprecated とは、非推奨であり将来(多くは Swift 3)で完全に削除されることを意味します。現時点では警告、将来はエラーになります。以下の機能はいずれも Swift 2.2 で deprecated となりました。
タプル splat 構文
Swift 2.1 以前は、型と要素名が一致するタプルを使って関数のパラメータをまとめて埋めることができました。これは “tuple splat syntax” と呼ばれます。
func describePerson(name: String, age: Int) {
print("\(name) is \(age) years old")
}
let person = ("Malcolm Reynolds", age: 49)
describePerson(person)
自己文書的で読みやすいという Swift らしさに反するため、Swift 2.2 で deprecated になりました。詳細は SE-0029(暗黙的なタプル splatの削除) を参照してください。
C スタイルの for ループ
Swift には他に慣用的なループ構文があるにもかかわらず、C スタイルの for ループが残っていました。
for var i = 0; i < 10; i++ {
print(i)
}
これは Swift 2.2 で deprecated になり、Swift 3.0 で完全に削除されました。単純なケースは Xcode の Fix-it が範囲を使った形へ変換してくれます。
for i in 0 ..< 10 {
print(i)
}
ただし Fix-it の能力には限界があり、逆順や刻み幅のあるループは自分で書き換える必要があります。逆順は reverse()、刻み幅は stride(to:by:) を使います。
// 逆順。10...1 と書くとコンパイルは通るが実行時にクラッシュするので注意
for i in (1...10).reverse() {
print(i)
}
// 2 ずつ増やす
for i in 0.stride(to: 10, by: 2) {
print(i)
}
詳細は SE-0007(CスタイルforループのSwiftからの削除) を参照してください。
++ と --
++ と -- も、前置・後置のどちらも deprecated になりました。C スタイルの for ループも deprecated されたため、for var i = 0; i < 10; i++ には deprecation が 2 つ含まれることになります。
i++
i--
++i
--i
i = i++
代わりに i += 1 や i -= 1 を使います。Xcode の Fix-it が用意されていますが、i = i++ についてはそもそも意味が定かでないため、Fix-it はコンパイルエラーを返します。
この変更には単一の理由があるわけではなく、i += 1 とほとんど長さが変わらないこと、Swift 初学者には学習コストになること、前置か後置かで結果が変わり混乱を招くことなど、小さな理由が積み重なったものです。Proposal では「もし今これらがなかったとして、Swift 3 にわざわざ追加するか?」という基準を満たさない、と簡潔にまとめられています。詳細は SE-0004(前置・後置のインクリメント/デクリメント演算子の削除) を参照してください。
var パラメータ
Swift 2.2 より前は、関数内で変更したいパラメータを var で宣言できました。
func greet(var name: String) {
name = name.uppercaseString
print("Hello, \(name)!")
}
これは便利な近道でしたが、inout との違いがわかりにくいという問題がありました。var パラメータへの変更は関数内にとどまるのに対し、inout パラメータへの変更は元の値に直接反映されます。この混乱を避けるため、var パラメータは Swift 2.2 で deprecated になりました(Swift 3.0 で削除)。従来の挙動が必要なら、関数内で自分でコピーを作ります。
func greet(name: String) {
let uppercaseName = name.uppercaseString
print("Hello, \(uppercaseName)!")
}
詳細は SE-0003(関数パラメータからのvarの削除) を参照してください。
関連リンク
- Swift 2.2 リリース — Swift 2.2 の公式リリース告知。Linux 版のインストール方法や全変更へのリンクがあります。
- SE-0020(Swiftバージョンによる条件付きコンパイル)
- SE-0022(Objective-Cセレクタの公開)
- SE-0001(引数ラベルとしてのキーワード)
- SE-0015(タプルの比較演算子)
- SE-0028(デバッグ識別子の刷新)
- SE-0029(暗黙的なタプル splatの削除)
- SE-0007(CスタイルforループのSwiftからの削除)
- SE-0004(前置・後置のインクリメント/デクリメント演算子の削除)
- SE-0003(関数パラメータからのvarの削除)