iQONをSwift 3.0.1にアップデートしたときに対応したポイント


> この記事は[VASILY DEVELOPERS BLOG](http://tech.vasily.jp/entry/update_swift_3_0_1)にも同じ内容で投稿しています。よろしければ他の記事もご覧ください。

iOS 10.1 のリリースから遅れること3日、Xcode 8.1 がリリースされました。この Xcode 8.1 では Swift のバージョンが 3.0.1 にアップデートされています。
iQON の iOS アプリでは、Xcode 8 リリース後すぐに Swift 2.3 へのアップデートは済ませたのですが、最近 Swift のバージョンを 2.3 → 3.0.1 にアップデートしました。

本記事は、作業中に対応したエラー修正の記録のようなものです。とても長くなっていますが、Swift 2系 → 3系にアップデートするときの手助けになればと思います。


# モチベーション
現在も引き続きSwift 2.3 で開発を続けることはできますが、いずれは Swift 3.x 系へアップデートすることになるでしょう。
一方、 [Realm](https://realm.io/jp/news/realm-objc-swift-1.1.0/) や [RxSwift](https://github.com/ReactiveX/RxSwift#requirements) などのメジャーなライブラリは続々と Swift 3.0 の正式対応版をリリースしています。
このまま Swift 2.3 で開発を続けた場合、アップデート時の作業が増えます。
さらに、各OSSライブラリも今後のアップデートは Swift 3.x 系のみということもあるでしょう。

iQONでは、使用しているライブラリが全て Swift 3.0 対応版がリリースされたため、 iQON でも Swift 3.0.1 へのアップデートに踏み切りました。


# 前提条件
本記事の Swift 3.0.1 アップデート作業は下記の環境においてのものです。

- MacBook Pro (Retina, 15-inch, Mid 2015)
- macOS Sierra 10.12.1
- Swift (2.3 → 3.0.1)
- Xcode (8.0 → 8.1)
- Carthage 0.18.1
- CocoaPods 1.0.1
- サポートiOSバージョン : 9.0 以降
- Objective-C / Swift のハイブリッド (比率は 40% : 60%)
- iPhoneのみ (not universal)


# 移行作業

大まかな流れは下記のようになっています。

- Xcode 8 のコンバーターで Swift 2.3 の文法を 3.0.1 式に一括置換
- CocoaPods, Carthage で管理するライブラリを Swift 3.0.1 に対応したバージョンに載せ替える
- ビルド時にXcodeに表示されるのエラーを取り除く 
〜ここでやっとビルドが通る〜

- Xcodeに表示されるのワーニングを取り除く - 表示崩れなどのバグ修正 & テストを繰り返す


## Swift 2.3 → 3.0.1 のコンバート

Xcode画面上部メニューの Edit → Convert → To Current Swift Syntax...

ライブラリの部分は後ほど対応するので、自分達で管理している部分だけチェックを入れてコンバートを実行します。
私の環境では、コンバートに10〜15分程度かかったと思います。

![20161101163654.png](https://qiita-image-store.s3.amazonaws.com/0/11454/50bae03b-54a9-85ca-3899-21a721ae942c.png "20161101163654.png")


<br />

## Carthage

`Cartfile` 内の各ライブラリのバージョンを Swift 3.0 に対応したものに変更して、 `carthage update` するだけです。


<br />


## CocoaPods

各ライブラリの Build Settings で Swift 3.0.1 を使うようにするために、Podfile に下記のような設定を追加します。

```ruby
swift_version = '3.0.1'
```

<br />

## 各ライブラリ

公式に Swift 3.0 移行ガイドを用意してくれているものもあるので、まずは公式情報を確認しましょう。
例えば [APIKit](https://github.com/ishkawa/APIKit) では、丁寧な移行ガイドがありました。
[APIKit 3 Migration Guide](https://github.com/ishkawa/APIKit/blob/master/Documentation/APIKit3MigrationGuide.md)


<br />


## コンパイルエラー対応

発生した多数のエラーとその対応方法を紹介します。

### [Error] String(describing:)

クラス名文字列を取得する処理

#### Swift 2.3

```
// Swift 2.3
String(SomeClass)
```


#### Xcode 8でコンバート後

```
String(describing: SomeClass)  // コンパイルエラー
```


#### Swift 3.0.1 で正しくは

```swift
String(describing: SomeClass.self)
```

<br />


### [Error] Implicitly Unwrapped Optional が廃止になった影響

Objective-Cで、`nonnull`, `nullable` の指定がないメソッドやプロパティの型は、Swift 2.3 から扱うとき、Implicitly Unwrapped Optional (IUO) として `!` が付いていました。
Swift 3.0 以降では IUO は廃止され、Optional の派生型として扱われます。

#### Objective-C

```objc
typedef void (^ViewHandler)(UIView *);
```

#### Swift 2.3

```swift
let handler: ViewHanlder = { (view: UIView!) in
    view.alpha = 0.5
}
```

#### Xcode 8でコンバート後

Swift 3.0.1 では、クロージャーの引数の型が Optional になります。

```swift
let handler: ViewHanlder = { (view: UIView?) in
    view.alpha = 0.5    // コンパイルエラー。viewはOptionalなので直接alphaにはさわれません
}
```

#### Swift 3.0.1 で正しくは

```swift
// Swift 3.0
let handler: ViewHanlder = { (view: UIView?) in
    view?.alpha = 0.5
}
```

#### ドキュメント
[[SE-0054] Abolish ImplicitlyUnwrappedOptional type](https://github.com/apple/swift-evolution/blob/master/proposals/0054-abolish-iuo.md)


<br />


### [Error] NSErrorのプロパティにアクセスできない

Objective-C のメソッドで、コールバックのクロージャーに`NSError`を返すメソッドがありました。
Swift 3では`NSError`が`Error`に変換されるため、`NSError` クラスのプロパティが参照できません。

#### Swift 2.3

```swift
failure: { (error: NSError!) in
    print("domain: \(error.domain) / code: \(error.code)")
}
```

#### Xcode 8でコンバート後

```swift
failure: { (error: Error) in
    print("domain: \(error.domain) / code: \(error.code)")    // コンパイルエラー。NSErrorのプロパティにアクセスできない
}
```

#### Swift 3.0.1 で正しくは

```swift
failure: { (error: Error) in
    let nserror = error as NSError
    print("domain: \(nserror.domain) / code: \(nserror.code)")
}
```


<br />


### [Error] プロトコルに適合したのメソッド定義がエラーになってしまう

#### Swift 2.3

下記のコードでも特に問題はありません

```swift
class BrandPanelListDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
    ...
}

extension BrandPanelListDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
```


#### Xcode 8 でコンバート後

```
Objective-C method 'tableView:cellForRowAt:' provided by method 'tableView(_:cellForRowAt:)' does not match the requirement's selector ('tableView:cellForRowAtIndexPath:')
```
というエラーが発生。

```swift
class BrandPanelListDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
    ...
}

extension BrandPanelListDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {  // コンパイルエラー
```

#### Swift 3.0.1 で正しくは

Xcode のエラーメッセージをクリックすると自動で`@objc(...)`が挿入されて、エラーはなくなります。

```swift
class BrandPanelListDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
    ...
}

extension BrandPanelListDataSource {
    @objc(tableView:cellForRowAtIndexPath:) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
```


もしくは、 `extension` ごとにプロトコルを記述しても修正できます。
こちらの方が `@objc(...)` の記述が不要なうえ、コードの見通しがよくなると思います。


```swift
class BrandPanelListDataSource: NSObject {
    ...
}

extension BrandPanelListDataSource: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        ...


extension BrandPanelListDataSource: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        ....
```


<br />


### [Error] CGContextSetLineDash

点線を描くための `CoreGraphics` のメソッドが変更されていました。

#### Swift 2.3

```swift
CGContextSetLineDash(context, 0.0, [4], 1)
```

#### Xcode 8 でコンバート後

```
'CGContextSetLineDash' is unavailable: Use setLineDash(self:phase:lengths:)
```

というエラーが発生します。


#### Swift 3.0.1 で正しくは

`CGContext`のインスタンスメソッドを使います。

```swift
context.setLineDash(phase: 0.0, length: [4.0])
```


<br />


### [Error] enumerated() の要素のキーワードが変更

`enumerated()`の要素にアクセスする際のキーワードが `index` → `offset` に変更になりました。

#### Swift 2.3

```swift
objects
    .enumerate()
    .forEach { print($0.index) }
```

#### Xcode 8 でコンバート後

```
Value of tuple type '(offset: Int, element: Any)' has no member 'index'
```
というエラーが発生します。

```swift objects
    .enumerated()
    .forEach { print($0.index) }     // コンパイルエラー
```


#### Swift 3.0.1 で正しくは

`enumerated()` (`EnumeratedSequence<Array<Element>>`) の各要素は `offset` でアクセスできます

```swift
objects
    .enumerated()
    .forEach { print($0.offset) }
```


#### ドキュメント
[Migrating to Swift 2.3 or Swift 3 from Swift 2.2](https://swift.org/migration-guide/)

> Users may need to manually rename the tuple element index to offset when accessing the result of Collection.enumerated()


<br />


### [Error] コンバーターのキャスト漏れ

`String` の変数に `NSString` を代入するときなどに明示的なキャストが必要になりました。

#### Swift 2.3

```swift
var parameters: [String: AnyObject] = [
    "key1": "apple",
    "key2": "orange",
    ]
parameters["key3"] = (someBool ? "banana" : "grape")
```

#### Xcode 8 でコンバート後

`parameters` の値の型に合わせて `as AnyObject` を差し込んでくれますが、中途半端です。

```swift
var parameters: [String: AnyObject] = [
    "key1": "apple" as AnyObject,
    "key2": "orange" as AnyObject,
    ]
parameters["key3"] = (someBool ? "banana" : "grape" as AnyObject)  // as AnyObject は "grape" にしかかかっていないのでエラー
```

```
Cannot convert value of type 'String' to expected argument type 'AnyObject'
```

というエラーになります。


#### Swift 3.0.1 で正しくは

`as AnyObject`のかかる範囲を変更します。

```swift
let parameters: [String: AnyObject] = [
    "key1": "apple" as AnyObject,
    "key2": "orange" as AnyObject,
    ]
parameters["key3"] = (someBool ? "banana" : "grape") as AnyObject
```

これでも良いのですが、多くのコードに `as AnyObject` が入ってしまい、可読性が著しく下がります。
`parameters` の型を `[String: Any]` に変更すると、 `as AnyObject` へのキャストが不要になります。

```swift
let parameters: [String: Any] = [
    "key1": "apple",
    "key2": "orange",
    ]
parameters["key3"] = (someBool ? "banana" : "grape")
```

#### ドキュメント
[[SE-0072] Fully eliminate implicit bridging conversions from Swift - swift-evolution](https://github.com/apple/swift-evolution/blob/master/proposals/0072-eliminate-implicit-bridging-conversions.md)


<br />


### [Error] コンバーターが `@escaping` を付けてくれない

『クロージャーを引数に持つクロージャー』を引数に持つメソッドにおいて、コンバーターが自動で `@escaping` を付けてくれない事がありました。
(コールバック地獄だということはさておき…)

`@escaping` については、弊社ニコラスのブログも参考にしてみてください。

[Swift 3の変更点の裏側 (アクセス制御 / @escaping) - VASILY DEVELOPERS BLOG](http://tech.vasily.jp/entry/swift_evolution_escape)


#### Swift 2.3

```swift
private typealias CompletionType = Void -> Void

...

private func runInBackground(task: CompletionType -> Void) {
```

#### Xcode 8 でコンバート後

```swift
fileprivate typealias CompletionType = () -> Void

...

private func runInBackground(_ task: @escaping (CompletionType) -> Void) {
```

`CompletionType` のクロージャーも非同期で呼ばれるため、`@escaping` が必要ですが、コンバーターは`@escaping`を付けてくれません。


そして、 `CompletionType` を実行するコードでは、下記のようなエラーが発生します。

```
Closure use of non-escaping parameter 'completeTask' may allow it to escape
```

![20161101163625.png](https://qiita-image-store.s3.amazonaws.com/0/11454/438e1c16-4a8c-3f40-8054-6016866dffde.png "20161101163625.png")


#### Swift 3.0.1 で正しくは

`typealias` 宣言時に `@escaping` をつけるのはエラー

```swift
// Error: "@escaping may only be applied to parameters of function type"
fileprivate typealias CompletionType = @escaping () -> Void
```


メソッド宣言時に `@escaping` を書くのが正しいようです。

```swift
fileprivate typealias CompletionType = () -> Void

...

fileprivate func runInBackground(_ task: @escaping (@escaping CompletionType) -> Void) {
```


#### ドキュメント
[[SE-0103] Make non-escaping closures the default](https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md)


<br />


### [Error] UIntのプロパティには数字を直接代入できない

UInt型のプロパティに直接数値リテラルを代入できなくなりました。

#### Swift 2.3

```swift
// maxCacheSize は UInt型
SDImageCache.shared().maxCacheSize = 1024 * 1024 * 512    // 何も問題ない
```

#### Xcode 8 でコンバート後

```swift
SDImageCache.shared().maxCacheSize = 1024 * 1024 * 512    // エラー
```

`Cannot assign value of type 'Int' to type 'UInt'` というエラーが発生します


#### Swift 3.0.1 で正しくは

明示的に `UInt` のコンストラクタを使うか、 `as UInt` でキャストする必要があります。

```swift
SDImageCache.shared().maxCacheSize = UInt(1024 * 1024 * 512)
SDImageCache.shared().maxCacheSize = 1024 * 1024 * 512 as UInt
```

#### ドキュメント
[[SE-0072] Fully eliminate implicit bridging conversions from Swift - swift-evolution](https://github.com/apple/swift-evolution/blob/master/proposals/0072-eliminate-implicit-bridging-conversions.md)


<br />


## ワーニング対応

発生したワーニングとその対応方法を紹介します。

### [Warning] DispatchQueue.GlobalQueuePriority が deprecated

Swift 3.0 から GCD の記述方法が変更になりました。
Xcode 8 のコンバーターが自動で変換してくれますが、変換されたものが deprecated でした。

#### Swift 2.3

```
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
}
```

#### Xcode 8でコンバート後

```
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async {    // ワーニング
}
```

しかし、下記のように2つのワーニングが発生します。

```
`default` was deprecated in iOS 8.0: Use qos attributes instead
```

```
`global`(priority:)` was deprecated in iOS 8.0
```


#### Swift 3.0.1 で正しくは

```
DispatchQueue.global(qos: .default).async {
}

// .defaultの場合、引数は省略可能
DispatchQueue.global().async {
}
```


<br />


### [Warning] 戻り値があるメソッドの戻り値を使っていない場合はワーニングになる

#### Swift 2.3

```swift
navigationController?.popViewControllerAnimated(true)
```

#### Xcode 8 でコンバート後

`Expression of type 'UIViewController?' is unused` というワーニングが発生する

```swift
navigationController?.popViewController(animated: true)
```

#### Swift 3.0.1 で正しくは

明示的に戻り値を使わないように書きます。

```swift
_ = navigationController?.popViewController(animated: true)
```

#### ドキュメント
[[SE-0047] Defaulting non-Void functions so they warn on unused results](https://github.com/apple/swift-evolution/blob/master/proposals/0047-nonvoid-warn.md)


<br />


## その他特に苦労した話

### Objective-C クラスのプロパティを文字列に組み込むときの不具合

IUO が廃止されたことで、 Objective-C のモデルクラスが持つプロパティを Swift 3.0 の String リテラル( `""` ) に含めると "Optional(1)" のような文字列になってしまいます。
iQONでは、APIのURL文字列でこの問題が多く発生したため、多くのリクエスト処理がエラーになってしまい、対応に苦労しました。

#### Objective-C

```objc
@interface User
@property (strong, nonatomic) NSString *name;
@end
```

#### Swift 2.3

```swift
user.name = "Bob"
print("name: \(user.name)")    // name: Bob
```

#### Xcode 8でコンバート後

IUO (`!`) は Optional の派生型なので、文字列リテラルに組み込むと "Optional" と表示されてしまいます。

```swift
user.name = "Bob"
print("name: \(user.name)")    // name: Optional("Bob")
```

#### Swift 3.0.1 で正しくは

Optional変数として `if let` / `guard let` で明示的にアンラップして変数を扱います。

```swift
user.name = "Bob"
if let name = user.name {
    print("name: \(name)")    // name: Bob
}
```

#### ドキュメント
[[SE-0054] Abolish ImplicitlyUnwrappedOptional type](https://github.com/apple/swift-evolution/blob/master/proposals/0054-abolish-iuo.md)


<br />


### APIKit のリクエストパラメータ設定処理

iQONではAPIの通信処理に [APIKit](https://github.com/ishkawa/APIKit) を使用しています。
Swift 3.0 に対応した APIKit 3.0.0 から リクエストパラメータを指定するための計算型プロパティの型が変更になりました。

```swift
public protocol Request {

// APIKit 2.x
var parameters: AnyObject? {

// APIKit 3.x
var parameters: Any? {
```

リクエストするURLごとにこの計算型プロパティを実装するのですが、 Xcode 8 のコンバーターはこれを修正してくれないため、自分で対応するしかありません。
この計算型プロパティの型を変更しないと、 `Request` protocol の `parameters` は実装されていないことになるため、通信時にパラメータが無いことになってしまいます。

この変更に気付かなかったため、アイテム検索のページで検索条件を変更しても何も結果が変わらず、原因がわかるまでかなり苦労しました。


<br />


### Optional の 大小比較 `<` , `>`

下記のような Optional の値の大小比較が Swift 3.0 ではできなくなりました。

```swift
let a: Int? = nil
let b: Int? = 4

print(a < b) // true
```

そのため、Xcode 8のコンバーターは **Optional の大小比較をするコードが存在するファイルごとに** 、下記のような不等号演算子を実装するコードを挿入します。

```swift
fileprivate func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
  switch (lhs, rhs) {
  case let (l?, r?):
    return l < r
  case (nil, _?):
    return true
  default:
    return false
  }
}

fileprivate func > <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
  switch (lhs, rhs) {
  case let (l?, r?):
    return l > r
  default:
    return rhs < lhs
  }
}
```

この演算子定義のおかげで既存のコードは同じように動作しますが、ファイルごとに宣言されているのは気持ちよくないですし、デフォルトの挙動が変わったのなら、 Optional の大小比較自体をやめるべきだと思います。

```swift
let a: Int? = nil
let b: Int? = 4

let result: Bool
if let a = a, let b = b {
    result = a < b
} else {
    result = false
}
print(result)   // false
```

#### ドキュメント
[[SE-0121] Remove Optional Comparison Operators](https://github.com/apple/swift-evolution/blob/master/proposals/0121-remove-optional-comparison-operators.md)


<br />


### private → fileprivate

Xcode 8 のコンバーターは `private`, `fileprivate` の使い分けが一切ないまますべて `fileprivate` に変換してしまいます
どこかのタイミングでスコープが小さい物は `private` に変更する予定です。


<br />


### プッシュ通知用のDevice Token

Swift 3.0 にアップデートしてから、プッシュ通知用のDevice Tokenが今までの方法では取得できなくなってしまいました。

#### Swift 2.3

```swift
let deviceTokenString = deviceToken
    .description
    .stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "<>"))
    .stringByReplacingOccurrencesOfString(" ", withString: "")
// "a8f1ef4e27181279f3b60e2db043f1409211faf6b24382e1a0a1223b797fbc79" のような64文字の文字列
```

#### Xcode 8でコンバート後

`Data` の `description` のレスポンスが変更されたため、必ず `32bytes` という文字列になってしまいます。

```swift
let token: String = deviceToken
    .description
    .trimmingCharacters(in: CharacterSet(charactersIn: "<>"))
    .replacingOccurrences(of: " ", with: "")
// "32bytes"
```

#### Swift 3.0.1 で正しくは

```swift
let token: String = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
// "a8f1ef4e27181279f3b60e2db043f1409211faf6b24382e1a0a1223b797fbc79" のような64文字の文字列
```

#### ドキュメント
こちらのQiitaの記事が参考になりました。
[Swift 3.0でのプッシュ通知用のDevice TokenのData型から16進数文字列への変換方法](http://qiita.com/mono0926/items/3cf0dca3029f32f54a09)


<br />


# 最後に
今回は、Swift 3.0.1 にアップデートする際の対応方法を紹介しました。
これらの対応方法はあくまで、iQONで発生したエラーの対応方法ですので、そのほかのエラーについては、 [swift-evolution](https://github.com/apple/swift-evolution) を参考にするのが良いです。

本記事がSwift 3系へアップデートする際の参考になれば幸いです。