プロジェクトをXcode 8/Swift 3に変換した時に自動変換できなかった結果から変更点をチェックする

Xcode 7/iOS 9.3のプロジェクトをXcode 8で読み込んでSwift 3を利用するように変換した時の自動変換結果については、こちらに記述しました。そして、ターゲットをiOS 10にして、一部、赤いエラーが出たところなどを直すことになります。それらについてまとめておきます。

署名ができない!

実は、この問題に最後まで苦しみました。署名に関するプロジェクトの設定をあれこれいじってもエラーが出て完了しませんでした。エラーのログをよく見ると、こんなメッセージが見えました。

xxxx.app: resource fork, finder information, or similar detritus not allowed
Command /usr/bin/codesign failed with exit code 1

しかし、リソースフォークをいじった記憶もありません。そこで検索すると、stack overflowのサイトに答えがありました。どうやら、画像なんかにはアプリケーションが勝手にリソースをつけていたりすることがあるので、それを消せばいいということでした。リソース等は署名対象外にしないといけないというのは、最近ルールとして増えたのかもしれません。プロジェクトのルートをカレントディレクトリにして「xattr -rc」、これでエラーがなくなりました。このコマンドは、リソースフォークやFinder情報などのファイルの付加情報を消します。-cで消す、-rでサブディレクトリをすべてさらいます。もちろん、ファイルそのものは消しません。拡張情報の処理コマンドです。

変換されない箇所が残るパターン

NSURLをURLは自動的に変換してくれると思ったのですが、以下のようなパターンの場合は、自動変換してくれず、手動で書き換えました。NSを消すだけですが、AnyObjectはAnyにするなどを知っておかないと作業は面倒になります。完了時のクロージャーはもしかすると、キーワードなしで、閉じかっこの次に記述しておけば変換してくれたのかもしれませんが、これは想像です。

  • variable as Dictionary<String, NSURL>?
  • self.webView.evaluateJavaScript(script, completionHandler: {(obj: AnyObject?, error: NSError?) -> Void in ….

返り値があるのに代入していない場合

例えば、以下のようなプログラムがあるとします。いずれも、返り値のあるメソッド呼び出しであり、インスタンス生成です。これらは、「Result of call to ‘lengthOfBytes(using:)’ is unused」などというメッセージで警告となります。返り値が使われていないというのです。以下のサンプルはさておき、返り値の不要な場合は時々あります。いちいち変数を定義すると、今度はその変数が使われていないと言った警告が出ます。(ちなみに、おなじみの定数「NSUTF8StringEncoding」は、「String.Encoding.utf8」のように、よりスッキリした記述に変わっています。)

"test".lengthOfBytes(using: String.Encoding.utf8)
String("")

この場合、以下のように、_ = に続いてステートメントを記述します。つまり、空代入のようなことをするということです。これも、返り値があるのに忘れているようなミスを防ぐためということになっていますが、ちょっとやりすぎな気もしますね。

_ = "test".lengthOfBytes(using: String.Encoding.utf8)
_ = String("")

#if等でコンパイル時の分岐がある場合

Xcode 8でのコンバートをする時に、定義定数がない状態でのコードが変更されます。したがって、#if等で変数が定義されているかどうかを確認するような場合、ELSE側だけが変換されました。変換されない方は手作業で変更するしかありません。

アプリケーションのバッジを稼働させる方法

バッジ等のユーザー通知をアクティブにするには、iOS 9まではAppDelegateクラスのapplication(_:didFinishLaunchingWithOptions:)メソッドに、例えば以下のようなプログラムを書いていました。変数applicationはメソッドの第1引数です。

let settings = UIUserNotificationSettings(types: UIUserNotificationType.badge, 
                      categories: nil)
application.registerUserNotificationSettings(settings)

記述方法が以下のように大きく変わります。UNUserNotificationCenter(User NotificationフレームワークのUNが頭文字)クラスのインスタンスをcurrentメソッドで得て、requestAuthorizationメソッドで機能の利用を要求します。引数は定数(.badgeがホーム画面のアプリケーションのアイコンにつけるバッヂの許可で、他に.soundなどの定数がある)の配列を指定します。そして、処理完了時に実行されるクロージャーが続きます。許可の可否に応じて処理をすることができますが、何も書かなくても動作はします。

let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.badge]) { (granted, error) in}

ちなみにrequestAuthorizationは2つの引数を持つので、次のような記述が可能です。むしろ、こちらは定義通りです。しかしながら、「最後の引数のクロージャー」については、引数の最後を示す閉じかっこの後に記述できる(Trailing Closure)ので、上記の書き方となります。

center.requestAuthorization(options: [.badge], completionHandler: {(granted, error) in})

なお、処理完了時のクロージャーに何も処理がない場合の一番短い書き方は、これではないかと思われます。

center.requestAuthorization(options: [.badge]){_,_ in}

openURLがdeprecatedになった

UPApplicationクラスのopenURLメソッドがdeprecatedになり、openを使うようにと警告が出てきます。ただし、引数が増えます。最初の引数にURLを指定するのは同じですが、optionsと完了時のクロージャーも記述します。それまで、openURLを使っていたのなら、おそらく当初は増えた引数は何も指定しないでOKでしょう。options:の後にnilを指定したらエラーになります。なので、中身が空のDictionaryを指定するのが手軽だと思います。そして、最後のクロージャーは閉じかっこの後に記述します。何も処理がない場合でも、引数並びは評価されるためnilは指定できません。いかが定義通りではありますが、もっと短くしたいのなら、{_ in} という記述でもいいでしょう。

// iOS 9
UIApplication.shared.openURL(navigationAction.request.url!)
// iOS 10
UIApplication.shared.open(navigationAction.request.url!, options:[:]) {(Bool) -> Void in}

NSRangeとRangeの変換は相変わらずできない

これ不便ですよね。ただ、文字列処理だけしていればRangeだけしか出ないのかもしれませんが、UITextFieldDelegateのメソッドなんかは文字列の変更箇所がNSRangeでやってくるので、Stringに対しての処理をしたい場合は、それをRangeに変更したいわけです。もちろん、NSStringで処理ということもありますが、せっかくSwiftなのだからStringで処理をしたいわけです。iOS 9の時には、自分で変換メソッドを作っていました。そこではadvancedBy(_:limit:)を利用していたのですが、Swift 3にはないらしく、index関数を利用して改めて作り直しました。extensiondでStringクラスにメソッドを追加して変換しています。以前のメソッドよりもだいぶんシンプルに短く書いていますが、それは自分自身のSwiftの知識が増えたからかもしれません。

extension String {
    func rangeFromNSRange(_ nsRange : NSRange) -> Range {
        let startIndex = self.startIndex
        let fromIndex = index(startIndex, offsetBy: nsRange.location)
        let toIndex = index(fromIndex, offsetBy: nsRange.length)
        return fromIndex ..< toIndex
    }
}

Any has no subscript members

AnyObjectで定義されたプロパティに対して、x[“test”]のようなさぶスクリプトが、iOS 9.3の時(あるいはSwift 2.3の時)には機能していたと思うのですが、Anyに対してはサブスクリプトが機能しないようなので、as! Dictionary<String, Int> といった明示的なキャストをする必要があります。