お久しぶりです。最近ウスネオイデスの育成にハマってます
仕事でSwift3対応をして出会ったいくつかの不具合をまとめました。
もしこれからSwift3対応するぞー!っていう人がいれば参考になればと思います。
DeviceTokenの取得方法が変わった
まずはこれです。Swift3対応をしたら必ずと言っていいほど出会うのでないでしょうか
Swift2系まではこんな感じで取得していたdeviceTokenですが
let token = (deviceToken.description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")) as NSString).replacingOccurrences(of: " ", with: "")
Swift3環境で動かすと
いままで64文字が取得できていた上記のコードでは"32bytes"という文字列が吐かれるようになってしまってます。
32bytesの文字量の文字列ではありません。"32bytes"です。意味不です
これを打開するには以下のようなコードを書く事で回避することができます。
let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
pushはアプリの起動動線として非常に重要なものですのでしっかりと抑えて置きたいとこです。
※上記対応の引用元: http://qiita.com/mono0926/items/3cf0dca3029f32f54a09
通知設定情報の取得方法が変わった
続いてもpush周り。
端末の通知設定の取得でこんなコード書いていませんでしたか?
guard let currentSettings = UIApplication.shared.currentUserNotificationSettings else { return }
currentSettings.types != .none
これでもビルドはとおります。ただし、期待してる動作はしません。
Swift3の環境からはtypesの中身は
types = [UIUserNotificationType.badge, UIUserNotificationType.alert]
こんな感じでリストで返ってくるようになったようです。
なのでプッシュ設定が許可されていないかどうかのチェックをする場合は
types.isEmpty // or types == []
で確認することになりそうです。
Error と NSError
swift3からNSプレフィックスが取れたのでNSErrorも同様にErrorとして一皮剥けました
その結果どうなったかっというとこれまでNSErrorでは結構自由にlocalizedDescriptionとかadditionalUserInfoに対して情報を突っ込めてましたがそれができなくなってます。code等がまるっきり参照できなくなってますね。
幸いErrorとNSErrorで相互変換が可能なので例えば
extension Error {
var code: Int {
let nsError = self as NSError
return nsError.code
}
}
こんな感じのエクステンションを作るだけでこれまで通りのcodeを取得できます。description等も同様です。
encode/decode
これ結構事故ります。アップデート気をつけてください
UserDefautlsやファイルキャッシュ等で自作のクラスを保存する場合NSKeyedArchiver, NSKeyedUnArchiverすると思います。
クラスのプロパティ等は
Swift2系
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject("hogege", forKey: "fugafuga")
aCoder.encodeBool(true, forKeyu: "fugafugafuga")
}
required init?(coder aDecoder: NSCoder) {
stringValue = aDecoder.decodeObjectForKey("fugafuga") as? String
boolValue = adecoder.decodeBoolForKey("fugafugafuga")
}
こんな感じでencode/decodeしてたかと思います。
Swift3系ではencodeが賢くなり
func encode(with aCoder: NSCoder) {
aCoder.encode("hogege", forKey: "fuagafuga")
aCoder.encode(true, forKey: "fugafugafuga")
}
引数で受け取ったobjectの型で勝手によしなにencodeしてくれるようになりました。
取り出す時は取り出したいものの型を厳密に指定してdecodeObjectやdecodeIntegerを使いこなさないといけなくなりました。
しかし、ここで問題なのが過去バージョンで
encodeObject(true, forKey: "fugafuga")
encodeObjectでObjectでないもの(Int値だったりBool値)だったりをdecodeして保存してしまった場合です。
これをちゃんと型を意識してとりだそー!と考えて
decodeBool(forKey: "fugafuga")
とすると、クラッシュします。
decodeObject(forKey: "fugafuga") as? Bool
こうやって取り出すしかなくなります。
お、これで終わりだと思うと次の落とし穴に落ちます。
先ほど述べたようにSwift3からはencodeは引数を見てよしなに保存してくれます、そして取り出す時も厳密に指定してあげないと値を取り出せません。
つまり上記のパターンの場合decodeObjectを使ってInt値を取り出せるのは過去バージョンからのアップデート後1回きりでその後は通常時と同じようにdecodeInteger(forKey:)を利用しないと値を取得できません。
つまり以下のような記述をしないといけなくなります。
if let oldSaveValue = decodeObject(forKey: "fugafuga") as? Bool {
value = oldSaveValue //旧バージョンのサポート
} else {
vaiue = decodeBool(forKey: "fugafuga")
}
悲しいなぁ
名前空間を意識したencode/decode
非常にレアケースかと思いますが一応記載しておきます。
class A {
class B {
}
}
このような構造のクラスがあります。
そしてこのクラスBをNSKeyedArchiverを利用してdata化して保存します。
そして時がきたらNSKeyedUnArchiverをして利用します。
上記の操作ですが
Optimaization Levelを
Fast. Whole Module Optimization [-o]
以上にするとクラッシュします。
どうやら正式名称 A.BクラスなところをBクラスとしてencodeしてしまい。
decodeするときに「Bクラス?どこだよくっそもうわけわかんねぇ!落ちよ!!」ってなるみたい。
一応
NSKeyArchiver.setClassName(A.B.self, forClassName: "B")
みたいに道案内してあげるとちゃんと動くがメリットなさすぎコストかかりすぎだと思うので特に意味がないようなら外だししてあげるのが無難
JSONのパース
これも結構限定的な事象か?
NSJSONSerialization.JSONObjectWithDataから取り出せない型を含んだDictionaryの扱いについてです。
これまでは暗黙的にInt型をBoolにキャストできていましたがSwift3からはできなくなります。
例
let dic: [String: Any] = ["test": (1 as Int)]
// Swift2.3
let s2_3 = dic["test"] as? Bool // true
// Swift3.0
let s3_0 = dic["test"] as? Bool // nil
もしテスト等でJSONを作る等あればIntは使わずNSNumberを使うこと
あとがき
一旦これで僕が(僕のいるチームが)Swift3対応した中で出会った不具合たちです。
だいたい上記のものを抑えとけば深刻な不具合は産まないと思います。
また何か見つかれば追記していきようにします
これをみた人にSwift3対応がうまくいきますように・・・