29077


**やりたいこと**  
URL（<https://sample.com/sample.txt>）にアクセスして、そのアドレスのテキストファイルの中身を`textView`に表示したい。

**詰まっていること**  
クロージャの中ではテキストファイルの中身が表示されるのに、外に持ち出せない。クロージャ内で`textView.text`に`webString`を代入してみたが、`textView`には何も表示されなかった。

**教えてほしいこと**  
クロージャの外への変数の持ち出しかた。そもそも、これはクロージャであっているのでしょうか？

[code]

    @IBOutlet weak var textView: UITextView!
    var webString = "default"
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let urlString = "https://sample.com/sample.txt"
        let textUrl = NSURL(string: urlString)
    
        NSURLSession.sharedSession().dataTaskWithURL(textUrl!) { (data, response, error) -> Void in
            self.webString = String(data: data!, encoding: NSUTF8StringEncoding)!
            print(self.webString) //この時点ではwebStringにテキストファイルの中身が代入されており、きちんとログ出力される。
        }.resume()
    
        self.textView.text = self.webString //webStringにテキストファイルの中身が代入されておらず、textViewには初期値の「default」が表示される。
    }
    
[/code]


CC BY-SA 3.0
2016-09-23T10:39:25.733
29075
2016-09-23T11:16:06.250
2016-09-23T11:06:35.633
5519
18443
question
swift
クロージャで代入した変数を、クロージャーの外に持ち出したい


代入できていないのではなくて、実行される順番を勘違いされています。。

`NSURLSession`のメソッドは非同期で実行されます。ネットワーク通信は時間がかかるため、その間、操作をブロックしてしまうわけにはいかないからです。

なので、このコードの実行順序は、下記のように、`NSURLSession.resume()`メソッドは通信の完了を待たずに終了し、すぐにその下の`self.textView.text
= self.webString`の行が実行されます。これが`textView.text`にデフォルトの値が入ってしまう理由です。

[code]

    // 1.
    let textUrl = NSURL(string: urlString)
    
    // 2. ただし、クロージャの中の処理はまだ実行されない
    NSURLSession.sharedSession().dataTaskWithURL(textUrl!) { (data, response, error) -> Void in
        // 4. 通信が完了した時点でクロージャが呼び出される
        self.webString = String(data: data!, encoding: NSUTF8StringEncoding)!
        print(self.webString) //この時点ではwebStringにテキストファイルの中身が代入されており、きちんとログ出力される。
    }.resume()
    
    // 3. 通信が開始する前に、2のメソッドを抜けて、この行が先に実行される
    self.textView.text = self.webString
    
[/code]

通信の結果を`textView.text`に代入するにはコールバックとしてのクロージャが呼ばれた後で行う必要があります。クロージャの中でやるのが簡単です。  
気をつける点としては、クロージャはバックグラウンドスレッドで呼ばれることがありますので、UIの更新（textView.textへの代入）はメインスレッドにディスパッチする必要があります。

[code]

    let textUrl = NSURL(string: urlString)
    NSURLSession.sharedSession().dataTaskWithURL(textUrl!) { (data, response, error) -> Void in
        self.webString = String(data: data!, encoding: NSUTF8StringEncoding)!
    
        dispatch_async(dispatch_get_main_queue()) {
            self.textView.text = self.webString
        };
    }.resume()
    
[/code]

期待した動作になるように書き直すと上記のようになると思います。


CC BY-SA 3.0
2016-09-23T11:16:06.250
29077
2016-09-23T11:16:06.250
5519
29075
answer