LoginSignup
342
327

More than 5 years have passed since last update.

Swift 2.2 で変わったところ

Last updated at Posted at 2016-03-22

Swift 2.2 の機能で変わったところを、機能面からざくりとですけど具体例も書いたりしながら挙げてみますね。

そこそこのボリューム感になったので、まずはどんな事柄が書かれているか、ざっくり箇条書きにしておきます。細かい部分については以降でじっくり記してみます。

この内容は Xcode 7.3 (7D175) に搭載されている Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29) を想定した話になります。

新機能

まずは、新たに登場した動作に関するところを記載します。

イニシャライザ

クラスに対して、失敗可能イニシャライザとエラーハンドリングなイニシャライザを実装するときに、初期化できないと判断したときに、保存型プロパティの初期化が終わる前でも初期化処理を打ち切れるようになりました。

これまでは、クラスのときはすべてのプロパティを初期化してから初期化処理を打ち切る必要がありました。構造体は今までも初期化を完了せずに打ち切れたので、クラスもそれと同じになったという感じです。

class MyClass {

    var value: Int

    // 保存型プロパティを初期化しないまま失敗できます。
    init?() {

        return nil
    }

    // 保存型プロパティを初期化しないままエラーを返せます。
    init(value: Int) throws {

        throw MyError()
    }
}

また、継承先のイニシャライザが親のイニシャライザを呼び出す前にも処理を打ち切れますし、コンビニエンスイニシャライザでも他のイニシャライザを呼び出す前に打ち切れます。

class MySubClass : MyClass {

    var subValue: Int

    // 親クラスのイニシャライザを呼び出さなくても処理を打ち切れます。
    override init?() {

        return nil
    }

    // コンビニエンスイニシャライザでも他のイニシャライザを呼び出さず処理を打ち切れます。
    convenience init?(value: Double) {

        return nil
    }
}

プロトコル

associatedtype

プロトコルには付属型という、プロトコルを型に適用する段階で具体的な型を決められる(それまで先送りできる)ものを規定することができました。今まではこれを typealias キーワードで規定しました。

protocol MyProtocol {

    // 任意の型を扱えるようにしておいて…
    typealias Element

    var value: Element { get }
}

struct MyItem : MyProtocol {

    // 適用時に具体的な型を割りあげられる(ここで Element = String になる)
    var value: String
}

この時に使う typealias という言葉が Swift 2.2 からは非推奨になって、代わりに、新しく使えるようになった associatedtype を使うように勧められます。

protocol MyProtocol {

    // 任意の型を扱えるようにしておいて…
    associatedtype Element

    var value: Element { get }
}

意味合い的には、先ほどの例では最終的には確かに ElementString に置き換えられているので型の別名 (typealias) みたいに捉えることもできますけど、プロトコルで使う typealias は普通使うそれとは違った側面もあって混乱しがちな印象なので、こうして新しい associatedtype みたいなキーワードが使われるのは、読みやすさの面で好印象です。

ジェネレーター

ジェネレーターというのは、何か値を順々に生成するもので、たとえば 配列から順番に要素を取り出す みたいなときにもジェネレーター (IndexingGenerator) が使われています。

具体的な仕組み(型)を隠蔽した任意のジェネレーターを作りたいとき、これまではグローバルの anyGenerator 関数を使って作っていました。

var generator = anyGenerator {

    values.generate()
}

これが、今度から AnyGenerator を使って生成するようになりました。コードだけ見ると先頭の A が大文字か小文字かみたいな違いですけど、仕組み的には大きな変化です。

var generator = AnyGenerator {

    values.generate()
}

タプル

同じ型のタプル同士で、各要素の型が Equatable に準拠していて、かつ要素が6つ以下の場合は、タプルを直接 ==!= 演算子で比較できるようになりました。

let tuple1: (Int, Double, String) = (1, 2.0, "TEST")
let tuple2: (Int, Double, String) = (1, 2.0, "TEST")

tuple1 == tuple2
tuple1 != tuple2

同様に、各要素の型が Comparable に準拠していて、かつ要素が6つ以下の場合は、タプルを直接 < などで大小比較できるようになりました。結果は、前方から順に辞書的に比較されます。

