LoginSignup
3
2

More than 5 years have passed since last update.

Swift3対応でやったこと(また更新します)

Last updated at Posted at 2017-05-17

Swift3対応

最新のOSがもうXcode8.3以上じゃないとビルドできないし、検証端末をうっかりアップデートしてしまったので、泣く泣く対応することにした。Swift2.3でお茶を濁していたのがあかんかった。

うちのアプリはViewControllerの数が50個弱。containerになっているものもあるので、たいして多くはないけど変更料としては以下の通りで、間に仕事割り込まれたりしながら1週間弱かかった。

時間かかるしスイッチングが結構頭使うので時間とって上長に理解してもらって粛々と進めるべし。

スクリーンショット 2017-05-17 14.42.29.png

対応した流れ

  1. podの修正
  2. 自動コンバート
  3. エラーを逐次修正

になると思う。でやる時に結構大事になのが作業ディレクトリをコピーしておくこと。
超アナログだけどswift3作業は長いので、その途中で急にビルドしなければいけない時とかにcheckoutしてビルドし直すと時間のロスが大きい。gitで管理するよりより物理的に分けておけば当然ながら早い。

podfileを変更

swift3でビルドすることを明示

swift2.3
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
      config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
      config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
      config.build_settings['ENABLE_BITCODE'] = 'NO'
      config.build_settings['SWIFT_VERSION'] = "2.0"
      config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'NO'
    end
  end
end
swift3.0
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
      config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
      config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
      config.build_settings['ENABLE_BITCODE'] = 'NO'
      // ここの指定を変更する
      config.build_settings['SWIFT_VERSION'] = "3.0"
      config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'NO'
    end
  end
end

自動コンバート

これで pod install してXcode8.3系で開くとコンバートするか聞かれるので言われるがまま実施。この時ライブラリ群はチェックからはずしておく。

エラーを逐次修正

ここからが本当の戦い。

ライブラリをswift3用にする

変更が必要だったのはうちではこの辺のライブラリ。
それぞれswift3で動くバージョンを探してビルドしてを繰り返す。

swift3.0
 +  pod 'Alamofire', '4.0.1'
 +  pod 'CocoaLumberjack/Swift', '3.0.0'
 +  pod 'Himotoki', '3.0.1'
 +  pod 'ObjectMapper', '2.0.0'
 +  pod 'RxSwift',    '~> 3.0'
 +  pod 'RxCocoa',    '~> 3.0'
 +  pod 'SwiftDate', '4.0.13'
 +  pod 'RxDataSources','1.0'
 +  pod 'KeychainAccess', '3.0.0'
 +  pod 'RealmSwift','2.1.0'
 +  pod 'CryptoSwift','0.6.9'
 +  pod 'Firebase/Core'
 +  pod 'ReachabilitySwift', '~> 3'
 +  pod 'Ji', '~> 2.0.0'
 +  pod 'PagingMenuController', '1.4.0'
 +  pod 'SkyWay-iOS-SDK'
 +  pod 'JSQMessagesViewController'
 +  pod 'SwiftyJSON', '3.1.0'
 +  pod 'APIKit', '3.0.0'
 +  pod 'DynamicButton', '~> 4.0.0'
 +  pod "KeyboardObserver", '~>1.0.0'

大変だったのがAPIKitとFirebaseとRxSwift。

APIKit

諸事情でAPIKitを一部変更して使っていた。ただちゃんと調べるとやりたかったことをするインターフェースちゃんとあってほんと感謝しながらpodに変更。Himotokiと一緒に使っていたため、サンプルにあるように以下のような実装がRequestに求められる。

swift3.0
import Himotoki

extension GitHubRequest where Response: Decodable {
    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
        return try Response.decodeValue(object)
    }
}

この処理を共通化する必要があり、以下のように共通のRequest classを作り環境ごとに接続先を切り替える設定やレスポンスへの共通の処理を実装した。

swift3.0
enum APIBaseURL:String{
    case dev = "XXXXX"
    case stg =  "XXXXX"
    case prod = "XXXXX"
}

protocol RJRequestType : Request {

}

extension RJRequestType {
    var baseURL:URL {
        #if DEV1
            return URL(string: "http://\(APIBaseURL.dev1.rawValue)")!
        #elseif STAGING
            return URL(string: "https://\(APIBaseURL.stg.rawValue)")!
        #else
            return URL(string: "https://\(APIBaseURL.prod.rawValue)")!
        #endif
    }
}


