36448


この度、テキストファイルをダウンロードして表示させるコードを実装したのですが、

`if let resData:NSData.....` の行にて `Call can throw but is not marked with
'try'` というエラーが発生しました。おそらく、エラーの実装を行っていないからだと思うのですが、

[code]

    .....returning: nil, error: &error)
    
[/code]

といった形に実装した場合、`extra argument 'error' in call`
というエラーが出てしまい、解決させることができません。どなたかご指導お願いいたします。

[code]

    @IBAction func tapBtn(sender: AnyObject) {
        let url = NSURL(string: "http://AAA.com/AAA.txt")!
        let request = NSURLRequest(url : url as URL)
        let error: NSError?
        //　Errorはこの下の行
        if let resData:NSData = NSURLConnection.sendSynchronousRequest(request as URLRequest, returning: nil) as NSData? {
            let myData = NSString(data:resData as Data, encoding: String.Encoding.utf8.rawValue) as! String
            downloadTextView.text = myData
        }
    }
    
[/code]


CC BY-SA 3.0
2017-07-17T09:14:50.533
36444
2017-07-17T11:39:39.690
2017-07-17T09:42:24.670
19110
13543
question
swift
テキストファイルダウンロードで発生するコンパイルエラー: Call can throw but is not marked with 'try'


随分古い(Swift1の時代の)コードを参考にされたようです。ここスタック・オーバーフローを **Call can throw but is not
marked with 'try'** のエラーメッセージで検索すれば、いくつか承認された回答のついたスレッドが見つかるのですが、すぐ見つかるのはSwift
2の時代に書かれたもので、あまりにも古いのと、あなたの提示されたコードには現在は実アプリで使用してはいけないクラスやコーディングが行われていますので、改めて回答の形にまとめておきます。

(随分長くなってしまったので、一番下までスクロールしてもらって結論の部分を見てから、必要に応じて途中の部分を拾い読みしていただいた方が良いかもしれません。)

最初に言っておきますが、
**Swiftはまだ生まれて間もない言語で成長の速度が早く、バージョンアップのたびに前のバージョンとは互換性のない修正が大量に導入されています。Swiftでのコード例をネット上から拾ってくるのであれば、記事の日付を確かめてご自身の使われているXcodeのバージョンに合致するものを選ばないと、iOS/macOSでのプログラミングの学習よりも、Swift言語の変遷史を学習する羽目になってしまいます。**

(最新のSwift 3の正式版がリリースされてからもう10ヶ月以上になり、ネット上をよく探せば、Swift
3版で同じ目的の処理を実行するコード例や日本語の解説が見つかるはずです。)

* * *

まずは、Swift 2で導入されたエラー処理の基本から。

新しいエラー処理方法に基づくメソッド(メソッドのSwift版リファレンスに`throws`と表記してあります)には以下のような変更があります。

  * `NSError *`へのポインターを含む引数は省かれる

  * 戻り値の型に変更がある

Swift 1まで`Optional<T>`(`T?`)だったもの → Swift 2から非Optionalの`T`

(従って、Optional-bindingの構文は使わない。)

Swift 1まで`Bool`だったもの → Swift 2から`Void`

(従って、成功・失敗の判定に戻り値は使えない)

また、それらメソッドを使用する場合、以下のような制約があります。

  * `throws`をつけたメソッド内か、`do-catch`構文のdoブロック内で使用する

  * そのメソッド呼び出しを含む式の前には必ず`try`を付加する

  * エラーを表すオブジェクトは`do-catch`構文のcatchブロックに直接渡されるので、呼び出し前にエラー型の変数宣言は必要ない

(必要に応じて、catch節内で宣言することができる。)

以上の変更と、コンパイルエラーが出ないような必要最小限の修正を加えると、あなたが提示したコードは以下のようになります。

[code]

    @IBAction func tapBtn(sender: AnyObject) {
        let url = NSURL(string: "http://AAA.com/AAA.txt")!
        let request = NSURLRequest(url : url as URL)
        //let error: NSError? //###errorオブジェクトを受け取る変数は通常事前には宣言しない
        //### throwsするメソッド(新エラー処理に対応して修正されたメソッド)を呼ぶのは必ずdo-catch構文のdoブロック内
        //### エラー発生の判定にOptional-bindingは使えない
        do {
            //### throwsするメソッドを呼ぶ式の前には`try`が必要
            let resData:NSData = try NSURLConnection.sendSynchronousRequest(request as URLRequest, returning: nil) as NSData
            //### `NSString`から`String`への変換(`NSString?`から`String?`へも)には`as`を使う
            let myData = NSString(data:resData as Data, encoding: String.Encoding.utf8.rawValue) as String?
            downloadTextView.text = myData
        } catch let error as NSError { //###エラー変数を宣言する場合はcatch節で
            //### エラーを黙って無視するのは解消困難なバグの原因になる。最低でもエラーメッセージは出力した方が良い
            print(error)
        }
    }
    