let tuple1: (Int, Double, String) = (1, 2.0, "TEST")
let tuple2: (Int, Double, String) = (1, 2.0, "TEST")

tuple1 < tuple2
tuple1 <= tuple2
tuple1 > tuple2
tuple1 >= tuple2

関数やメソッド

引数ラベル名を考慮した関数の参照

Swift では関数やメソッドも関数型の変数に入れて扱えます。たとえば、次のような型があったとします。

struct MyValue {

    // 同じ名前 (method1) で、唯一存在するメソッド
    func method1(value: Int) {

    }

    // 同じ名前 (method2) で、引数の数が異なるものが存在する
    func method2(value: Int) {

    }

    func method2(value1: Int, value2: Int) {

    }

    // 同じ名前 (method3) で、引数の数が同じで型が異なるものが存在する
    func method3(value: Int) {

    }

    func method3(value: Double) {

    }

    // 同じ名前 (method4) で、引数の数も型も同じでラベルが違うものが存在する
    func method4(_ value: Int) {

    }

    func method4(value: Int) {

    }
}

従前のおさらい

このとき、たとえば method1 については、次のように変数に取得できていました。

let value = MyValue()
let f = value.method1

同じ名前で引数が異なるメソッドが複数ある method2 の場合、単純に let f = value.method2 みたいに取得することはできませんが、次のように型を明示することで取得できていました。

let f1: (Int) -> Void = value.method2
let f2: (Int, Int) -> Void = value.method2

同じ名前で引数の型が異なる method3 の場合も、単純に let f = value.method3 みたいに取得することはできませんが、先ほどと同様に型を明示することで取得できていました。せっかくなので、別の書き方で紹介します。どちらも同じことですが、先ほどは代入先の変数の型を指定していましたが、今度は代入元で as 演算子を使って型を明示しています。

let f1 = value.method3 as (Int) -> Void
let f2 = value.method3 as (Double) -> Void

本題の新機能

ここまでは順調でしたが、さて、引数の数も型も同じでラベル名だけが異なる method4 を変数に入れようとすると、今まではどうにもできない状況でした。というのも、ラベル名が違うだけなので、最終的な型はどちらも (Int) -> Void になります。そのため、型を明記したところでどちらのメソッドのことを意味しているかが判断できませんでした。

それが Swift 2.2 からは区別して扱えるようになります。そのためには次のように、メソッド名に引数ラベルのリストを添えます。引数ラベルのリストの書き方は、ラベル名を意味するように名前の後に : を添えます。ラベルが省略されるところのラベル名は _ になります。

let f1 = value.method4(_:)
let f2 = value.method4(value:)

これによって、引数が同じでラベルだけ違うメソッドを区別でき、呼び出すときはラベルを添えて実行できます。

f1(10)
f2(value: 10)

ちなみに、ラベル名なしの関数型変数で扱うことで、呼び出し時にラベルを付けないこともできます。もちろん代入先のラベルの有無にかかわらず、代入した方のメソッドが適切に実行されます。

let f1: (Int) -> Void = value.method4(_:)
let f2: (Int) -> Void = value.method4(value:)

f1(10)
f2(10)

イニシャライザで便利に使う

ラベル名を指定して関数型として扱う機能で便利に使えるのは、イニシャライザを扱うときです。イニシャライザはいわゆるメソッド名が必ず init で、ラベル名によって区別することがよくあります。たとえば特に、別の型を受け取って自分自身の型に変換する変換イニシャライザで顕著です。

たとえば Int 型を扱う配列 let values = [1, 3, 5, 7] があったとき、これらをすべてまるっと文字列化した配列を作りたい場合は次のようにします。このとき map メソッドを使って、それぞれの値を String 型が持つ Int 型からの変換イニシャライザに渡します。

values.map(String.init)

ところが Int 型の配列から UInt 型の配列を作りたいとき、UInt 型には Int 型からの変換イニシャライザがちゃんと用意されてるのですけど、単純に values.map(UInt.init) としても ambiguous use of 'init' というエラーになってしまいます。この原因は UInt が、他にも Int 型を引数に取るイニシャライザ init(bitPattern: Int) を持っているためで、単に UInit.init と記しただけでは、どちらのイニシャライザを使ったら良いのかわからないためです。

