Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Build Insiderオピニオン:小野将之(6)

Build Insiderオピニオン:小野将之(6)

Swift 3.1のリリースプロセスおよびそれに含まれる変更内容の紹介(後編)

2016年12月26日

Swift 3.1のリリースが2017年春に迫ってきた。今回は前後編に分けて、そのリリースプロセスや変更内容を解説する。後編ではSwift 3.1に取り込まれることが想定される変更点を取り上げる。

小野 将之
  • このエントリーをはてなブックマークに追加

Swift 3.1の主な変更一覧

 SwiftリポジトリのCHANGELOGとSwift EvolutionリポジトリのProposal Statusを見ると、Swift 3.1の開発状況が分かる。以下では12月20日時点で最新のDEVELOPMENT-SNAPSHOT-2016-12-15-aツールチェインでの実装状況を記す。

実装済み

 まずは実装済みのものから紹介しよう。

実装中

 現時点のスナップショット版では試せないが実装中であり、基本的にSwift 3.1に入る予定のものには以下がある。

実装着手前

 Swiftに組み込むことは決定しているが、まだ実装着手に至っていないものもある。Swift 3.1に含められるように目指しているが、前編で述べたタイムリミットである2017年1月16日に間に合わなければ次期バージョン以降に持ち越しとなる。

レビュー中

 以下は現在レビュー中だ。

Swift 3.1の変更を先取り

 それでは、今実際に試せるSwift 3.1に含まれる予定の変更を見ていこう。コード例は以下の環境によるものだが、基本的にはこれ以降のバージョンであれば同様に動くはずである。

  • Xcode 8.2.1
  • Swift Toolchainバージョン: DEVELOPMENT-SNAPSHOT-2016-12-15-a

SE-0045: Sequenceプロトコルにprefix(while:)メソッドとdrop(while:)メソッドが追加

 Swiftには、map(_:)filter(_:)など、多くのコレクション操作メソッドが存在するが、そこに欠けていたprefix(while:)メソッドとdrop(while:)メソッドが追加された。

 まず、prefix(while:)メソッドの簡単な使い方を以下に示す。これはコレクションの先頭から走査を開始し、条件に一致する間はその要素を抽出する。

Swift
let x = [1, 2, 3, 4, 5]
x.prefix { $0 < 3 } // [1, 2]
// `filter(_:)` と同じ結果
x.filter { $0 < 3 } // [1, 2]
prefix(while:)メソッドの使用例

 上の例では、結果はfilter(_:)メソッドを使った場合の結果と同じになる。ただし、prefix(while:)メソッドでは条件に一致するとそれ以降の走査をしないため、評価は3回だけ走るが、filter(_:)メソッドでは全要素を走査するため評価が5回走る違いがある。このように「ある条件を満たしている間の要素を抽出したい」という用途であれば結果が同じだとしてもprefix(while:)を用いるのがよい。

 ただ、上の例で2つメソッドの呼び出し結果が同じになったのは、コレクションがたまたまソート済みだったからだ。例えばコレクションの要素を逆順にしてから同じ処理を行うと、filter(_:)メソッドの結果は先の例と変わらないが、prefix(while:)メソッドの方は1つ目の要素「5」が条件を満たさないため、そこで処理が打ち切られて、結果は空になる。

Swift
let y = x.reversed() // [5, 4, 3, 2, 1]
y.prefix { $0 < 3 } // []
// `filter(_:)` と結果が異なる
y.filter { $0 < 3 } // [1, 2]
コレクションの内容によっては2つのメソッドの実行結果は全く異なるものになる

 このようにこれら2つのメソッドは一見似ているが全く別物なので、もちろん適宜最適なコレクション操作メソッドを使い分ける必要がある。

 drop(while:)メソッドは、prefix(while:)メソッドとは逆に「ある条件に合致している間はその要素を除去したい」ときに用い、例えば次のように使える。

Swift
let x = [1, 2, 3, 4, 5]
x.drop { $0 < 3 } // [3, 4, 5]
x.reversed().drop { $0 < 3 } // [5, 4, 3, 2, 1]
コレクションを前方から走査して、条件に合致した要素をドロップ

 prefix(while:)メソッドとdrop(while:)メソッドの追加は「SE-0045」という5月に作られた古めのProposalで提案された。このことから分かるように、これはSwift 3.1の目玉の変更ではなく、単にSwift 3.0に間に合わなかったのが、ようやくSwift 3.1のタイミングで入ることになったということである。単なるメソッドの追加なので、Swift 3.0に詰め込みたかった破壊的変更の絡むProposalと比べて優先度が低かったため対応が遅れた。

