cockscomblog?

cockscomb on hatena blog

Swift 2.2

寒さも和らぎ、日によっては春の訪れを感じさせる今日この頃、いかがお過ごしでしょうか。春といえば Swift です。Swift は春と秋に、まるで衣替えのように大きなリリースがあります。2016年の春と予告されていた Swift 2.2 は、おそらく来週には正式にリリースされるものと思われます3月22日にリリースされました

Swift 2.2 は、バグの修正や警告や診断の改善、コンパイル時間や実行速度の向上が主目的であるとされ、それに加えて Swift 2.0 以来のちょっとした機能向上を図ってのリリースとなります。Swift2.2 は OSS となった Swift の初めてのバージョンアップでもあります。すなわちコミュニティからの直接的なフィードバックを経た、最初の Swift と言えるでしょう。そんな Swift 2.2 の変更から主だった(おもしろい)部分を紹介します。

春に備えて準備をはじめましょう!

class の failable initializer でより早く失敗できる

Swift 2.1 では、class の failable initializer (init?()init() throws) に制限がありました。

For classes, however, a failable initializer can trigger an initialization failure only after all stored properties introduced by that class have been set to an initial value and any initializer delegation has taken place.

Swift 2.1 までの The Swift Programming LanguageFailable Initializers に記載されていたのこの制限により、stored property を全て初期化した後じゃなければ return nilthrow ができませんでした。

Swift 2.2 ではこれが解消され、より早いタイミングで失敗できるようになりました。struct など value type ではできていたことではありますが、class で利用できると便利なことが多いでしょう。

Ref.

#if swift(>=2.2)

#ifSwift のバージョンによって分岐させられるようになりました。当てはまらない方はコンパイラに解釈されないので、エラーや警告も発生しません。この機能により、複数Swift のバージョンに対応したコードを書けるようになります。特にライブラリの開発で役に立つでしょう。

#if swift(>=2.2)
    print("Swift 2.2!")
#else
    // コンパイラによって解釈されない
#endif

Swift 2.2 以降で使えることで、例えば Swift 3.0 と 2.2 のふたつのバージョンでコンパイルできるコードが書けるようになります。しかし Swift 3.0 では大きく API が変更される予定なので、分岐によって分けることがどれくらい現実的かは未知数ではあります。

Ref.

関数を参照する際に引数ラベルまで指定できる

Swift では関数を簡単に参照することができますが、引数ラベルの名前が違う以外はすべて同じシグネチャを持つ関数があった場合でも、引数ラベルまで利用して参照できるようになりました。

class NiceClass {
    func awesomeMethod(x: String, some: String) {}
    func awesomeMethod(x: String, another: String) {}
}
let nice = NiceClass()

let some = nice.awesomeMethod(_:some:)
let another = nice.awesomeMethod(_:another:)

この例のように書くことができます。Swift 2.1 までは nice.awesomeMethod としか書けなかったので、このような状況では曖昧さが解決できませんでした。UIView.insertSubview(_:aboveSubview:)UIView.insertSubview(_:belowSubview:) のように、現実的にもこのようなメソッドが存在していたので、嬉しい変更と言えるでしょう。もしオーバーロードによって型が違うだけのシグネチャがあるときは、型コンテキストを作って型推論に頼らずに書くことで、曖昧さが解決できます。

また関数のほかイニシャライザでも同様です。

Ref.

#selector

Selector というのは Objective-Cメソッドを指すデータで、target-action パターンなどの動的なメッセージパッシングに利用されてきました。Swift においても Objective-C の言語機能を利用して設計されたフレームワークを使用する都合上、この selector を作ることができ、Selector 型のインスタンスとして表現されています。SelectorStringLiteralConvertible として定義されていたため、多くの場合は Selector 型のコンテキストで単なる文字列リテラルとして書かれてきました。

Swift 2.2 からは、Selector の作り方が変更され、#selector 式が導入されます。この式の導入により、Swift の関数を参照するように Selector を生成できます。

let sel = #selector(MyViewController.doSomething(_:))

このとき MyViewController.doSomething(_:) は、@objcObjective-C 用の別名をつけられていても関係なく、Swift 内での表現が利用できます。また SE-0021 と合わせて、引数ラベルまで指定できます。さらにオーバーロードによって引数ラベルまで指定しても曖昧な場合は、as を使って関数の型を記述することができ、例えば #selector(MyViewController.doSomething(_:) as (Int) -> Void) のようにも記述できます。

メソッドのレシーバ (例における MyViewController に当たる部分) はクラスでもインスタンスでもよく、あるいは self でも問題ありません。self の場合は省略もできます。