ここで Swift 2.2 で使えるようになった、ラベル名を加味した関数指定が役立ちます。

values.map(UInt.init(_:))

このように、ラベル名が無い方のイニシャライザを使うように指定することで、変換イニシャライザを使って Int 型の要素を UInt 型にマップした配列を取得できるようになりました。

イニシャライザの衝突はよくある

このような場面はそんなに特殊な場合ではなくて、たとえば次のように、整数リテラルからインスタンスを作りたくて IntegerLiteralConvertible に対応させた自作の型とかでありがちです。

struct MyIndex : IntegerLiteralConvertible {

    var rawValue: Int

    init(_ value: Int) {

        self.rawValue = value
    }

    init(integerLiteral value: Int) {

        self.rawValue = value
    }
}

内部データを数値で表現する場合、整数型 (Int) からの変換イニシャライザを用意することはよくありますけど、それと合わせて 整数リテラルに対応したい と思うこともよくあります。このとき、整数リテラルに対応させるために必須の init(integerLiteral: Int) を実装した途端、もう衝突しています。

これまでは、こうなると MyIndex.init を諦めて { MyIndex($0) } みたいにクロージャーを作ってあげないといけないみたいになってましたが、新たにラベル名を加味した選択ができるようになったおかげで、クロージャーをわざわざ作らなくても良くなったのが嬉しいところです。

引数ラベル名に予約語を使う

引数ラベル名や引数名で、予約語とかぶる名前でも、ほとんどのものが指定できるようになったそうです。例えばこのような感じで、予約語 protocol を引数名としてそのまま使うことが可能です。

func action(protocol: Int) -> Int {

    return `protocol` + 100
}

ほとんどの予約語が使えますけど var, let, inout は引数を扱う上で重要な予約語なので使えません。これらを使いたいときは従前通り、引数名のところでも `` をつけて記載する必要があります。

func action(`let`: Int) -> Int {

    return `let` + 100
}

引数として指定した予約語と同じ名前の変数を実装内で使うときには、今まで通り、予約語ではないことを示す `` をつけて使う必要があるようでした。

ただし `` を付けないといけない手間は実装だけで、実際に作った関数やメソッドを呼び出すときには、宣言のときと同じように予約語をそのままラベルに使って呼び出せます。提供される機能を使う側にとって何も煩わされることがないので、機能を提供する側は積極的に使っていって良さそうです。

// 予約語 `in` を引数ラベルで指定したとき。
func write(text: String, in: Language) {

    // 実装内では予約語を `` で括る必要があります。    
    textStore[`in`] = text
}

// 呼び出しのときは、予約語をそのまま記載できます。
write(text, in: language)

また、実装時の `` を書くのが煩わしいと思える場合は、外部引数名と内部引数名を分ける手があります。内部引数名を予約語にあるものとは違う名称にしてしまえば、実装内ではそれを使って値を扱えます。外から見えるラベル名は、外部引数名と内部引数名を分けなかった時と同じです。

func write(text: String, in language: Language) {

    textStore[language] = text
}

write(text, in: language)

ArraySlice のインデックス

配列から部分配列を切り出すと、それは ArraySlice 型として切り出されます。部分配列は、切り出す時に指定したインデックスが維持されています。

var items = [1, 2, 3, 4, 5, 6]
var subItems: ArraySlice = items[2 ..< 5]

items.indices           // 0..<6
subItems.indices        // 2..<5

この部分配列に対して、これまでの Swift 2.1 では removeFirst メソッドを実行すると、先頭要素が削除されてその後にインデックス番号が詰められるという動きをしていました。

subItems.indices        // 2..<5

subItems.removeFirst()

subItems.indices        // 2..<4

これが Swift 2.2 からは ArraySliceremoveFirst を実行しても、インデックス番号が維持されるようになりました。

var items = [1, 2, 3, 4, 5, 6]
var subItems: ArraySlice = items[2 ..< 5]

subItems.indices        // 2..<5

subItems.removeFirst()

subItems.indices        // 3..<5

このように Array については removeFirst に伴ってインデックスも前方へ詰められていきますけど、切り出した ArraySlice に限っては、切り出す前のインデックスを保ったまま動作してくれるようになっています。スライスして処理しつつ、おおもとの配列に対して処理をかけていきたいときとかに、インデックスがそのまま使えて便利そうです。