[/code]

* * *

ただし、以下の点は修正しておいた方が良いでしょう。

  * Swift 3で導入された値型を使用していないせいで余分な`as`キャストが多数必要になっている

(`NSString`の使用に至っては、Swift1の時代から`String`型があるので、Swift1の時代のコードとしてもお勧めできない形です。)

  * Swift 3からメソッドの第一引数もデフォルトでパラメータラベルを持つようになったので、Interface Builderが自動生成するメソッド形式に合わせておいた方が良い

以上の内容を反映するとこんな感じです。

[code]

    //### @IBActionのメソッドヘッダーはXcode8の自動生成する形式に合わせる
    @IBAction func tapBtn(_ sender: Any) {
        let url = URL(string: "http://AAA.com/AAA.txt")! //### `NSURL`ではなく`URL`を使う
        let request = URLRequest(url : url) //### 'NSURLRequest`ではなく、`URLRequest`を使う
        do {
            //### `NSData`ではなく`Data`を使う
            //### Swiftが絶対に型推論を間違えないところでは型の選択はSwiftに任せる
            let resData = try NSURLConnection.sendSynchronousRequest(request, returning: nil)
            //### `NSString`ではなく`String`を使う
            let myData = String(data: resData, encoding: .utf8)
            downloadTextView.text = myData
        } catch let error as NSError { //### 通信系のエラーには`code`に重要情報が入るので、ここだけ`NSError`のまま
            print(error)
        }
    }
    
[/code]

余計な`as`キャストがほとんど不要になったことがわかっていただけると思います。

* * *

さて、上記の形に修正を済ませたコードを最新のXcode
8.3.3でビルドすると(あるいはビルドしなくてもソースコードエディターの診断機能が正常に動いていれば)、
**'sendSynchronousRequest(_:returning:)' was deprecated in iOS 9.0: Use
[NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h**
という警告が表示されるはずです。

`NSURLConnection`クラス自体には明示的な`deprecated`宣言(廃止予定のため非推奨)が付いていないので、メソッドの方にしか警告が表示されませんが、実はクラス自体の使用が非推奨です。

また`sync...`で始まる同期型メソッドを実アプリに使用してはいけません。ネットワーク通信が正常に行えない環境でアプリが使われた場合に(AppleはApp
Storeの審査で必ずこのチェックをするようです)タイムアウトエラーが発生するまでの非常に長い間、アプリがフリーズした形になってしまいます。

ここは警告で推奨される書き換えを行うべきところでしょう。警告内のコードはObjective-
C形式になっていますが、Swiftだと以下のような形で使うことになります。

[code]

    @IBAction func tapBtn(_ sender: Any) {
        let url = URL(string: "http://AAA.com/AAA.txt")!
        let request = URLRequest(url : url)
        //### `URLSession`を使う場合の基本パターン
        URLSession.shared.dataTask(with: request) {data, response, error in
            guard let resData = data, error == nil else {
                //### `error`は`Error?`型なので、`code`などの情報が欲しい場合は`NSError?`にキャストしてから使う
                if let error = error as NSError? {
                    print(error)
                }
                if data == nil {
                    print("data == nil")    //<-こちらのみが発生することはないはずだが念のため
                }
                //### 異常時には以下のコードは実行せずにcompletionHandlerから出る
                return
            }
            let myData = String(data: resData, encoding: .utf8)
            //### `dataTask(with:completionHandler:)`メソッドに渡されるクロージャーの中なので`self.`が必要
            self.downloadTextView.text = myData
        }.resume() //### `dataTask(with:completionHandler:)`の戻り値である`URLSessionDataTask`の`resume()`メソッドを呼ぶことで実際のデータ取得処理が始まる
    }
    
[/code]

いかがでしょうか。ネット上をうまく探していれば、初めからこの最後のコードに極めて近いコードを見つけることができたはずです。会社内の古いコードを最新のSwiftに変換する役割をもらった、とかいうのでない限り、ネット(あるいは書籍?)から情報を拾ってくる場合には最新の内容かどうかを十分吟味してから使用することをお勧めします。

**EDIT**  
当初のコードには`.resume()`が抜けておりました。これを忘れると、一番肝心な処理が起動されません。失礼しました。


CC BY-SA 3.0
2017-07-17T10:55:34.450
36448
2017-07-17T11:39:39.690
2017-07-17T11:39:39.690
13972
13972
36444
answer