Swift Digest
Blog | Swift.org Blog

Swift 2.2 の新機能

New Features in Swift 2.2

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

追加された機能

コンパイル時の 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 では、セレクタを文字列で指定できました。そのため addNewFireflyReferenceaddNewFireflyRefernce のようにタイプミスしてもコンパイルが通り、実行時にメソッドが見つからずクラッシュする、という落とし穴がありました。

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 には classfuncletpublic のように特別な意味を持ち、識別子として使えないキーワードが数多くあります。これまでもキーワードを引数ラベルに使うことはできましたが、バッククォートで囲む必要がありました。

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 += 1i -= 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の削除) を参照してください。

関連リンク