ただし、すぐ上の行でも触れたことですが勘違いやすいので補足すると、このように振る舞うのは ArraySlice だけです。切り出す前の Array 型は removeFirst を実行すると、従前通りインデックス番号が詰められます。混同しないように注意する必要がありそうです。

items.indices           // 0..<6

items.removeFirst()

items.indices           // 0..<5

それ以外の Range 型についても見てみると、こちらは ArraySlice ではないですけど Slice を切り出せるようになってますけど、こちらについてもとりあえず、インデックスは維持されるような様子でした。範囲の場合は範囲がずれたらどうしようもないので、これはもともと妥当な動作と思いますが。

var range = 0 ..< 50

range.startIndex        // 0
range.indices           // 0..<50

var subRange: Slice = range[5..<10]

subRange.removeFirst()
subRange.startIndex     // 6
subRange.indices        // 6..<10

バージョン判定

コード内で #if swift を使って、ビルド時の Swift バージョンに応じたコード切り替えができるようになりました。

let check: Int

#if swift(>=2.2)
check = 1
#elseif swift(>=2.1)
check = 2
#else
check = 3
#endif

条件から外れるコードはビルド対象外なので、言語レベルで互換性のないコードをこれでうまく分岐すれば将来、複数のバージョンに対応する Swift コードを記載できるようになるはずです。

ソースコードの位置に関する情報

Swift にはこれまで、ソースコードに関する情報を取得するためのマクロっぽいシンボルが用意されていました。

ソースコードが書かれているファイル名を取得したいときは __FILE__ を、行番号を取得したい場合は __LINE__ を、文字位置を取得したい場合は __COLUMN__ を、そのコードが書かれている関数やメソッドを __FUNCTION__ で取得できるようになってました。

これらが Swift 2.2 からは、それぞれ #file, #line, #column, #function に変更になりました。従前の __ で括った書き方をすると、コンパイラが非推奨としてこの新しい書き方を勧めてきます。

今までは Objective-C の流れを汲んでプリプロセッサマクロみたいな書式になっていましたけど、Swift から見て普通の変数ともとれることもありますし、Swift では一般に、ソースコードの制御に係る識別子は # で始まる名前になっている印象なので、この変更にはとても好印象です。

ちなみにこれらは、たとえばデバッグ情報を出力するときに便利だったりして、実際に XCTestXCTAssertEqual などでも次のように使われています。厳密に書くと複雑なので、要所だけ切り抜いて引用にします。