新しい #selector 式では、独自の構文の導入によってコンパイラによるチェックが可能になりました。存在しないメソッドを指そうとしているとコンパイラがエラーを出します。あるいは Objective-C 側から参照できないメソッドの場合にもエラーとなります。このことでより安全に利用できるようになりました。

Ref.

associatedtype

protocol の associated type を作るとき、Swift 2.1 までは typealias キーワードを使うことになっていましたが、Swift 2.2 からは廃止予定となり、新たに associatedtype キーワードを利用することになります。Swift 3.0 からは associated type を作る目的での typealias は廃止されます。

protocol CollectionType : Indexable, SequenceType {
    associatedtype Generator : GeneratorType = IndexingGenerator<Self>
    func generate() -> Self.Generator
    ...
}

typealias キーワードには他にも用途があり、紛らわしいので新たにキーワードが用意された格好です。protocol に準拠する型で associatedtype に対応する型を与えるときは、これまでと変わらず typealias キーワードを利用します。

Ref.

#file, #line, #column, #function

Swift にはソースコードの現在地を示す識別子 (magic identifier) があり、Swift 2.1 までは __FILE__, __LINE__, __COLUMN__, __FUNCTION__ というように、C のマクロと似たフォーマットが利用されてきました。Swift 2.2 からはこれらは廃止予定とされ、それぞれ #file, #line, #column, #function という # でプリフィックスされた新たな識別子が与えられました。Swift 3.0 では旧来のスタイルは完全に廃止されます。

Swift 2.2 以降 Swift 2.1 以前 説明
#file __FILE__ ファイル名
#line __LINE__ 行番号
#column __COLUMN__ 列番号
#function __FUNCTION__ 関数宣言名

ところで、以前からある line control statement としての #line はどうなるのか、気になることかと思います。残念ながら Swift 2.2 の時点では、どちらも #line で表現され、コンテキストによって意味が変わります。

Line control statement では、#line [line-number] [file-name] というフォーマットで #line#file の結果を変えることができ、#line でリセットできます。特にリセットするための #line と、行番号の #line は非常に曖昧です。現在の所、行の最初のトークンとして現れる #line は line control statement として解釈されるようになっています (apple/swift)。

この曖昧な状態は長くは続かず、line control statement を #sourceLocation(file: "foo", line: 42) のようにリネームすることが決定されています。リセットするときは #sourceLocation() となります。余談ですが、当初は #setline となることに決まっていましたが、継続的な議論の末にさらに変更されました (swift-evolution-announce)。

Ref.

Other Improvements

引数ラベルに予約語が使用可能になる

SE-0001

