プロジェクトをXcode 8/Swift 3に変換した時の自動変換結果から変更点をチェックする

Xcode 8.0が正式に出ました。あるアプリケーションのプロジェクトをコンバートした時に自動的に書き換えられた結果をもとにどのような変更があったのかを記録しておきます。なお、このプロジェクトは、Swift 2.3の時に、Swift 3でdeprecatedになるという警告の箇所は、警告が出ないように変更した結果です。それ以前のバージョンのSwiftで作られたソースでは、もっと様々な変更の必要があると思います。まず、ターゲットは、iOS 9.3のままの状態でコンパイルが通るようにしてみました。

クラスそのものが変更されたもの

以下のクラスについて、名前が変更されています。矢印の左側が、Xcode 7.x(iOS 9.3)の場合の記述で、矢印の右側の記述に自動的に変換されたことを示しています。NSの付いたクラス名が付いていないものに変わってきています。おなじみのNSURLやNSErrorが、配列や文字列ど同様NSのないクラス名に変更されています。クラスの機能については大きくは変わっていないようです。NSURLSession及びその名前で始まるNSURLSessionTaskも含めて、頭の「NS」はなくなっていますが、今日現在、ドキュメンテーション中に「NSURLSession」が残っていたりします。最後のURLSession.AuthChallengeDispositionは列挙型です。

  • NSUserDefaults→UserDefaults
  • NSError→Error
  • NSURL→URL
  • NSBundle→Bundle
  • NSURLSession→URLSession
  • NSURLCredential→URLCredential
  • NSURLAuthenticationChallenge→URLAuthenticationChallenge
  • NSURLSessionAuthChallengeDisposition→URLSession.AuthChallengeDisposition
  • AnyObject→Any

インスタンスを得るメソッドがシンプルなプロパティに

「UIApplication.sharedApplication()」などは頻繁に記述していたのですが、Applicationという単語の重なりがなんか重い感じがしていましたが、「UIApplication.shared」のように、長いスタティックメソッドが短いプロパティに変更されました。コードがかなりスッキリします。以下のリストの最後から2つ目にあるにあるDipatchQueueクラスは、iOS 10から搭載されたクラスで、dispatchで始まるGrand Central Dispatch関連のAPIをクラスにラップしたものです。このクラスのドキュメントはまだ完全に埋まっていませんが、クラスはiOS 10からなのにメソッドのsync(execute:)のようにiOS 4から関数としてサポートしているものもあり、要するにやっとクラスとして使えるようにAPIを整えたという状況のようです。最後のUIColorクラスは、いくつかの色のオブジェクトを得るスタティックメソッドが用意されていましたが、これもColorの重複があったものの、redやgreen、blackといったプロパティで得られるようになりました。

  • UIApplication.sharedApplication()→UIApplication.shared
  • NSBundle.mainBundle()→Bundle.main
  • standardUserDefaults()→standard
  • UIScreen.mainScreen()→UIScreen.main
  • dispatch_get_main_queue()→DipatchQueue.main
  • UIColor.redColor()→UIColor.red

アクセス修飾子

従来の3種類から、open、public、internal、fileprivate、privateの5段階に変更されました。openとfileprivateがSwift 3より導入されたものです。従来のprivateは、全部fileprivateに変更されてしまいます。省略時にinternalになるのは変更ありません。
Swift 2.xではprivateによるアクセス制限はクラス単位ではなくファイル単位でした。したがって、1ファイルに2つのクラスを定義した場合、privateなプロパティでも別のクラスからアクセスができました。Swift 3ではファイル単位でのfileprivateと、クラス単位のprivateに分離しました。従来のprivateが現在のfileprivateになったということで、こうした変換を行なったのでしょう。
一方、publicとopenについては、元のpublicは現在はopenに対応します。いずれも、モジュール外からアクセス可能にするための修飾子ですが、Swift 3ではpublicはサブクラス化やoverrideできないという制約が付きます。

オプショナルバインディングの書き方

Swift 2位の時に、letの後にカンマで区切って複数の代入文が書けるようになり、ifを多重にネストしなくてもよくなったのですが、Swift 3ではカンマで区切ったそれぞれの代入文にletが必要になりました。以下の、let b = yのletは今までは不要だったのですが、これが入るようになります。

var x: String?
var y: String?
if let a = x, let b = y {
   print(a,b)
}

UIKitのAPIの変更