func XCTAssertTrue(expression: () -> BooleanType, _ message: () -> String = default, file: StaticString = #file, line: UInt = #line)

こんな風に #file#line とが、引数 fileline の既定値として使われていて、プログラマーは何も意識しなくても、自動的に XCTAssertEqual の書かれたソースファイルと行番号が取得されて、エラーと判定した際にコードの位置をレポートできるようになっています。

Objective-C 互換機能

Objective-C との連携を意識した部分でも幾つか改良が図られている様子です。

セレクター

Objective-C 互換メソッドやプロパティを performSelector メソッドを使って動的実行するときに使うセレクターは、Swift では Selector 構造体を使って表現します。

この Selector を初期化するときに、今までは文字列リテラルで初期化することになっていました。

import Foundation

let formatter = NSNumberFormatter()
let selector: Selector = "stringFromNumber:"

formatter.numberStyle = .DecimalStyle

let string = formatter.performSelector(selector, withObject: 2000).takeRetainedValue()

これが、Swift 2.2 からは次のようにリテラルっぽい書式を使ってセレクターを作れるようになりました。書き方としては #selector で始まり、引数として 型名.メソッド名(引数リスト) という書き方になります。

let selector: Selector = #selector(NSNumberFormatter.stringFromNumber(_:))

このようにすることで、今まで文字列で作成していた Selector と同じものが作られます。リテラルっぽい表記ですけど、他のリテラルのような変換プロトコルみたいなものは用意されていない感じです。

複数の引数

メソッドを指定するところで書いている (_:) みたいなところは引数リストですが、この場合は、引数が1つあって、その引数のラベルが省略されることを意味しています。複数の引数を取る場合は、次のように

let selector = #selector(NSObject.setValue(_:forKey:))

このとき特に嬉しいのが、セレクターを書くときもコード補完が効くところです。また、もし存在しないセレクタを記載したときは、実行前にビルドエラーになってくれます。

同じ名前のメソッドに注意

複数のクラスで同じ名前のメソッドがある場合は、少し注意する必要性が出てきます。例えば、次のようなクラスがあったとします。

final class MyClass1 : NSObject {

    func method() -> String {

        return "MyClass1"
    }
}

final class MyClass2 : NSObject {

    func method() -> String {

        return "MyClass2"
    }
}

これらのクラスが実装している method をセレクターとして使いたい時、それぞれ次のように書けますが、これらはすべて同じものになります。

let sel0: Selector = "method"
let sel1: Selector = #selector(MyClass1.method)
let sel2: Selector = #selector(MyClass2.method)

どうやらメソッド名と型に注目しているみたいで、セレクターが出来てしまえばクラス名は特にどうでもよくなるようです。これらのどのセレクターをどちらの型のインスタンスに適用しても、適用したインスタンスにある同じシグネチャのメソッドを実行できるようになっているようです。

それともうひとつ気になるのが、同じシグネチャのメソッドを文字列で指定した場合に Swift が推奨する自動修正を適用すると、現状ではあるうちのどれかの型が代表して使われるようです。

シグネチャが同じであれば使用上は問題はないと思うのですけど、ぜんぜん違うクラスのセレクターを指定しているように見えるのも気持ちが良くないところなので、自動修正機能を使う場合はそのあたりも気にしておくと良いかもしれません。

プロパティのセレクター

メソッドではなくプロパティをセレクターで表現したい場合には、新しい書き方的な NSNumberFormatter.format という表現はできない様子です。

プロパティをセレクターで表現したい場合には、相変わらず次のようにして、文字列を使った表現をする必要があります。

let sel: Selector = Selector("format")

ちなみに、メソッドの場合は非推奨として新しい書き方を勧められますけど、プロパティに限ってはこのような書き方で許してもらえます。ただしこのように Selector の変換イニシャライザとして記載する必要があり、文字列リテラルとしてそのまま指定すると非推奨として扱われます。

列挙型

Swift の列挙型を Objective-C に取り込むときに、これまでも @objc を使って、次のようにして Objective-C では別の名前で使うみたいなことができました。

import Foundation

@objc(ESSwiftEnum)
enum SwiftEnum : Int {

    case Value1
    case Value2
}

このようにすると Swift では普通に SwiftEnum.Value1 みたいに使えながら、Objective-C では次のように、列挙型の名前が @objc で指定したものになり、各ケースがその列挙型名を添えた名称で使えるようになります。

ESSwiftEnum value = ESSwiftEnumValue1;

これまでは各ケースの名前はそうやって自動で作られましたけど、Swift 2.2 からは名前も適宜調整できるようになった様子です。次のように各ケースに対して @objc を使って名前を指定できます。

@objc(ESSwiftEnum)
enum SwiftEnum : Int {

    @objc(ESSwiftValue1) case Value1
    @objc(ESSwiftValue2) case Value2
}

こうすると Objective-C からは、そうやって指定したケースを使った記載ができるようになります。

ESSwiftEnum value = ESSwiftValue1;

非推奨になった機能

続いて、非推奨になった機能を記載します。将来の Swift 3.0 では廃止される予定で、その移行期間みたいな感じになるようです。

非推奨なものはビルドも通るし、変わりの書き方を Swift が教えてくれるのでそんなに気にしなくても良さそうな感じもあるのですけど、Swift が推奨する書き方が本来の動作と違うコードを生むこともあるようなので注意しておく必要があります。

関数・演算子

カリー化構文

引数を複数回に分けてとるカリー化構文が非推奨になります。使おうとすると代わりに2つの引数を取る関数に定義し直すことを勧められます。

// NG: Curried function declaration syntax has been removed
func action(v1: Int)(v2:Int) -> Int {

    return v1 + v2
}

action(1)(v2: 2)

引数を複数回に分けてとる関数を定義したい場合は、これまでも書けたもう一つの方法の、引数をとって戻り値を返す関数を返す関数で定義するようにします。

func action(v1: Int) -> (v2:Int) -> Int {

    return { (v2:Int) -> Int in

        return v1 + v2
    }
}

action(1)(v2: 2)

インクリメント演算子とデクリメント演算子

C 言語でお馴染みだったインクリメント演算子 ++ とデクリメント演算子 -- が非推奨になりました。

var value: Int = 10

value++
value--

++value
--value

これと同じことをしたい場合は、Swift も勧めてくれるように、次のように += 演算子や -= 演算子などを使うようにします。

value += 1    // ++value と同等
value -= 1    // --value と同等

ところで、ここでものすごく注意なのですけど、このインクリメント演算子とデクリメント演算子は、変数の前につけるか後につけるかで動きが微妙に変わってくることはよく知られてると思うのですけど、Swift が推奨してくる通りに書き換えると、どちらにつけた場合でも同じコードに直してくれます。

具体的には、たとえば value++ であっても FixIt は value += 1 に直してくれる様子です。つまり期待している動作と違うコードに直される可能性があるので注意が必要です。

整理しておくと、まず ++value は次の通りの動作をします。

let result = { () -> Int in

    value += 1
    return value
}()

そして value++ は次の通りの動作をします。こちらが、今のところ自動修正では間違ったコードに変換されるので注意が必要です。

let result = { () -> Int in

    defer {

        value += 1
    }

    return value
}()

制御構文

C 言語スタイルの for ループ

次のような、いわゆるお馴染みの C 言語スタイルの for ループが非推奨になりました。

for var i = 0; i != values.count; ++i {

}

これからは次のように、範囲を作って繰り返し処理するという方法をとるのが推奨されます。

for i in 0 ..< values.count {

}

把握しきれていないところ

他にも幾つか変わったところがあるらしいのですけど、これまであまり気にしてこなくて、どう変わったかいまひとつ把握しきれていないものがあります。それらについても簡単にながら記載しておくことにします。

すべてのスライスが removeFirstremoveLast を持つように

すべてのスライス型が removeFirst メソッドと removeLast メソッドを持つようになったそうです。スライス型といえば、たぶんこんなものが挙げられると思います。

let subRange: Slice<Range<Int>> = (0 ..< values.count)[2 ..< 4]

subRange.removeFirst()

この辺りは自分はあまり詳しく見ていたわけではないので分からないのですが、とりあえずスライス型はどれも CollectionType に所属していて、そこでは特定の条件下でだけ removeFirstremoveLast が既定の実装として備えられているようでした。

extension CollectionType where SubSequence == Self {

    public mutating func removeFirst() -> Self.Generator.Element
    public mutating func removeFirst(n: Int)
}

extension CollectionType where SubSequence == Self, Index : BidirectionalIndexType {

    public mutating func removeLast() -> Self.Generator.Element
    public mutating func removeLast(n: Int)
}

ただ、これが Swift 2.1.1 の時に適用されない場合がどの型であったかまでは、今のところ確認できませんでした。

そして全てスライスで removeFirst が採用されたというのは良いとして、Range<T>removeFirst を使おうとすると Ambiguous reference to member 'removeFirst()' エラーになる様子でした。以前もそうなっていたかは把握できていません。

C 言語の匿名構造体

C 言語の匿名構造体が Swift に取り込まれるときに、ネストされた構造体として取り込まれる、らしいです。ただ、具体的にどういうものになるかを確認したかったのですが、どういうコードを想定すれば良いのか分かりませんでした。

C 言語の匿名構造体が使われる例としては、たとえば次のようなものがありますが、これは Swift では普通に名前付きの構造体として扱える感じです。

typedef struct
{
    int v1;
    int v2;

} MyCStruct;

これ以外にも匿名構造体の使い道はいろいろあるみたいですけど、そもそものどういうときにどう使うのが適切なのかみたいなところがよく分からなくて、とりあえず今の自分の知識では C 言語の匿名構造体を Swift に取り込むところまで達せなさそうな印象でした。ともあれこうして Swift が対応しているということは、少なくとも C 言語で比較的普通に使われる手法だったりするのでしょう。

342
327
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
342
327