関数の引数ラベルに、これまでは予約語が使用できませんでした。Swift 2.2 ではこの制限が緩和され、inout, var, let を除くほとんどの予約語が使えるようになります。これまでもバッククオート (“`”) を用いることで予約語を使うことができましたが、それが必要なくなりました。

forin など前置詞として使われる単語も予約語として扱われているので、この変更によって書きやすくなる場面があるでしょう。

tuple が比較できるようになる

SE-0015

要素が比較可能なふたつの tuple を比較できるようになります。これは標準ライブラリに tuple の比較演算子が実装されたことによるものです。比較するためには要素数が同じであることと、個々の要素が Equatable であることや Comparable であることが必要です。

ただし tuple の要素数が6以下である必要があります。それ以上の要素数について比較したい場合には、自分で実装を追加しなければなりません。これはそれぞれの要素数の tuple 毎に実装を用意しなければならない都合上、どこかに限度を設ける必要があり、大抵は要素数が6以下であることや標準ライブラリのバイナリサイズの増加分とのバランスにより決定されています。

@objcenum につけられるようになる

Declaration Attributes

If you apply the objc attribute to an enumeration, each enumeration case is exposed to Objective-C code as the concatenation of the enumeration name and the case name. For example, a case named Venus in a Swift Planet enumeration is exposed to Objective-C code as a case named PlanetVenus.

Raw type が Intenum@objc 属性で Objective-C から見えるようになります。Objective-C から見たときは enum の名前と case の名前が結合されます。

Deprecated

Swift 2.2 では、これまで利用できたいくつかの機能が廃止予定になります。Swift という言語をより洗練させるために、非互換であってもきちんと整理していこうという姿勢ですね。これらは Swift 3.0 で実際に削除されます。代替的な書き方が存在するはずなので、順次置き換えていきましょう。

カリー化関数構文

SE-0002

カリー化関数を作るための func curried(x: Int)(y: Int) という構文は、それほど便利に利用されておらず、連鎖的に言語や実装を複雑にしていることから、Swift 3.0 で削除され、Swift 2.2 でも警告されます。

func curried(x: Int)(y: Int) -> Int {
    return x * y
}

func closure(x: Int) -> (y: Int) -> Int {
    return { y in x * y }
}

例のふたつの関数は同等の働きをするので、直接クロージャを用いた下の形式に書き換えることになるでしょう。

関数の引数の var

SE-0003

func increment(var x: Int) -> Int {
    x += 1
    return x
}
increment(1)

関数の引数の var は、便利であることが少なく、inout などとも紛らわしいことから、Swift 3.0 で削除される予定です。Swift 2.2 でも警告されます。

++, -- 演算子

SE-0004

インクリメントやデクリメントのための ++-- 演算子は、前置と後置で返り値が異なるなど複雑であり、また for-in でループすることがふつうである現在となっては利用頻度も低いなどの理由から、Swift 3.0 で削除される予定です。Swift 2.2 でも警告されます。

x += 1x = x.successor() といった代替的な方法に書き換える必要があります。

C スタイルの for ループ

SE-0007

C スタイルの for ループ (for init; comparison; increment {}) は、Swift において使用頻度が非常に低く、簡潔な for-in に較べて複雑であることから、Swift 3.0 で削除され、Swift 2.2 から警告されます。

ほとんどのケースでは for-in で書き換えられ、あるいは while でも同様に記述できるはずです。for-in では収まりの悪いようなケースについては標準ライブラリの改善によって解決される予定であるほか、パフォーマンス上の違いについても最適化によって解決が図られることになっています (swift-evolution-announce)。

?

Optional の Sequence での lazy な flatMap

SE-0008

lazy を使って LazyCollection を作り、それを flatMap した場合について、flatMapOptional を返す場合についても LazyCollection が保たれるべきである (遅延評価されるべきである) という提案です。Optional を返す場合は LazyCollectionTypeflatMap ではなく SequenceTypeflatMap が利用されるためにこの問題が起きているというわけです。

この提案は Swift 2.2 の変更として承認されていますが、実装された様子がありません (SR-361)。

Xcode 7.3

Xcode も合わせてアップデートされます。オートコンプリートが fuzzy な入力にも対応するのは、誰もが嬉しいことでしょう。つまり例えば、UITableViewdequeueReusableCellWithIdentifier(_:forIndexPath:) メソッドを呼び出すのに、deqcellwi などとタイプしてもよい、ということです。これまでは前方一致でしかオートコンプリートされなかったことを考えると、信じられない進歩です。

Interactive Playgrounds によって、Playground で表示されるビューを操作できるようになるのも、とてもおもしろいことです。UI のプロトタイピングにはもちろん、何かを説明するのにも便利そうですね。

また OSS となった Swift の toolchain を利用することもできるようになります。

Xcode のリリースノートを見ると興味深い変更がもう少しあります。

The static analyzer checks for missing localizability. This check is off by default and can be enabled by selecting Yes for “Missing localizability” in the “Static Analyzer – Generic Issues” build settings. (23414217)

デフォルトでは無効になっているものの、NSLocalizedString を使っていないことを静的解析でチェックできるようになっていそうです。

Simulator.app supports delivering touch pressure to iOS and watchOS by using a Force Touch trackpad.

シミュレータ利用時に、Force Touch トラックパッドで 3D touch できるようになりました。

View debugger も全体的に強化されているようです。

Ref.

Xcode Release Notes


Swift 2.2 ではこのように小規模な改善が行われました。それほど大きな変更ではありませんが、しかし現実の問題に対する具体的な解決策と言えるようなものが多いように思われます。これはまさにコミュニティからのフィードバックの効果と言えるでしょう。

これらに加えて、コンパイル時間が改善しパフォーマンスも向上するほか、コンパイラ自体のクラッシュも減り、より正確に警告やエラーを発するようになります。これらが日々の生産性に寄与することに疑いの余地はありません。

そしてもちろん秋には Swift 3.0 が控えています。Swift 3.0 では新しい API ガイドラインによって API のネーミングに大きな変化が起き、まるで別物のように感じられるかもしれません。enum の個々の case が lower case になる (Optional であれば .some.none になる) と知ったら驚くことでしょう (SE-0023)。それ以外にも、ABI の安定に向けて generics を完全にするための議論が始められています (swift-evolution)。Swift 3.0 と共にリリースされる Swift Package Manager は、Xcode のプロジェクトファイルを生成するようになり、これまでより簡単に Swift のライブラリを扱えるようになるでしょう (apple/swift-package-manager, swift-evolution)。そして Swift 3.0 の開発はまだまだ続きます。今はまだわからない、素晴らしい機能が追加されることでしょう。

詳解 Swift 改訂版

詳解 Swift 改訂版