メソッド名やプロパティ名などがあちらこちらで変わります。ただし、総じて読みやすくはなります。例えば、UIViewControllerのshowViewController(_:sender:)、dismissViewControllerAnimated(_:completion:)、presentViewController(_:animated:completion:)の各メソッドが、show(_:sender:)、dismiss(animated:completion:)present(_:animated:completion:)へと極めて短くなりました。また、最初の引数にはキーワードをつけないということで統一されていて、最初のキーワードは関数名に組み込まれていましたが、状況によっては引数のキーワードとして使うようになりました。なので、dismissのように通常のメソッドでも、最初の引数にキーワードが付くことがあります。一方、UINavigationControllerクラスではpopViewControllerAnimated()がpopViewController(animated:)にはなるなど変化はあるものの、大きな変化のないクラスもあります。

UIViewクラスのhidden、userInteractionEnabledプロパティはisHidden、isUserInteractionEnableとなりました。Boolean型のプロパティのインタフェースを「is+属性名」としたということで、この部分はJavaなどのルールに即したということでしょう。また、プロパティ名にあった「URL」はほぼ「url」で置き換えられており、URLで始まるプロパティもurlで始まるようになっています。定数についても、以前は.Badgeのように頭文字は大文字でしたが、.bridgeのようにドットの次は小文字になりました。たまたま、Objective-Cで作ったクラスを入れていたのですが、そのクラスの定数も同様に頭文字が小文字になったので、この処理はブリッジ部分での変更であることが確かです。結果的に、クラス以外の頭文字は小文字というルールが浸透した感じです。

さらに、メソッドだったものもプロパティとして扱えるものは、プロパティになりました。UIViewControllerクラスでは、supportedInterfaceOrientationsメソッドがあり、それをオーバーライドして返り値をプログラムで記述することで、自分で作っているビューコントローラでの挙動を変更できました。これが、supportedInterfaceOrientationsプロパティに変わります。ただし、その後は、{…} で値を返すように記述することで、プログラムがゲッターとして機能します。つまり、var プロパティ名 : クラス に続いて { get { } set { } } と記述するのがセッターやゲッターの基本ですが、ゲッターだけなら、{ …..; return xx; } のように引数指定なしのクロージャのように記述するだけで変わりません。従って、メソッドからプロパティに変わっても、{ } 内は同じです。

func supportedInterfaceOrientations()->UIInterfaceOrientationMask // Swift 2.x
var supportedInterfaceOrientations:UIInterfaceOrientationMask // Swift 3

関数を定義する時、最初の引数にキーワードがないとき、単に記述しないで済みましたが、Swift 3では _ の記述が必要になりました。

@IBAction func tapRegistering(sender: UIButton) { // Swift 2.x
@IBAction func tapRegistering(_ sender: UIButton) { // Swift 3

@escapingが追加されるクロージャーの引数定義

WKWebViewのデリゲートメソッドの部分では、引数のクロージャーを持つものがありますが、メソッド定義の記述に@escapingが加わりました。Swift 2.3(iOS 9)ではこの@escapingはない状態で定義されていました。このアノテーションを追加することで、引数に設定されたクロージャーがメソッド実行後に消えてなくなってしないように保持をします。このクロージャー自体をlazyで指定したプロパティで使う場合や、あるいは配列にlazyプロパティを指定してさらにmapメソッドを適用することで、mapメソッドのクロージャーの適用を配列の要素を取得する時点で行うことができるのですが、そのmapメソッドの引数にクロージャーを指定するような場合に、@escapingで保持を指定するということになっています。

func webView(_ webView: WKWebView,
 decidePolicyFor navigationResponse: WKNavigationResponse,
 decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {

ちなみに、iOS 10/Xcode 8とはあまり関係ありませんが、配列に対するlazyプロパティについてはブログ等でほとんど見られないので、ちょっと試して見ました。サンプルプログラムを見てください。配列mに対して、mapメソッドを直接ではなく、lazy.map()で適用します。nはprintすると、「LazyMapRandomAccessCollection<Array<Int>, Int>(_base: [1, 2, 3], _transform: (Function))」と出てくるように、特殊なコレクションです。このnをsubscriptによりアクセスすると、そのアクセスした時にmapメソッドを実行して結果を返します。kの値は最初は100ですが、lazy.map()の後に、kの値を変えて配列の要素にアクセスすると、その時のkの値が適用されて、201 202という値が得られます。まさに、メソッドの適用をlazyにするという仕組みと言えるでしょう。

var m = [1,2,3]
var k = 100
let n = m.lazy.map({s in return k + s})
print(n[0]) // 101 と出力
k = 200
print(n[0], n[1]) // 201 202 と出力

このプロジェクトはすでにSwift 2.3の段階で警告も含めてクリアしているので、++演算やそれを使ったfotなどの警告となっていた記述は一切ない状態だったので、それほど時間がかからずに変更はできました。こうして違いが出たところは概ね自動的に変更してくれるので、それはそれでいいのですが、仔細に見ることで色々と新しい機能も理解できるようになります。