extension RJRequestType where Response: Decodable {

    func interceptObject(_ object: AnyObject, URLResponse: HTTPURLResponse) throws -> AnyObject {
        switch URLResponse.statusCode {
        case 200:
            let err = RJAPIError(object: object)
            if err.errorCode != nil{
                throw err
            }
            return object

        default:
            throw ResponseError.unacceptableStatusCode(URLResponse.statusCode)
        }
    }
}

extension RJAuthRequestType where Response: Decodable  {
    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
        return try decodeValue(object)
    }
}

extension RJAuthRequestType where Response: Mappable{

    func response(from object: Any, urlResponse: HTTPURLResponse)-> Response? {
        guard let dictionary = object as? [String: AnyObject] else {
            return nil
        }

        let mapper = Mapper<Response>()
        guard let object = mapper.map(JSONObject: dictionary) else {
            return nil
        }

        return object
    }
}

これによってAPIKitで必要なRequestの共通処理を一箇所で記述した。 :metal:
感謝が止まらない。

Firebase

最初は以下を指定していたんだけどビルド時にエラーがでた。

swift2.3
pod 'Firebase','3.3.0'

ld: 89 duplicate symbols for architecture x86_64

見れみるとリンカエラーで他ライブラリと競合している可能性があるバージョンだったので、これはと思い以下に変更、無事ビルド :metal:

swift3.0
pod 'Firebase/Core'

GoogleのiOSライブラリはトラウマがある。昔あるライブラリが依存も何も見ずに動いててios8だと落ちるバグがあった。

RxSwift

厄介な変更点が多い。

swift2.3
rx_text
swift3.0
rx.text
swift2.3
.subscribeNext{}
swift3.0
.subscribe(onNext: { })

この辺はプロジェクト全体に対してreplaceをかける。もうほんとかけまくる。一つ修正が必要なのを見つけたらプロジェクト全体で似たようなコードを探してreplaceを繰り返す。心は殺す。

スクリーンショット 2017-05-17 15.17.51.png

あとは実装ミスのせいかもしれないが以下のように実装しているとsubscribeした時に値がnilもくるようになっていた。実装方法がいけなかったのかもしれないのでもうちょっと勉強する。

swift2.3
let validatedEmail = Variable<String>("")

NSのプレフィックスが消えたことへの対応

NSError, NSURL, NSData, NSDateが消えることとなった。
この辺はkumagai氏の資料が熱い

結論を引用すると

・積極的に使おう
・値型になっていることに注意
・ブリッジすると別インスタンスになる

この辺を注意してばんばんなくしていく :metal:

引数の変更によるエラーがわかりにくい

適切な引数を呼ばないとinvoke系のエラーに悩まされることになる。気をつけていこう。

swift2.3
    func setImageSmoothly(_ url: NSURL, placeholderImage: UIImage?) {
        sd_setImageWithURL(url, placeholderImage: placeholderImage) { [weak self] image, error, cacheType, imageUrl in
            if error != nil {
                return
            }
            if image != nil && cacheType == .None {
                self?.fadeIn(FadeType.Normal)
            }
        }
    }
swift3.0
    func setImageSmoothly(_ url: NSURL, placeholderImage: UIImage?) {
        sd_setImage(with: url as URL, placeholderImage: placeholderImage, options: []) { image, error, cacheType, imageUrl in
            if error != nil {
                return
            }
            if image != nil && cacheType == .none {
                self.fadeIn(FadeType.normal)
            }
        }
    }

nilの比較

swift2.3
object?.list.count > 2 

みたいなのがあるとnilに対して演算子が効かなくなっているので勝手にこういうのができる。

// FIXME: comparison operators with optionals were removed from the Swift Standard Libary.
// Consider refactoring the code to use the non-optional operators.
fileprivate func >= <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
  switch (lhs, rhs) {
  case let (l?, r?):
    return l >= r
  default:
    return !(lhs < rhs)
  }
}

ちゃんと消してoptional使えと言われるのでこの部分を消して、実装を治す

swift3.0
let object = object.list.count, object > 2 

その他

Swift2.2からSwift3.0への変換を行ってみて
の記事がとてもよくまとまっていて参考になりました。ありがとうございます。 :metal:

自動コンバートしてDeletegaメソッドの実装時に引数が間違ってて適切に呼ばれないこととかあったりして辛かった。

3
2
2

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
3
2