SE-0141: @available構文においてのSwiftバージョン分岐

 Swift 2.0で、@available#available構文を用いてプラットフォームとそのバージョン指定を行えるようになった。

Swift
@available(iOS, introduced: 10)
func f1() { // iOS 10以上でないと呼べない
}

func f2() {
  if #available (iOS 10, *) {
    // iOS 10以上のときのみ処理される
  }
}
@available/#available構文

 Swift 3.1では、2つのうちの@available構文でSwiftのバージョンも指定できるようになる。ただし、#available構文でのSwiftのバージョン指定には対応しておらず、Swift language version checks not allowed in #available(...)というコンパイルエラーになってしまう。

Swift
func ng() {
  if #available(swift 3, *) {
  }
}
#available構文でSwiftのバージョンを指定するとエラーになる

 基本的な使用例は次の通りであり、@available構文で修飾されたもの(メソッド、型など)を使えるSwiftのバージョン/非推奨となるSwiftのバージョン/廃止となるSwiftのバージョンを表現でき、対応外のバージョンのSwiftが使用されたときには警告やコンパイルエラーを発生してくれる。f5メソッドに付けたrenamedはオプション引数で、これを付けることで廃止になった代わりに新設されたメソッドを示せる。

Swift
@available(swift, introduced: 4)
func f3() { // Swift 4以降で使える
}
@available(swift, deprecated: 4)
func f4() { // Swift 4で非推奨(警告表示)
}
@available(swift, obsoleted: 4, renamed: "f3")
func f5() { // Swift 4で廃止(代わりにf3メソッドを使う)
}
@available構文の使用例

 第1引数にiOSなどを指定した場合は、unavailable構文によって「そのプラットフォームではバージョンを問わず使えない」ということも表現できるが、swiftを指定した場合はunavailable指定はできない。これはUnknown platform 'swift' for attribute 'available'という警告が表示されることで分かる。

Swift
@available(swift, unavailable)
func f6() {
}
@available構文の第1引数に「swift」を指定した場合、unavailable指定は使えない

 もともとSwiftでは、#if swift(>=N)マクロによって、次のようにSwiftバージョンによってソースコードを切り替えることはできたが、このマクロを使うとコンパイル時にどちらかの処理が切り捨てられてしまう。

Swift
#if swift(>=4)
  // Swift 4以降用のコード
#else
  // Swift 4より前用のコード
#endif
if swift(>=N)マクロ

 このProposalは、前編で取り上げた-swift-version Nフラグによって過去のSwiftバージョンの互換モードをサポートできるようにする対応(「SR-2582: Add -swift-version command line flag」)にも関連している。

 これを実現するには、以下の2つの選択肢があったが、「標準ライブラリの配布をしやすい」などの理由で後者に決まり、実装が行われた。

  • #if swift(>=N)マクロを利用して、バージョンごと複数バイナリを生成
  • @availableでSwiftのバージョンも指定できるようにして、複数のSwiftバージョンに対応した単一バイナリを生成

 経緯としてはSwift言語自体の開発を円滑にするためのものだったが、通常のアプリ開発においても例えば「Swift 3の制約でどうしてもこの対処が必要だが、Swift 4に含まれるあの対応が入ればもっとシンプルに書ける」というときなどに、あらかじめ@available(swift, deprecated: 4)を仕込んでおき、その後、Swift 4でビルドした際にコンパイルエラーにする、という活用などもできるであろう。

SR-1009: 具体的な型を用いたジェネリクス制約

 まず、Swift 3.0まで「具体的な型を用いたジェネリクス制約」に対応しておらず不便だったことを、Optional<String>型に新しいメソッドを足す例にとって説明する。

 Optional型は次のようにWrappedというジェネリクス型で含む値の方を表現するようになっている。

Swift
public enum Optional<Wrapped> : ExpressibleByNilLiteral { ... }
SwiftのOptional型の定義

 Optional<String>型に新しいメソッドを追加したいとすると、素直に考えれば次の「コードA」のような書き方になるであろう。

Swift
// コードA
extension Optional where Wrapped == String {
  /** nilや空文字の場合はtrue、それ以外のときはfalseを返す */
  var isNilOrEmpty: Bool {
    return self?.isEmpty ?? true
  }
}
コードA: Optional型にメソッドを追加する例

 ただ、この書き方だと、Swift 3.0では次のようなコンパイルエラーとなってしまう。

