Swift3対応
最新のOSがもうXcode8.3以上じゃないとビルドできないし、検証端末をうっかりアップデートしてしまったので、泣く泣く対応することにした。Swift2.3でお茶を濁していたのがあかんかった。
うちのアプリはViewControllerの数が50個弱。containerになっているものもあるので、たいして多くはないけど変更料としては以下の通りで、間に仕事割り込まれたりしながら1週間弱かかった。
時間かかるしスイッチングが結構頭使うので時間とって上長に理解してもらって粛々と進めるべし。

対応した流れ
- podの修正
- 自動コンバート
- エラーを逐次修正
になると思う。でやる時に結構大事になのが作業ディレクトリをコピーしておくこと。
超アナログだけどswift3作業は長いので、その途中で急にビルドしなければいけない時とかにcheckoutしてビルドし直すと時間のロスが大きい。gitで管理するよりより物理的に分けておけば当然ながら早い。
podfileを変更
swift3でビルドすることを明示
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
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で動くバージョンを探してビルドしてを繰り返す。
+ 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に求められる。
import Himotoki
extension GitHubRequest where Response: Decodable {
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
return try Response.decodeValue(object)
}
}
この処理を共通化する必要があり、以下のように共通のRequest classを作り環境ごとに接続先を切り替える設定やレスポンスへの共通の処理を実装した。
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の共通処理を一箇所で記述した。
感謝が止まらない。
Firebase
最初は以下を指定していたんだけどビルド時にエラーがでた。
pod 'Firebase','3.3.0'
ld: 89 duplicate symbols for architecture x86_64
見れみるとリンカエラーで他ライブラリと競合している可能性があるバージョンだったので、これはと思い以下に変更、無事ビルド
pod 'Firebase/Core'
GoogleのiOSライブラリはトラウマがある。昔あるライブラリが依存も何も見ずに動いててios8だと落ちるバグがあった。
RxSwift
厄介な変更点が多い。
rx_text
rx.text
.subscribeNext{}
.subscribe(onNext: { })
この辺はプロジェクト全体に対してreplaceをかける。もうほんとかけまくる。一つ修正が必要なのを見つけたらプロジェクト全体で似たようなコードを探してreplaceを繰り返す。心は殺す。
あとは実装ミスのせいかもしれないが以下のように実装しているとsubscribeした時に値がnilもくるようになっていた。実装方法がいけなかったのかもしれないのでもうちょっと勉強する。
let validatedEmail = Variable<String>("")
NSのプレフィックスが消えたことへの対応
NSError, NSURL, NSData, NSDateが消えることとなった。
この辺はkumagai氏の資料が熱い。
結論を引用すると
・積極的に使おう
・値型になっていることに注意
・ブリッジすると別インスタンスになる
この辺を注意してばんばんなくしていく
引数の変更によるエラーがわかりにくい
適切な引数を呼ばないとinvoke系のエラーに悩まされることになる。気をつけていこう。
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)
}
}
}
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の比較
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使えと言われるのでこの部分を消して、実装を治す
let object = object.list.count, object > 2
その他
Swift2.2からSwift3.0への変換を行ってみて
の記事がとてもよくまとまっていて参考になりました。ありがとうございます。
自動コンバートしてDeletegaメソッドの実装時に引数が間違ってて適切に呼ばれないこととかあったりして辛かった。