LoginSignup
19
17

More than 5 years have passed since last update.

Swift3対応をこれからする人が役立ちそうなこと!

Last updated at Posted at 2017-02-22

どうも、iOSエンジニアの原健太(@kEn_chaN_1024)です。

先日、Anny MagazineのiOSアプリをSwift3対応しました!
これから対応する方々の参考になればと思い、まとめました!

実装期間

1人で、他のタスクなども並行して対応していましたが、2〜3週間かかりました。

ライブラリのアップデート

使用しているライブラリをSwift3に対応したバージョンに上げる必要があります。
Swift3に対応していないライブラリも中にはありますので、ちゃんと確認してからSwift3対応に着手するのかを決めましょう。

Anny Magazineでは、ライブラリ管理をCarthageで行っているので、その時に大変だった事を書いていきます。

Alamofire(https://github.com/Alamofire/Alamofire)

Alamofireは以前は3系を使っていましたが、4系を使うことになり、APIが少々変更になりました。
こちらはAPIの本数分修正しなければいけなかったので、ライブラリに依存したコードを散在させない設計は大事だなと思いました。
SwiftyJSONも使っているので、以下のようなコードになりました。

before

Alamofire.request(Router.readCoupons(Api.user.userId)).validate().response { (request, response, data, error) -> Void in        
    if let error = error {
      // error handling
    } else {
      // success handling
    }
}

after

Alamofire.request(Router.createLikedDashProduct(userId: Api.user.userId, productId: productId)).validate().responseJSON { response in

    switch response.result {

    case .success(let data):
        let json = JSON(data)
        // success handling

    case .failure(let error):
        // error handling
}

SwiftValidator(https://github.com/jpotts18/SwiftValidator)

  • before : Validators.isEmail()(senderEmailAddress)
  • after : EmailRule().validate(senderEmailAddress)

Realm

  • before : Realm().objects(Receiver).first
  • after : Realm().objects(Receiver.self).first

Google Analytics

値を返すメソッドには@discardableResultを明示しない限り、受け皿が必要になりました。

  • before gai?.tracker(withTrackingId: Configuration.gaTrackingId)
  • after _ = gai?.tracker(withTrackingId: Configuration.gaTrackingId)

NSMutableDictionaryを[AnyHashable: Any]にキャスト

トラッキングするには、NSMutableDictionary[AnyHashable: Any]にキャストしなければいけませんが、直接ではいけないので、
NSMutableDictionary -> NSDictionary -> [AnyHashable: Any]
という順番でキャストしています、、、

let events = (builder.build() as NSDictionary) as? [AnyHashable : Any]
tracker?.send(events)

Eureka(https://github.com/xmartlabs/Eureka)

  • before: $0.addRule(RuleRequired())
  • after: $0.add(rule: RuleRequired())

carthage updateは--no-use-binariesのオプションを

ライブラリ管理にCarthageを用いている場合は、update後に
Module compiled with Swift 3.0 cannot be imported in Swift 3.0.2

のようなエラーが出る場合があります。これは、引っ張ってきたバイナリデータがSwift3.0でコンパイルされている事によるエラーなので、
--no-use-binariesのオプションを付けて、バイナリデータを引っ張ってこない事を明示しなければいけません。

また、その度に全てのライブラリをアップデートする必要はなく、以下のようにこのようなエラーが出たライブラリだけに対してcarthage updateすることも出来ます。

carthage update --platform iOS --no-use-binaries SwiftyJSON

Xcode Converter

Xcodeで、
Edit -> Convert -> To Current Swift Syntax
と選択すると、自動でSwift3への変換をある程度行ってくれます。
こちら、一回だけだと漏れている部分が多々ありますが、2 - 3回行うとほぼ正確に全て変換してくれました。

メソッドの定義の変更

上記の自動コンバートで、メソッドの第一引数のラベルが省略されてしまいました、、、
こんな感じです。

before

  • 呼ばれる側:func add(num1: Int, num2: Int)
  • 呼ぶ側:add(num1: 1, num2: 3)

after

  • 呼ばれる側:func add(_ num1: Int, num2: Int)
  • 呼ぶ側:add(1, num2: 3)

僕は、第一引数もラベルを省略しない書き方が好きだったので、一括変換などで、元に戻すのを少し頑張ったのですが、最終的には多すぎて心が折れました、、、
なので、今は全てのメソッドが第一引数のラベルが省略された形になっています、、、(泣)

NSErrorをErrorに変更

NSErrorをErrorに変更すると、従来使えていたuserInfoなどが使えなくなりステータスコードなどが取得できなくなったので以下のような修正をしました。

以前はこんな感じでステータスコードを判断していましたが、

if let statusCode = error.userInfo["StatusCode"] as? Int {
   if case .forbidden = error {                   
     if statusCode == 403 {
     }
   }
}

こんな感じのApiError作った

enum ApiError: Error {

    case unauthorized
    case forbidden
    case notFound
    case serverError
    case others

    init?(statusCode: Int?) {

        guard let statusCode = statusCode else { return nil }

        switch statusCode {
        case 401:
            self = .unauthorized

        case 403:
            self = .forbidden

        case 404:
            self = .notFound

        case 500...599:
            self = .serverError

        default:
            self = .others
        }
    }
}

Fix All in scopeコマンドがある

ポチって押すと自動で直るワーニングあるじゃないですか??
黄色い三角の中に丸があるやつ。
ファイル中のそのワーニングを一気に直す、Fix All in Scopeっていうコマンドがあります。
XcodeのEditor -> Fix All in Scope(control+option+command+F)
で実行できます。

が、asのキャストとか無駄に入るので要注意です。

selectorの書き方

以前はこんな感じでも成り立っていたんですが、ちゃんと引数書かなきゃいけなくなった

これが、

pushedButton(sender: AnyObject) { ... }
  • before : #selector(ViewController.pushedButton(_:))
  • after : #selector(ViewController.pushedButton(sender:))

ちょこちょこClosureの引数の型が変わってる

UIActivityViewControllerとか、CSSearchableIndexとかのクロージャの引数の型がちょこちょこ変わったりしていて、Xcodeの自動Convertでは変わらなかったので,手動で変更する必要があります。

ジェネリクスメソッド

以前はこのようなコードが動いていました。ジェネリクスメソッドに対して、定数初期化の時の型指定で型推論してくれていたんですが、してくれず、Errorをはくようになりました。
ジェネリクスで共通化していた部分をばらして、普通に堅い型として記述したんですが、そうするしか無かったのでしょうか、、??

fileprivate func configureInformationCell<T: InformationCellType>(_ collectionView: UICollectionView, indexPath: IndexPath) -> T where T: UICollectionViewCell {
}

let cell: InformationListCollectionViewCell = configureInformationCell(collectionView: collectionView, indexPath: indexPath)

返り値を返すメソッドには受け皿が必要になった

こちらはGoogle Analyticsの時にも書いたのですが、@discardableResultを明記しない限り、返り値があるメソッドにはその受け皿が必要になりました。

self?.navigationController?.popViewController(animated: true)
_ = self?.navigationController?.popViewController(animated: true)

if else endif

#if / #else / #endif の間に挟まれているコードはマイグレーションツールに無視されて、 Swift 2 のままです。なので、手動で直す必要があります。

Travis CIでのビルドがこける

こちらはノウハウというか現状抱えている課題なのですが、Travis CIのBuildがコケるようになりました。
Build PhasesのCarthageのRun Scriptでタイムアウトしてしまいます。

まとめ

正直めちゃくちゃストレスフルな根気のいる作業でした。
Apple、やってくれたな!!!!
Swift4からは互換性を持たすと言っていますが、本当でしょうか。
Swift3対応しているiOSエンジニアがいる組織では、ぜひその人を励ましてあげてほしいです。
そして、そんな苦しんでいるiOSエンジニアの手助けに少しでもなれば幸いです。

19
17
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
19
17