Same-type requirement makes generic parameter 'Wrapped' non-generic
上のコードはSwift 3.0ではコンパイルできない

 「具体的な型を用いたジェネリクス制約」に対応していない、ということである。

 対処としては次のように、Stringを直接使わずにそれを包むProtocolStringProtocol)を用意して、型制約としてはWrappedStringProtocolに準拠している(where Wrapped == Stringではなくwhere Wrapped: StringProtocol)という記述にすれば、制限を回避できる。

Swift
protocol StringProtocol {
  var value: String { get }
}
extension String: StringProtocol {
  var value: String { return self }
}

extension Optional where Wrapped: StringProtocol {
  /** 値があればそれを返して、なければ空文字を返す */
  var getOrDefault: String {
    return self?.value ?? ""
  }
}
上のエラーを回避するコード

 Swift 3.1では、この対処コードを書かずとも、「コードA」の書き方でコンパイルが通るようになった。さまざまなライブラリやアプリのコードでこのような対処がなされている現状なので、この書き方が可能となる恩恵は大きい。

SR-1446: ジェネリクス型の入れ子

 クラスの所属・役割を明確にしたり、名前の重複を回避したりするために、型を入れ子で表現することがある。特にSwiftの場合、名前空間がモジュール単位となっていて、細かい粒度で名前空間を用意しにくいこともあり、型の入れ子定義は多用されていると感じている。

 ただ、これもジェネリクス型は入れ子にできないという制限があったため、Swift 3.0までは仕方なくジェネリクス型の入れ子をあきらめるという、設計の妥協をせざるを得なかった。

 Swift 3.1では、以下のいずれも書けるようになった。

Swift
// SwiftリポジトリのCHANGELOGのコード例をそのまま掲載
struct OuterNonGeneric {
  struct InnerGeneric<T> {}
}

struct OuterGeneric<T> {
  struct InnerNonGeneric {}
  struct InnerGeneric<T> {}
}

extension OuterNonGeneric.InnerGeneric {}
extension OuterGeneric.InnerNonGeneric {}
extension OuterGeneric.InnerGeneric {}
ジェネリクス型の入れ子

 ジェネリクスの制限については、2016年5月に、swift-evolutionメーリングリストに「[swift-evolution] [Manifesto] Completing Generics」という声明が投稿されていて、ジェネリクスの機能制限の解消は大きな課題の一つであった。

 今見た「SR-1009: 具体的な型を用いたジェネリクス制約」と「SR-1446: ジェネリクス型の入れ子」の2件でその一部が解消した。そういう意味では、これらの対応はSwift 3.1における改善の大きな目玉といえるだろう。まだ着手前の「SE-0143: 条件付きのジェネリクス制約」など、まだ残っている制限はあるが、今後もジェネリクスの制限は少しずつ改善されていくであろう。

SR-1882: 文字列補間(String interpolation)にOptional型を直接用いると警告

 以下のコードではhello Optional("world")という文字列が出力される。

Swift
let s: String? = "world"
print("hello \(s)")
Optional型の値で文字列補間

 このコードだけ見れば妥当な挙動だが、通常のアプリでは意図せずOptionalの変数を用いて文字列を組み立ててしまうミス(hello worldとするつもりがhello Optional("world")になるなど)の発生につながる。

 Swift 3.1では、このケースで以下の警告が発生するようになる。

String interpolation produces a debug description for an optional value; did you mean to make this explicit?
Swift 3.1で上記のコードをコンパイルすると警告が表示される

 本当にOptional()で包まれた文字列でよいのであれば、次のようにすれば警告を解消できる。

Swift
print("hello \(s as String?)")
print("hello \(String(describing: s))")
「Optional()」付きの文字列値で文字列補間を行う(警告なし)

 Optional()を外したい場合は普通にあらかじめアンラップしてから使うか、次のように?? ""でデフォルト値を設定するなどすればよい。

Swift
print("hello \(s ?? "")")
??演算子を使ってデフォルト値を指定すれば「Optional()」が外れる

 特に、Swift 3.0では「SE-0054: ImplicitlyUnwrappedOptional型の廃止」によって以前にImplicitlyUnwrappedOptional型だったものがOptional型に変わった箇所が多く発生したので、警告で追従漏れに気付けるのは地味ながらうれしい(Swift 3.0と同時にこの対応がなされていればベストだったが……)。

Swift Package Manager(SwiftPM)の新機能を先取り

 Swift Package ManagerSwiftPM)という、アップル(Apple)公式のパッケージマネージャーにも大きめの変更がある。2016年12月7日にswift-build-devメーリングリストに「[swift-build-dev] Try out new SwiftPM features!」という投稿があり、DEVELOPMENT-SNAPSHOT-2016-12-07-aより、開発中の次の機能が使えるようになったことがアナウンスされた。以前は--enable-new-resolverフラグで明示的に指定したときの挙動だったものがデフォルトの挙動になった次第である。

  - Editable Packages - Documentation

  - Package Pinning - Documentation

 SwiftPMにはSwift 3.0の段階で実装できていない機能がまだまだたくさん残っていることがswift-build-devメーリングリストへの「[swift-build-dev] Swift Package Manager 3.0 Project Status」という投稿でも報告されていた。アップル公式というアドバンテージがあるので将来的にはiOSアプリ開発でのパッケージマネージャーの定番にもなっていくであろうが、まだまだ開発途中の段階なので、しばらくは今定番のCocoaPodsCarthageを利用する状態が続くだろう。ただ、サーバーサイドSwiftなどクロスプラットフォーム系のプロジェクトではすでにSwiftPMが活発に利用されている。

 SwiftPMについては開発が落ち着いたタイミングで紹介記事を書きたいと思っている。

まとめ

 前編と合わせて、

  • Swift 3.1~4の開発動向
  • Toolchains版を用いた開発最新版のSwiftの利用の仕方
  • Swift 3.1に含まれるであろう具体的な変更内容

を紹介した。Swift 3.0までは大きな機能追加や破壊的変更が続いていたが、3.1で行われるのは表面的には比較的地味に見えるアップデートになりそうである。3.0までに大きな変更を集中させた成果もあり、互換性担保や残った細かい改善に力を入れられるようになった、ともいえる。

小野 将之(おの まさゆき)

小野 将之(おの まさゆき)

学生時代から情報系の専攻、プログラミングのアルバイトなどでコンピューターに親しむ。

その後、大手SIerを経て、4年ほど前から複数のベンチャー企業でiOSアプリ開発をメインとするようになった。

SwiftはWWDC 2014年にベータ版が発表された直後から、ずっと触り続けている。

2015年からQiitaで多数の記事を書き、好評を集めている(http://qiita.com/mono0926)。

現在は株式会社Vikonaのエンジニアとして、JOIN USのiOS版アプリ開発に加えて、Ruby on RailsによるサーバーAPI開発もこなしている。

※以下では、本稿の前後を合わせて5回分(第2回~第6回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Build Insiderオピニオン:小野将之(6)
2. Swiftは3.0で安定するのか? リリース予定日と新機能リスト

2016年後半のリリースが予定されているSwift 3。そのリリースに先駆けて、どんな変更点があるのか、懸案となっている互換性はどうなるのかなどを見ていく。

Build Insiderオピニオン:小野将之(6)
3. Swiftの開発体制、swift.org/Swift Evolutionリポジトリとは?

次期Swiftに搭載予定の新機能といった最新情報はどこで入手できるのか。Swiftについての情報を常にキャッチアップするために見ておくべきサイトを紹介する。

Build Insiderオピニオン:小野将之(6)
4. Swift 3.0でなぜ「Cスタイルのforループ」「++/--演算子」などの仕様が廃止されたのか

大規模な破壊的変更が行われる最終的なバージョンといわれているSwift 3.0がついに正式リリース。多数の変更から「廃止」となった言語仕様にフォーカスを当て説明する。

Build Insiderオピニオン:小野将之(6)
5. Swift 3.1のリリースプロセスおよびそれに含まれる変更内容の紹介(前編)

Swift 3.1のリリースが2017年春に迫ってきた。今回は前後編に分けて、そのリリースプロセスや変更内容を解説する。前編ではリリースプロセス/互換性/開発版のSwiftを利用する方法を取り上げる。

Build Insiderオピニオン:小野将之(6)
6. 【現在、表示中】≫ Swift 3.1のリリースプロセスおよびそれに含まれる変更内容の紹介(後編)

Swift 3.1のリリースが2017年春に迫ってきた。今回は前後編に分けて、そのリリースプロセスや変更内容を解説する。後編ではSwift 3.1に取り込まれることが想定される変更点を取り上げる。

サイトからのお知らせ

Twitterでつぶやこう!